Jun 16, 2007

Formatting date to client timezone

Unlike a locale information that is passed by browsers via HTTP header, there is no straightforward way to retrieve the information about client timezone offset.
I also have not found any ready-to-use solutions to workaround this in web and one day when we've been posted the bug that our SMS sending times appears to be in the server timezone. I've found the solution.
The idea is pretty easy, the only way you can pass client timezone to the server is by calculating it using

var tzo = new Date().getTimezoneOffset();        


Next step - you need to pass it to the server, there is a number of ways to do it but they all have one drawback, you cannot format dates from the just loaded page, since script executes on the client. Anyway the options are: Cookie and AJAX. Cookie is easier and seems that AJAX is overkill here, that is why I've chosen cookie way.

The whole code on the client looks like
<c:if test="${sessionScope['sonoportal.timezoneOffset'] == null}">
<!--Setting TZ offset-->
<script type="text/javascript">
var tzo = new Date().getTimezoneOffset();
document.cookie = "timezoneOffset=" + escape(tzo * (-1));
</script>
</c:if>

We've also added the condition not to send cookie if we already have info about user timezone offset in the HttpSession.

Now we need to process the timezone on server and set it to the session. In our application we are using standard JSTL fmt:formatDate for formatting dates and times. Fortunately you can set default timezone in the user's HttpSession scope and it will be used authomatically. Here is the code for a Filter that do the job:

package com.sonopia.sonoportal.web.filter;

import java.io.IOException;
import java.util.TimeZone;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.jstl.core.Config;

import org.apache.commons.lang.time.DateUtils;

/**
* @author Mykola Paliyenko
*/
public class SetTimezoneOffsetFilter implements Filter {

public static final String PROPERTY_TIMEZONE_OFFEST = "sonoportal.timezoneOffset";

public static final String COOKIE_NAME = "timezoneOffset";

public void destroy() {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getSession().getAttribute(PROPERTY_TIMEZONE_OFFEST) == null) {
if (req.getCookies() != null) {
for (Cookie cookie : req.getCookies()) {
if (COOKIE_NAME.equals(cookie.getName())) {
int timezoneOffsetMinutes = Integer.parseInt(cookie.getValue());
TimeZone timeZone = TimeZone.getTimeZone("GMT");
timeZone.setRawOffset(
(int) (timezoneOffsetMinutes * DateUtils.MILLIS_PER_MINUTE));
Config.set(req.getSession(), Config.FMT_TIME_ZONE, timeZone);
req.getSession().setAttribute(
PROPERTY_TIMEZONE_OFFEST, timezoneOffsetMinutes);
// removing cookie, Sun sucks as usual in their API
cookie.setMaxAge(0);
res.addCookie(cookie);
}
}
}
}
chain.doFilter(request, response);
}

public void init(FilterConfig config) throws ServletException {
}

}


Note that we are removing cookies once we get the timezone offset to reduce client/server traffic since otherwise it get send with every single request.
What remains? Declaring filter web.xml file
        <filter>
<filter-name>SetTimezoneOffsetFilter</filter-name>
<filter-class>
com.sonopia.sonoportal.web.filter.SetTimezoneOffsetFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>SetTimezoneOffsetFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>



Thats all. Now your site is one of a little set of sites that care about user's time zone.

Limitations/Drawbacks


- First served page in session will show the server times.
- If user changing timezone within session it will not be tracked by server. It is a very minor issue but still
- Additional global filter for the application

8 comments:

jdev said...

Thanks for the quick and clean solution that doesn't require any additional tricks with fmt:formatDate
alike custom tags. I wish you blogging with never-fading enthusiasm :)

Rajan Chandi said...

Hi...I found this post to be useful...can I exactly know how to use fmt:formatDate tag here?

If I just use fmt:formatDate..that doesn't help much. as It displays the date local to the server. If I don't want to use the variable, I don't need to set the scope attribute in the fmt:formatDate.

Thanks in advance.

Rajan

Mykola Paliyenko said...

Rajan, just use it, after you will done all steps described above fmt:formatDate will format all dates to the client timezone, not to the server one.

HTH

kunjal said...

Cant you do the same thing - setting the getTimezoneOffset() in the request parameter and pass that to the server in a hidden parameter (form post). This would also require an additional server trip as your solution (cookie); but would be much more easier. If someone has ideas on how to do this in AJAX let me know as this would be an asynchronous call back and wont require a page refresh.

Amir said...

I know this is an old post but you can now get locale from the request object.

getLocale()
http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/ServletRequest.html#getLocale()

Alexogar said...

This is not working in case when you need to get timezone with daylight changes(e.g Moskow). I `m try to find solution now.

timeZone.useDaylightTime() // false ever.

Bowie Brotosumpeno said...

Thanks a lot, I'm looking of this to deal with my website a sort a Calendar.

Anton B. said...

It is not handle daylight-saving time.
It is not handle changing of timezone with a time.