Debugging timezone issue in Java [Linux]
Tags: java, linux, timezone, debugging
Published: 2022-03-18 08:28:40


Have you ever noticed that in some cases, Java will not take the time zone which is configured in a server? This has serious impacts if the server handles scheduling tasks and runs them periodically.

Before diving deep down, let’s understand what is a time zone?
> **Time Zones** are geographical world globe division of 15 degree each, starting at Greenwich, England. It’s a uniform standard time for legal, commercial and social purposes.

### Issue: Timezone difference between the system and java

While executing the below code, It will print the system time along with the timezone. I’ve added the output of the date command as well.

![Example code](https://cdn-images-1.medium.com/max/5008/1*mKC8gBc2UhmZByRGt3Gujg.png)

![Output](https://cdn-images-1.medium.com/max/3000/1*GTe_7g1JfQjtawUbTNDT7w.png)

Even though the system timezone is configured as PST, java prints the timezone as GMT.

### Quick fix:

 1. Set user.timezonein the command line as `-Duser.timezone=America/Los_Angeles`

 2. Set the environmental variable `TZ` to provide the timezone.

The above solutions will work, but to understand and fix it once for all, read further for the [actual fix](#actual-fix). (All required source code links are embedded in the method names)

### Detailed explanation on why do we get this timezone difference?

In the example java code, we call `System.out.println` of `new Date()`. We know that java calls an object’s `.toString()` method internally to provide the string representation of an object. Let’s take a look at the `toString` method of the [Date class](https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/Date.java#l999) in java.

![](https://cdn-images-1.medium.com/max/7048/1*4Q8Yodid7FJeAlcG5aAKSQ.png)

The timezone on `Line:16` is set by calling the `date.getZone()`. The variable `date` is initialized by calling the [normalize](https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/Date.java#l1193)() method.

![](https://cdn-images-1.medium.com/max/6232/1*Lj9TkYNhaQDDeq_z5laRqw.png)

The timezone in `normalize` is set by calling the[TimeZone.getDefaultRef](https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/f940e7a48b72/src/share/classes/java/util/TimeZone.java#l632)() in `TimeZone.java` which then calls [setDefaultZone](https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/f940e7a48b72/src/share/classes/java/util/TimeZone.java#l643)()to get the system timezone.

![](https://cdn-images-1.medium.com/max/4816/1*0gCF3uPscMPqzi8Am08NCA.png)

![](https://cdn-images-1.medium.com/max/5016/1*px4rBDwNG7gydtASZcH0EQ.png)

Now, let's break up and understand what the `setDefaultZone()` does.

 1. It checks for the zone by checking the `user.timezone` property (which we didn’t set via the command line).

 2. If the `user.timezone` property is `null`, it gets the `java.home` property and calls the `getSystemTimeZoneID()` which is a **native** method.

 3. Any `NULL` values will set the timezone to default GMT. (So, this is why the timezone is set to GMT!)

![Java Native method to get the system timezone](https://cdn-images-1.medium.com/max/7720/1*0T_0-rfb7R_NZF1K6fMViQ.png)

Ignoring the unwanted details, let’s focus on what the [findJavaTZ_md](https://hg.openjdk.java.net/jdk/jdk/file/f91999057a5a/src/java.base/unix/native/libjava/TimeZone_md.c#l769)() does.

![](https://cdn-images-1.medium.com/max/3400/1*07Xqs4I851c3uaUxVFoavA.png)

Now, we can understand why the quick search solutions mentioned to set values for `user.timezone` and `TZ`! In our case, we didn’t set the TZ environmental variable which means `TZ = NULL`.

It calls [getPlatformTimeZoneID](https://hg.openjdk.java.net/jdk/jdk/file/f91999057a5a/src/java.base/unix/native/libjava/TimeZone_md.c#l221)() if TZ is `NULL`. (We’re about to reach a conclusion in the next step! Hang on..)

![](https://cdn-images-1.medium.com/max/5424/1*FP_4coRTJdsNODPYQbaVSA.png)

As the comment says, it will check for `/etc/timezone` file which contains the system timezone information. I checked in my Ubuntu machine for the file and it's straightforward.

![](https://cdn-images-1.medium.com/max/3776/1*gvC-WyXhr5SezGr9jgGSqg.png)

If the file is **not** present, it checks `/etc/localtime` to obtain the timezone.

![](https://cdn-images-1.medium.com/max/6656/1*OmJgJ5utuwb8vpZEIqwPuQ.png)

Here’s what happens in the above code.

 1. Get the file status of `/etc/localtime` using [lstat](https://linux.die.net/man/2/lstat64)

 2. Check whether it's a symlink and read the symlink to obtain the timezone.

For `lstat` to work as expected, it's required to have the executable(`x`) permission on the file (as stated in the manpage of lstat). In my case, the /etc/localtime file doesn’t have the executable permission set. Hence, it returns NULL taking the default timezone of GMT.

![/etc/localtime without executable permission](https://cdn-images-1.medium.com/max/4304/1*s87IxJH00l5WPJCUrRZEsQ.png)

### Obtaining Timezone via symlink:

If the file has the required permission set, the output will be like the below one. After resolving the symlink, the zone info will be taken using the [getZoneName](https://hg.openjdk.java.net/jdk/jdk/file/f91999057a5a/src/java.base/unix/native/libjava/TimeZone_md.c#l81)method.

![/etc/localtime with executable permission](https://cdn-images-1.medium.com/max/7272/1*ZTn8aUKKFeT-b00YiMpF0w.png)

![getZoneName — method](https://cdn-images-1.medium.com/max/5440/1*Sv0ywk7Jc6GCyNMub_yifw.png)

If the `/etc/localtimefile` is not a symlink, the `getPlatformTimeZoneID` method will try to open and read the file like /etc/timezone. As the last fallback, it will recursively iterate the `/usr/share/zoneinfo` directory to find the timezone. When none of them works out, the timezone is set to GMT- the default!

### **Actual Fix:**

For my case, I just had to create a symlink of `/etc/localtime` from `/usr/share/zoneinfo/America/Los_Angeles`.

![Symlink creation for /etc/localtime](https://cdn-images-1.medium.com/max/7080/1*tYCITEpgRVvLtjb6lKI28g.png)

After setting the symlink, running the `DateExampleprogram` yields the expected result.

![](https://cdn-images-1.medium.com/max/3560/1*DhCWfN7Lz7279-lrxeNVaQ.png)

Thank you for reading!