Liferay & Timezones

Liferay expects the JVM to use the GMT timezone, and you should too.

One thing that I commonly see Liferay admins doing is changing the user.timezone JVM parameter to their local timezone.

I mean, I get why this might seem like a good idea. I only serve users around me who are in the same timezone. I don't like the logs showing the GMT time instead of the local time. I want everything to reflect my particular timezone. When I query the database, it shows timestamp columns in the wrong TZ because my database doesn't know the time is actually in GMT...

I'd like to talk about reasons that you really shouldn't do this...

#1 - It's Not Tested

Liferay actually does a lot of automated testing before the release of every Fixpack, Hotfix, and especially the GA releases.

But one thing that doesn't get tested is a non-GMT JVM. This can hide bugs that exist in the code. Sometimes these bugs get reported (only by admins not using GMT), sometimes they just remain hidden if you're lucky enough to be so small-scale that you don't have to cross a timezone.

The bugs start to show themselves however as soon as something happens in a different timezone; for example, if an admin changes the instance's default timezone or a user changes their timezone. These kinds of things will cause all kinds of odd visual and runtime issues when Liferay's internal TZ stuff is doing it's thing.

#2 - Nobody Plans on Staying Local

When I hear admins say things like "I'm only serving people close by me", I always counter with "Do you plan to stay that way forever?"

Nobody starts a business with plans of keeping it small, local and keeping it from growing outside of their current market. Everyone wants the business to exceed and grow, bringing the riches and benefits from having the next big thing. That's just the nature of business.

Even if you're a non-profit or you're hosting an intranet or whatever, the people in charge hope for growth and want and need a system capable of serving the whole world, not just the small portion you might occupy at the moment.

#3 - It is Hard to Change Later On

Every record in your database will have some kind of timestamp associated with it. When was the record updated? When was the article published? What is the expiration time? When did the scheduled job run and when should it run again?

I can tell you from experience that if you do use a non-GMT timezone and need to change it in the future, it is really a challenge. Some times will be easy to update in the DB when they are part of columns, but others that might be captured inside a CLOB column holding JS or XML with an embedded time in it will be more difficult to change.

But Why Use GMT?

Presenting local times to users is basically a result of math. The math can be easy or complicated, depending upon your approach.

If I am in EDT and you, the reader, is in Berlin, Germany, well your timezone is CEST. There's a 6 hour time difference between us, so any time I put in such as 3pm, I can add 6 to it and show the time to you as 9PM.

Things get more complicated though when I need to show that 3pm time to someone in Perth, Australia (+12 hours away from EDT) or Mumbai (+13.5 hours away from EDT). I'd pretty much need a table to have the calculations of adds/subtracts to convert times from EDT to whatever timezone a user happens to be in.

And that is just for converting EDT. For someone in Berlin, they would need the table to convert CEST to other timezones so their times will be represented.

This leads to a big matrix of complex data for time conversions, basically an unwieldy mess. Add Daylight Savings Time into the mix and the matrix goes from bad to worse.

The mess can be eliminated, though, by using GMT. All timezones themselves are offsets from GMT. So if we take my 3pm EDT time and store it as 19:00 UTC, we know how to get to CEST by adding 2 hours to UTC, so it is 21:00 or 9pm.

Conversion for any timezone is easily handled by doing the two step - Convert the source time to GMT, then convert to destination time.

So this is what Liferay does - every time is itself converted into GMT for storage. Even if you think you are using your local timezone in the JVM, Liferay thinks that it is actually in GMT. Now this kind of thing can work as long as the instance default timezone is GMT and users also have their timezones as GMT (basically doing no TZ calculations at all), but as soon as one of these things change, that's when the bugs start coming out. If the JVM is configured to use EDT but Liferay thinks it is still GMT and a user sets their own timezone to EDT, the 3pm EDT they think they should see reports as 11am because Liferay does the calculation on what should be GMT to EDT and subtracts 4 hours.

Leaving the JVM as GMT is the right thing to do. So how should you handle timezones?

How to Handle Timezones

Okay, so how are you supposed to handle timezones in Liferay?

First rule we have already covered, leave the JVM's user.timezone set to GMT. Period.

For your Liferay instance (company), you can set the default timezone using either the company.default.time.zone property in portal-ext.properties before first launch, or you can use the control panel to set the default timezone for the instance after first launch.

For users, they can (and should) set their timezone, even if you set it for them (i.e. using a model listener or service wrapper to make sure at least the company timezone is used). This is the timezone setting that is most important because it is the one every Liferay tag knows to use when presenting times.

This is how you ensure that times expressed locally will be presented in the timezone that is appropriate for the user that is viewing the data.

What Problems Won't Be Solved

So there are still some outstanding issues to be aware of, ones that don't go away.

Your logs are going to show all times as GMT since the JVM thinks the time is in GMT. There are ways around this though. The Apache Extras for Log4j (http://logging.apache.org/log4j/extras/) has an org.apache.log4j.EnhancedPatternLayout class that can handle TZ conversions; by adding the jar to ROOT/WEB-INF/lib and then changing the portal's log4j.xml to use this layout, the logging can reflect local timezones instead of GMT.

The database will also be an issue. Some databases (i.e. MySQL) expect storage to be as UTC and will likely handle the presentation of times correctly. Other databases, not so much. Each database though has ways to configure so the DBMS knows the column data is in UTC however, but you'll have to figure out how to do all of that configuration effort. Or you could just listen to me when I say don't look inside the Liferay database :)

 

4
Blogs

Good post and in my opinion not only applicable for Liferay.

We all should have enough mathematic knowledge to calculate GMT to our actual time.

The math is necessary, certainly, but the point I wanted to make in the post is that it is important to simply not just think locally anymore.

 

The world now is too big, any project started today that only considers a local timezone is either going to be short lived or will be a barrier to growth.

 

Whatever your application is, look at it as though it is the next Amazon.com or Microsoft.com or Walmart.com or Google.com. I guarantee you these folks have never considered using only a single timezone in their platform.

 

And neither should we...

It is also documented here:

   1- https://help.liferay.com/hc/en-us/articles/360029031471-Preparing-for-Install#jvm-requirements    2- https://help.liferay.com/hc/en-us/articles/360018175171-Locales-and-Encoding-Configuration#set-the-jvm-time-zone-to-gmt    3- https://learn.liferay.com/dxp/7.x/en/installation-and-upgrades/installing-liferay/installing-liferay-on-an-application-server/installing-on-tomcat.html?highlight=user%20timezone%20gmt

And in case you start Liferay with user.timezone != GMT, a warning trace will be written telling you to not doing it, see:   - https://github.com/liferay/liferay-portal/blob/2d2e8ae74e9e7b33a07328277da64f54db576a04/portal-impl/src/com/liferay/portal/internal/servlet/MainServlet.java#L271-L286