A practical example for Handling Time Zone conversion in a Java REST API context

Anushka Darshana
9 min readSep 6, 2021

This article mainly focused on checking the ability of handling the time zones with different java date/time data structures. Practical code exaple given and I am tring to elemorate the conversion flow comprehensivly.

The Example consist of Angular/Node Js front end to Spring boot back ends with Mysql/PostgresSQL DB also support to work with three different persistence mechanism SpringData, Spring JDBC Template and Plan JDBC and also given the option to run the dbs with Docker containers.

This is the second part of the article I wrote for Guide to Time Zone Handling for Java APIs. If you want to get some theoretical understanding you may follow the above link.

In this article, I am going to demonstrate the 2n architecture that I described in the first article which is that client, server, and DB running in 3 different time zones

# How the client looks

Angular client Interface

# Checkout and setup

You may start checking out this repository from GitHub. https://github.com/anushkadar/TimeZoneClientServer.git

Basically, the repository t contains two projects, one for Angular Front end client and the other for Spring boot Server application.

There I have setup 3 persistence frameworks including
Spring Data JPA, Spring JDBC Template, Plan JDBC
Also configurable to run against Mysql Server and Postgres Server.

There are several ways to run a java app in a specific time zone.
The simplest way is to add the VM argument via the IDE you are running it.
Ex: -Duser.timezone=Europe/London

## Setting up the DB
I have added scripts to create databases (MySQL and PostgreSQL) in Sever Application project /resources directory.

Database Scripts

If you are familiar with Docker, the simplest way is to execute given Docker compose files separately.
command: docker-compose up
This will download the database image from the docker hub and will start the database container by creating the database.
Then You need to connect to the database and execute create DB script.

Otherwise, you may create the DB and table with existing local DB or can install DB separately

## Setting up the Server
As I mentioned earlier server is a spring boot Maven project so once your server is up and running you may start the application using your favorite IDE.

Connecting to which DB is configurable in application.properties.
Change the value in spring.datasource.url property according to the configured DB

## Setting up the Client
You need Node server installed if you do not yet before proceeding to set up the client. Then it's just going to the client directory of your project via command prompt and just executes the command
npm install
You may suggest fixing vulnerabilities using
npm audit-fix or
npm audit-fix — force
no worries just follow the suggested instructions

Finally, you can start the client with
npm start

Now all 3 departments are up and ruining and it's time to play with it.

# Now it’s time to explain the real business.

Here we are going to compare the Date / Times from Client to Server to DB for each Datatype for each different persistence framework. Additionally App is supporting to configure run with either MySQL or PostgreSQL DB

Datatypes
· String
· Date
· LocalTime
· LocalDate
· LocalDateTime
· OffsetDateTime
· ZonedDateTime

Persistence Frameworks
· Spring Data (JPA)
· Spring JDBC Template
· Plan JDBC

Databases
· MySQL
· PostgreSQL

# How Java Types mapped to DB Types

@Entity
@Table(name = "date_tb")
public class Data implements Serializable {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "date_str")
private String dateStr;
@Column(name = "date")
private Date date;
@Column(name = "local_time")
private LocalTime localTime;
@Column(name = "local_Date")
private LocalDate localDate;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
@Column(name = "local_datetime_dt")
private LocalDateTime localDateTimeDt;
@Column(name = "local_datetime_ts")
private LocalDateTime localDateTimeTs;
@Column(name = "offset_datetime")
private OffsetDateTime offsetDateTime;
@Column(name = "zoned_datetime")
private ZonedDateTime zonedDateTime;
#Mapping to this MySQl table
CREATE TABLE IF NOT EXISTS `sample_db`.`date_tb` (
`id`INT NOT NULL AUTO_INCREMENT,
`date_str` VARCHAR(500) NULL,
`date` DATETIME NULL,
`local_time` TIME NULL,
`local_date` DATE NULL,
`local_datetime_dt` DATETIME NULL,
`local_datetime_ts` TIMESTAMP NULL,
`offset_datetime` TIMESTAMP NULL,
`zoned_datetime` TIMESTAMP NULL,
PRIMARY KEY (`id`));

#Mapping to this PostgreSQL
CREATE TABLE IF NOT EXISTS date_tb (
"id" SERIAL,
"date_str" VARCHAR(500) NULL,
"date" timestamp NULL,
"local_time" time NULL,
"local_date" DATE NULL,
"local_datetime_dt" timestamp NULL,
"local_datetime_ts" timestamptz NULL,
"offset_datetime" timestamptz NULL,
"zoned_datetime" timestamptz NULL,

PRIMARY KEY ("id"));

## High-Level Architecture of the Application

High-level Architecture

This is a typical rest client-server application architecture. But there are few specific behaviors Date/Time data where the transformation happening for each data type.

As illustrated in the above flow chart there are 3 stages that data transformation happening and similarly each time there is a time transformation happening. 1 from the client-side and two from the server-side.

Data Transformation flow

Actually, Here we have a complete round trip from request sending client to server to then DB then again from DB to sever to respond back to the client.

Let’s first begin with the departure from the client-side means sending the request.

#First

The client (Running in UTC + 3 Time Zone ) sent its local dates/ times to the server with the rest of the API call, once received Jackson framework, deserialize the date into the java Data type.
To specify Jackson to which Time Zone our server is ruing can be done with bellow spring configuration in application.properties.
you may enable/disable and configure the Timezone which that Jackson should deserialize Date times

spring.jackson.deserialization.adjust-dates-to-context-time-zone=true
spring.jackson.time-zone=Asia/Karachi

#Second

The Sever (Running in UTC + 5 Time Zone ) then after the following the business flow date/times objects pass to the Persistence framework. Most modern persistence frameworks like Hibernate/Spring Data are configurable to make them aware of the time zone of the Database that they going to communicate with.

 #for plain hibernate
hibernate.jdbc.time_zone=UTC
#for Spring boot jpa
spring.jpa.properties.hibernate.jdbc.time_zone=UTC

If the framework is capable of doing it then it will convert the date to a Specified Time zone
And our DB is configured to in UTC

#Third

We retrieve the data from DB and which is done by the persistence framework and JDBC driver together. There if the framework will do the necessary timezone conversion before the objects passing to the Controller. Then again before the response is sent back to the server the Jackson framework serialize those Data into JSON.

At this point, there is something specific.
That is Jackson will not convert it to the client timezone. remember it's not.
The reason is simple that Jackson is unaware of that.
But if your Java datatype carrying a timezone offset it Jackson will serialize the date with it. What it means is both Java OffsetDateTime and ZonedDateTime converting with offset. Again Java Date also doing this magic but I am not able to figure it out. Most probably Jackson-Datatype-JSR310 was handing this specially

Then what about the other DataTypes like LocalDateTime
Those will not convert Jackson by default except specified to do so annotating the particular filed

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
@Column(name = "local_datetime_dt")
private LocalDateTime localDateTimeDt;

Here this annotation lets Jackson know the time should be converted to UTC prior to sending. ‘Z’ is representing the UTC and it is an International standard. Otherwise, you may write a custom serializer.

# Forth

In the fourth and last stage, the client receives the JSON response and localizes dates to its timezone. This time will be localized by angular framework if it is associated with the offset. otherwise, it will display the time as it is.

Our client is a typical Angular app running on node server and which is configured to run in Istanbul time zone (UTC + 3) and Spring boot Server Configured to run Karachi Time Zone (UTC + 5) and DB is running in UTC.

The reason to choose the above time zones is, it will be easy for us to calculate and see the difference rather than using time zones with UTC +- minute figures.

# It's time for Analyze. Let’s check the result for each scenario

What should be the correct result if all worked well?
The client has called at the time 16:00:00 GMT+0300 that is Istanbul time
Since the Server running in Karachi time server should receive the time to
18:00:00 GMT+0500 Then
Since the DB is rung In UTC it should be
13:00:00

# Let’s Begin with the client
we can avoid String date and Local Date for now since it’s not carrying the time figures. Also, the difference between LocalDateTIme Dt and Ts is that In the DB LocalDateTIme Dt is mapped to DateTime Datatype while LocalDateTIme Ts is Mapped to TimeStamp DataType.

As per the client response, we can see there is only one data type that worked wrong that is LocalDateTime Ts

# Let’s check what’s going on in Server
Since the Server ruing on UTC + 5 Karachi time correct DateTime should be 18:00:00 GMT+0500.
But A per the above logs we can see the only Date, OffsetDateTime, and ZonedDataTime are correct. Check the left column of the bellow table.

Server logs when received From Client — — — — — — — Server logs prior to sending Back to Client

You may also check the above logs while you running the application but there is one thing you need to remember. That is these logs belong to the Application Logic Layer.
This means the times showing in the left column are actually after Jackson deserialization happen but the logs in right side are before Jason serialization happen (getting from persistence layer of the application)

# Let’s check the DB then

Since the DB is running in UTC DateTime should be in UTC that means the time should be 13:00:00 there but Date, OffsetDateTime, and ZondedDatetime have the correct time.

Then the next problem is even though both Server and Database figures are incorrect for other classes LocalTIme/LocalDateTime, how come still the client sees those correct.

Let’s try to resolve that puzzle now.

LocalTime received without any transformation by Jason to the server, but then storing to the DB hibernate apply to remove the offset time from it to match with UTC because the server runs on UTC + 5. So on the server, it was 11.00.00
Then once returning from the server it converts to 16.00.00 by hibernate. Then there is no conversion done by Jackson since there is no offset associated with the LocalTime.
The client received the time as it is and it will display as it is.

Then what happens to the localDateTimeDt and localDateTimeTs both received the same figures from the server and both have the same values in the DB. Then again prior to sending back to client logs shows the same value. But why client display is different.

The answer was given before that we only annotated the localDateTimeDt field and then it was converted to UTC while responding to the client by Jackson.

What is the result if try with other Persistence API
I let you try that out. The result for using JDBC Template and plain JDBC will be the same for both client and server logs but in the DB it won’t store the correct date time to match with UTC.
Also if remove the below configuration from the application property

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

Spring Data will also work in the same way as other Frameworks.

Finally, if you are running the app against Postgres DB, you will encounter an issue like below for Spring JDBC and JDBC options. The reason is that the latest Postgres driver won't support java. util.Date further. But There are workouts.I let you try to fix it by yourself.

https://jdbc.postgresql.org/documentation/head/8-date-time.html

Conclusion
If your application supposes to run in different timezones, or your application communicating with 3rd party applications running in different timezones, or if your database is exposing to another application that could run in a different timezone it's ideal to use OffsetDatetime, ZonedDateTime.

Cant, you use Java LocalDateTime, localDate, Local Time?
No worries still you can but in that case, you need to handle the conversion manually in the back while reading writing to DB also you need Jackson to instruct to always serialize date times to include offset.

--

--