Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In JSF - Getting the Client's Locale (To Display time in browser's timezone)

I'm writing a webapp in JSF 2.0 that displays Timestamps as part of the information to the user. I would like the user to see the timestamps localized to their location (browser's locale).

So far, whatever I did, the timestamps would always appear localized to the timezone of the server.

I tried getting the locale with these methods:

Locale browserLocale = FacesContext.getCurrentInstance().getViewRoot().getLocale();

or

Locale browserLocale = FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();

Both returned the server's locale.

I then use the locale object with SimpleDateFormat to print timestamps.

Am I using the correct API?
I've read somewhere that you have to use client side code (Javascript) to get the browser's timezone. Is that correct? How would you do that?

Thank's 'n' Advance.

UPDATE found this (jsTimezoneDetect) JavaScript code. But I'm still not sure how to translate the timezone to a Java Locale Object

like image 689
Ben Avatar asked Dec 22 '11 07:12

Ben


4 Answers

You probably should use built-in formatting tag instead of SimpleDateFormat. Your question implies that you want to show date and time to International user, and in this case you should really use user's local format (they tend to differ, you know).

In case of time zone, it has nothing to do with Internationalization and Localization, i.e. there are several different time zones in USA. There are two approaches you can use here:

  1. Store time zone information in the user profile (if you have one). This is the easiest way and allow you to use built-in <f:convertDateTime> tag.

  2. Get time zone information from web browser. You can get it via Ajax request just like in Ben's example. Technically you can also use <f:convertDateTime> tag here.

  3. You can send the timestamps in UTC in some common, locale-independent (or invariant if you prefer) format, parse it on the client side to create JavaScript's date object and format for locale with Globalize.

Some examples will follow but let me explain something first.

Locale browserLocale = FacesContext.getCurrentInstance().getViewRoot().getLocale();

This will give you web browser's locale (but not time zone, since this is not locale related). It will actually read the contents of HTTP Accept-Language header and choose the best possible locale. If it is not working for you, please make sure that you have correctly set supported locales in your faces-config.xml. By best possible Locale, I understand that it will try to use user's most preferred Locale (if that's supported by your application), then second best and so on. If none of the Locales is supported, it will fall-back to your application's default Locales (again, faces-config.xml has to have this setting) or to server's default Locale if this setting is missing (or at least I think so, it kind of makes sense).

Locale browserLocale = FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();

This one will give you the top Locale from Accept-Language header. Please check your web browser's settings - there is almost no way for it to give you the server Locale, unless they are exactly the same as your web browser's. It can give you server's defaults if and only if, none of the web browser's Locale is supported by JVM (which seems a bit unlikely).

BTW. FacesContext.getCurrentInstance().getExternalContext().getRequestLocales() will give you the Iterator so you can manually iterate through the list of Locales in Accept-Language header. It is just to let you know, you probably should not use it (UIViewRoot is really enough).

Now, suppose you have some bean with the user profile and the method which will give you the time zone. This method is better than Ajax call, in the sense that it might happen that two different time zones have the same UTC offset but switch Daylight Saving Time on different date (in other words some timestamps would be printed incorrectly). Anyway, in case like this, you can format your time-stamp like this (date also come from some bean):

<h:outputText value="#{someBean.timestamp}">
  <f:convertDateTime type="both" dateStyle="default" timeStyle="default" timeZone="#{userProfile.timeZone}" />
</h:outputtext>

Now let me explain the attributes:

  • type - what to show, both means date and time
  • dateStyle - style of date (out of short, medium, long, full, default). You should really use default here as this will use the most proper format for each Locale
  • timeStyle - similar to date style but for time part
  • timeZone - takes either an UTC offset (so you don't need to convert anything) or time zone name (i.e. America/Los_Angeles).

The tag will use current view Locale by default, so you do not have to worry about this part, especially if you set up Locale support correctly.

Combining it with Ajax (see Ben's answer) would be easy enough (I think).

I also mentioned that you can write out invariant dates, parse them on the client-side and then format them with Globalize. Assuming that you have parsed date already (it depends on the representation you want to use, so I will skip this part), it could be done like that:

// you also have to assign the culture based on UIViewRoot locale and send it out with JavaScript
Globalize.culture(theLocale);
var formattedDateTime = Globalize.format(parsedDateTime, "f"); // this will use short date time format

Unlike Java, Globalize have only short ("f") and long ("F") date and time formats. So I decided on using short one.
Please also keep in mind, that Globalize cultures are separated via hyphen, not underscore, so you need "fr-CA", not "fr_CA" for example.
Please let me know if you want to use that method and need more concrete example.

like image 192
Paweł Dyda Avatar answered Oct 18 '22 18:10

Paweł Dyda


Succeeded. Here is what I did:

Added to JSF a hidden input field so I can send JavaScript values to the server:

<h:form prependId="false">
    <h:inputText id="timezone_holder" value="#{bean.timezone}" styleClass="hide">
        <f:ajax listener="#{bean.timezoneChangedListener}"></f:ajax>
    </h:inputText>
</h:form>

Using the plugin above, I ran JavaScript code that retrieved the offset of the browser.

$(document).ready(function() {
    var timezone = jstz.determine_timezone();

    $("#timezone_holder").val(timezone.offset());
    $("#timezone_holder").change();
});

When the timezone input is changed (initiated from the javascript code above) I run this code in the eventListener:

String strFromJavaScript = getTimezone();
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext()
                .getRequest();
Locale browserLocale = request.getLocale();
TimeZone tz = TimeZone.getTimeZone("GMT" + strFromJavaScript);

// set time zone
SimpleDateFormat formatter = new SimpleDateFormat("MMM d, yyyy, HH:mm", browserLocale);
formatter.setTimeZone(tz);

Then, whenever I need to format a date I use the Date Formatter that was created above.

like image 30
Ben Avatar answered Oct 18 '22 17:10

Ben


Another option would be to create a cookie on a Javascript that executes when the home page is ready. After that, the cookie will exist on each subsequent request and would be available

Your Javascript could use jQuery and jsTimezoneDetect

    $(document).ready(function() {  
        setTimezoneCookie();
    });

    function setTimezoneCookie() {

        var timezone = jstz.determine().name();

        if (null == getCookie("timezoneCookie")) {
        document.cookie = "timezoneCookie=" + timezone;
        }
    }

    function getCookie(cookieName) {
        var cookieValue = document.cookie;
        var cookieStart = cookieValue.indexOf(" " + cookieName + "=");
        if (cookieStart == -1) {
            cookieStart = cookieValue.indexOf(cookieName + "=");
        }
        if (cookieStart == -1) {
            cookieValue = null;
        } else {
            cookieStart = cookieValue.indexOf("=", cookieStart) + 1;
            var cookieEnd = cookieValue.indexOf(";", cookieStart);
            if (cookieEnd == -1) {
                cookieEnd = cookieValue.length;
            }
            cookieValue = unescape(cookieValue.substring(cookieStart, cookieEnd));
        }
        return cookieValue;
    }

Your Facelet would then use the cookie's value if it exists:

<h:outputText value="#{bean.time}">
   <f:convertDateTime
       dateStyle="full"
       timeStyle="full"
       type="both"
       timeZone="#{cookie.timezoneCookie.value}">
   </f:convertDateTime>
</h:outputText>
like image 29
Ghasfarost Avatar answered Oct 18 '22 17:10

Ghasfarost


You may want to try jsTimezoneDetect to detect timezone on the client side and send to the server.

UPDATE: to get a user's Locale, you can try the following:

HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
Locale locale = request.getLocale();

Returns an Enumeration of Locale objects indicating, in decreasing order starting with the preferred locale, the locales that are acceptable to the client based on the Accept-Language header. If the client request doesn't provide an Accept-Language header, this method returns an Enumeration containing one Locale, the default locale for the server.

like image 32
Mr.J4mes Avatar answered Oct 18 '22 16:10

Mr.J4mes