The best way to handle time zones in a Java web application

The best way to handle time zones in a Java web application
Techiio-author
Written by Sagar RabidasFebruary 23, 2022
17 min read
Hibernate
1 VIEWS 0 LIKES 0 DISLIKES SHARE
0 LIKES 0 DISLIKES 1 VIEWS SHARE
Techiio-author
Sagar Rabidas

Software Developer

This blog e will discuss the best way to handle time zones in a Java web application.

What is difficult about time and time zones:-

Handling time is very difficult. If you don’t believe me, check out this awesome list of time-related fallacies.

Now, to make matters worse, a web application request spans at least three distinct layers:

  • the browser
  • the webserver
  • the database

Each of these layers can observe a different time zone, therefore making things even more difficult for us, software developers.

Database time zone:-

Databases can use the local time zone of the underlying operating system or a custom define time zone.

For consistency, it’s best if all database timestamps are stored in UTC because, this way, it’s going to be much easier to calculate timestamp intervals since all user timestamps are relative to the same time zone.

More, if a user moves to a different time zone, there’s no need to change the already-stored user=specific date/time information since the conversion can be done in the web layer anyway.

So, if you are using MySQL, you can set the database time zone to UTC in the /etc/MySQL/my.conf configuration file, like this:

default_time_zone='+00:00'

Or, if you are using PostgreSQL, you can set the database time zone to UTC in the /var/lib/PostgreSQL/data/PostgreSQL.conf configuration file, as follows:

timezone = 'UTC'

If you happen to be using Amazon Aurora, then you don’t need to set the UTC zone because Aurora uses UTC by default.

As I explained in this article, RevoGain uses Amazon Aurora MySQL, so no change was needed to use UTC on the database side.

Server time zone:-

With the add by default, a java application uses the gadget time area. Again, if you are the use of AWS, then the default time area is UTC. You can see that when inquiring for the application logs as the log messages timestamp are relative to UTC.

If your JVM is not using the UTC timezone, but the database is, then you have two alternatives.

Setting the default server time zone:-

You can set the operating system timezone to UTC, or if you cannot change that, you can set the default JVM timezone.

The JVM time zone can be set using the user. timezone property:

java -Duser.timezone="UTC" com.revocation.RevoGainApplication

Convert to a given timezone using JDBC:-

If you cannot change the OS or the JVM timezone, you can still convert a Java Date/Time or Timestamp to a specific time zone using the following two JDBC methods:

  • PreparedStatement#setTimestamp(int parameter Index, Timestamp x, Calendar cal) – to convert the timestamp that goes to the database
  • ResultSet#getTimestamp(int columnIndex, Calendar cal) – to convert the timestamp coming from the database

If you are using Spring Boot, you can achieve this goal by setting the following configuration property in your application. properties file:

spring.jpa.properties.hibernate.jdbc.time_zone=UTC

Behind the scenes, this setting will instruct Hibernate to use the provided time zone when reading and writing timestamp column values.

The browser time zone:-

In the browser, JavaScript stores Date/Time in the local (host system) time zone.

We can, therefore, use the user time zone to convert all UTC timestamps stored in the database or created in the JVM. To do that, we need to store the user’s time zone during authentication.

On the login page, the authentication form contains a timezone offset hidden field:

<form th:action="@{/login}" method="post" class="form-signin" >
     
    ...
     
    <input type="hidden" id="timeZoneOffset" name="timeZoneOffset" value=""/>
     
    <button class="btn btn-lg" type="submit">Login</button>
</form>

The timezone offset hidden input field value is set to the user time zone offset:

<script>
    jQuery(document).ready(function() {
        jQuery("#timeZoneOffset").val(new Date().getTimezoneOffset());
    });
</script>

Saving the user-specific time zone on the webserver

We can read the timezone offset value using the following web filter:

public class TimeZoneOffsetFilter implements Filter {
 
    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain)
                throws IOException, ServletException {
        TimeZoneOffsetContext.set(request.getParameter("timeZoneOffset"));
         
        chain.doFilter(request, response);
         
        TimeZoneOffsetContext.reset();
    }
}

The TimeZoneOffsetContext is just a placeholder utility that stores the time zone information so that we can read it after Spring Security authenticates the user login request:

public class TimeZoneOffsetContext {
 
    private static final ThreadLocal<String> timeZoneOffsetHolder =
        new ThreadLocal<>();
 
    public static String get() {
        return timeZoneOffsetHolder.get();
    }
 
    public static void set(String timeZoneOffset) {
        timeZoneOffsetHolder.set(timeZoneOffset);
    }
 
    public static void reset() {
        timeZoneOffsetHolder.remove();
    }
}

We can set the user time zone in the Spring Security UserDetails object that’s associated with the currently logged user, like this:

@Service
@Transactional(readOnly = true)
public class UserService implements UserDetailsService {
 
    ...
 
    @Override
    public UserDetails loadUserByUsername(String username)
                throws UsernameNotFoundException {
        User user = userRepository.findByEmail(username);
         
        if (user == null) {
            throw new UsernameNotFoundException("""
                This email or password are invalid.
                Please review them and try again.
                """
            );
        }
         
        return new ApplicationUserDetails(user)
            .setTimeZoneOffset(
                TimeZoneOffsetContext.get()
            );
    }
     
    ...
}

The ApplicationUserDetails stores the time zone information and provides timestamp formatting capabilities:

public class ApplicationUserDetails
        implements UserDetails {
 
    public static final DateTimeFormatter DATE_TIME_FORMATTER =
        DateTimeFormatter.ofPattern(
            "dd/MM/uuuu HH:mm:ss"
        );
 
    private User user;
     
    private ZoneOffset zoneOffset;
 
    public ApplicationUserDetails(User user) {
        this.user = user;
    }
     
    ...
 
    public ZoneOffset getZoneOffset() {
        return zoneOffset;
    }
 
    public ApplicationUserDetails setTimeZoneOffset(String timeZoneOffset) {
        if (timeZoneOffset != null) {
            int offsetMinutes = Integer.valueOf(timeZoneOffset) * -1;
            this.zoneOffset = ZoneOffset.ofTotalSeconds(offsetMinutes * 60);
        }
        return this;
    }
 
    public String getFormattedDateTime(LocalDateTime dateTime) {
        if(zoneOffset != null) {
            OffsetDateTime serverOffsetDateTime = dateTime.atZone(
                ZoneId.systemDefault()
            ).toOffsetDateTime();
             
            OffsetDateTime clientOffsetDateTime = serverOffsetDateTime
                .withOffsetSameInstant(zoneOffset);
                 
            return DATE_TIME_FORMATTER.format(clientOffsetDateTime);
        }
        return dateTime.format(DATE_TIME_FORMATTER);
    }
}

Converting timestamps to the user time zone:-

Now, we can convert timestamps to the user-specific time zone. For instance, when displaying the activity log, we can shift the operation timestamp to the user’s time zone, as follows:

<tr th:each="op, status : ${operationsPage}"
    th:style="${status.odd}? 'font-weight: normal;'">
    <td th:text="${op.id}"></td>
    <td th:text="${userDetails.getFormattedDateTime(op.createdOn)}"></td>
    <td th:text="${op.credits}"></td>
    <td th:text="${op.type.label}"></td>
</tr>

Hibernate
JWA
JPA
Java
1 VIEWS 0 LIKES 0 DISLIKES SHARE
0 LIKES 0 DISLIKES 1 VIEWS SHARE
Was this blog helpful?
techiio-price-plantechiio-price-plantechiio-price-plantechiio-price-plantechiio-price-plan
You must be Logged in to comment
Code Block
Techiio-author
Sagar Rabidas
Software Developer
Techiio-followerTechiio-followerTechiio-followerTechiio-followerTechiio-follower
+8 more
300 Blog Posts
14 Discussion Threads
Trending Technologies
15
Software40
DevOps46
Frontend Development24
Backend Development20
Server Administration17
Linux Administration26
Data Center24
Sentry24
Terraform23
Ansible83
Docker70
Penetration Testing16
Kubernetes21
NGINX20
JenkinsX17
Techiio-logo

Techiio is on the journey to build an ocean of technical knowledge, scouring the emerging stars in process and proffering them to the corporate world.

Follow us on:

Subscribe to get latest updates

You can unsubscribe anytime from getting updates from us
Developed and maintained by Wikiance
Developed and maintained by Wikiance