DEV Community

Yogaraj.S
Yogaraj.S

Posted on

Debugging timezone issue in Java [Linux]

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

Output

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. (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 in java.

The timezone on Line:16 is set by calling the date.getZone(). The variable date is initialized by calling the normalize() method.

The timezone in normalize is set by calling theTimeZone.getDefaultRef() in TimeZone.java which then calls setDefaultZone()to get the system timezone.

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

Ignoring the unwanted details, let’s focus on what the findJavaTZ_md() does.

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() if TZ is NULL. (We’re about to reach a conclusion in the next step! Hang on..)

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.

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

Here’s what happens in the above code.

  1. Get the file status of /etc/localtime using lstat

  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

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 getZoneNamemethod.

/etc/localtime with executable permission

getZoneName — method

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

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

Thank you for reading!

Top comments (1)

Collapse
 
jaimecruz profile image
Jaime Cruz

Thank you for the detailed explanation! In my case, I set the timezone system-wide using
sudo timedatectl set-timezone America/Lima , which creates a symbolic link. I confirmed this with ls -l /etc/localtime showing lrwxrwxrwx 1 root root 34 Sep 14 08:06 /etc/localtime -> ../usr/share/zoneinfo/America/Lima
I ran a similar test with Node.js, and the output reflected the correct timezone:

$ date
Sat Sep 14 12:47:25 -05 2024

$ node ShowTime.js
// Current time: 12:47:25 PM
// Timezone: America/Lima

$ java ShowTime
// 10:47:25.220526276
// JVM Timezone: America/Los_Angeles
Enter fullscreen mode Exit fullscreen mode

However, when I ran the same test with the JVM, it still used the default America/Los_Angeles timezone. While your quick fix works perfectly, I’d like to apply a permanent solution to ensure the JVM consistently picks up the correct system timezone.

Thanks again for your thorough explanation and guidance!
Best wishes!