Stop using java.util.Date

I used to leverage java.util.Date class to store and represent date time data but I suffered a lot from many problems due to java.util.Date’s limitations itself.

In fact I am not alone. Many people suffer too. Here is an article about java.util.Date that I think it covers enough : https://codeblog.jonskeet.uk/2017/04/23/all-about-java-util-date/. Below is the summary, it’s limitations are :

  • A Date instance represents an instant in time, not a date. It is basically a long number that we call timestamp. This brings more headaches while reading data, debugging and conversion this timestamp to actual date.
  • It doesn’t contain time zone information. The toString() of java.util.Date is sticked with executing system timezone. You will get different dates in different machines in different timezones. This may confuse your customer when you want to show your java.util.Date as a string. Developers also have to pay more attention about system current timezone while writing code and most of time, they forgot.
  • It doesn’t have a format. This brings many ambiguities when conversing data from string and vice versa.
  • It doesn’t have a calendar system. It means it does not guarantee that a java.util.Date will return correctly information about day of week , etc which is useful when you want to check about weekend or holidays.

As the conclusion in the above link, and as my point of view too, we should avoid to use java.util.Date as much as possible, especially when your application gonna run over multiple timezones. Oracle perceives this painful and they did release new libraries for date time : DateLocal & DateTimeLocal in Java8. They are a great libraries to use but while databases still do not work with those new ones – they still uses timestamp (old java.util.Date) to represent a Date column, we have to cure ourself.

My problems with java.util.Date

I am a full-stack developer. In my company, we build server by pure Servlet, deploy using Google Cloud Platform and use GWT for front-end. GWT is old now but this system is built from the day GWT gets it trend, about 2012. The big advantage that GWT brings to us is it allows Java developer to create UI by Java. The fact is that we built a complete full-stack framework based on Java.

Our database is Datastore which is provided by Google and the official tutorial tells us to store date time data by setting a java.util.Date instance to the entity. Then after a year, we got reports from users that their inputed date times are displayed wrong, usually 1 hour backward or forward. I started investigations and this is what I found out :

When creating DateTimeInput class, we have 2 options to implement :

  • Get value & set value by a String
  • Get value & set value by a timestamp

If we know about how ambiguous the timestamp is, we will choose String . But unfortunately, we decided to use timestamp because a naive thinking that it is how the computer sees time. And it is the root cause of problems.

Our product currently runs in US and Viet Nam. US is primary base because our customers are American. Viet Nam bases are for customer services only and date time representations must be in US timezone. US itself streches across multiple timezones. We chose US/Pacific as the default timezone because our server is located over there.

Remind that each machine has its own timezone and the data conversion between machines are independent. Our need is showing date as the same no matter what timezone clients are in and showing at US/Pacific date time. If a user in US input a date as Dec 10 2020 08:00AM, a user in Viet Nam must see the time Dec 10 2020 08:00 too and vice versa, when a user in Viet Nam input Dec 10 2020 08:00, user in US must see Dec 10 2020 08:00 too.

Let say a user in VN inputs a date at Dec 10 2020, 00:00AM, the client application (we implement Single Page Application) converts this date to timestamp 1607533200000 then submits to the server. If the server stores this value, a user in US will see that date as Dec 09 2020 09:00:00.

→ To cure this, we decided to detect the client time zone and submit to server. So that, server application can adjust the timestamp to target timezone.

In fact, before storing to Datastore, we adjusted the timestamp to GTM+0 timezone. This is achieved by a few implicit logic that other colleages may unaware.

Example a user want to store Dec 10 2020, 00:00AM when he is at VN (GMT+7), it means Dec 10 2020, 00:00AM GMT+7, server tries to convert to date Dec 10 2020, 00:00AM GMT+0 which is T = 1607533200000 + 7 HOURS. Whenever a user want to get that date back, assume he is in timezone GMT+6, we adjust from stored timestamp : T’ = T – 6 HOURS.

This way, we can display the same date regardless timezone, in theory. This implicit logic is to hide the complexity for front-end works but, honestly with my feel, this is a crime because it does hurt developer brains and brings surprises.

Above method will work well unless there is another monster call DST (Daylight Saving Time). Actually contributing to this crime is the way we are getting client time zone. Practically, sending timezone along with each date field is seem noisy to developer at the first glance so we decided to get it using a HTTP Header named tz. This tz is calculated at the time the client application builds the request, this case it is using Date::getTimezoneOffset() of Javascript . So a bug happens when a user is outside of DST period select a date in DST period and vice versa. See below for an example :

  • Let say the DST period is from Mar 10 to Nov 10. Notice that this period is different between years. A HTTP request created at Feb 01 in US/Pacific will have a header tz = -8. This request need to carry along another date : May 10 00:00 . Because the Date instance of Javascript also automatically detects timezone itself, so, it knows that this May 10 is in DST period, so what it returns is May 10 00:00 GMT-7 timestamp instead of May 10 00:00 GMT-8. As the result, we submit the timestamp of May 10 00:00 GMT-8 which is equal to May 09 23:00 GMT-7. Then when it is toString() in client side, people see it is May 09 instead of May 10

Solution

To get rid all headache in storing Date field, processing/converting them, I introduce a new way to store Date data. In short, we stored date as a String with format :

yyyy-MM-dd’T’HH:mm–[timezone_id].

Example : 2020-12-10T00:00–US/Pacific. I call this DateString

Benefits : This way, we eliminate all parsing/converting code which is bugs’s favorite places, ensure the readability over data and keep data consistent. It is not hard to write functions to replace methods of java.util.Date by processing above date string.

To support this kind of Date, we created an input named DateInputWithTimezone which get & set value by String. Server side application there is nothing to do except storing submitted string. The timezone tail of this DateString allows to make timezone selector feature that allows users specify timezone the date they are inputing is at.

Query support : This date string format also support queries on date. If the date A is after date B, the DateString A also is “greater than” DateString B. Example to filter documents created from Mar 01, 2020 to May 01 2020, we can compare strings like :

bundle.find(...)
.where(Document.created, GREATER_THAN_OR_EQUAL, "2020-03-01T00:00--US/Pacific")
.where(Document.created, LESS_THAN_OR_EQUAL, "2020-05-01T00:00--US/Pacific")

Trade off : A potential problem is when a date field contains data in multiple timezones, example some document is at US/Pacific, some is at VN/Ho_chi_minh. There is 2 approaches to deal with these :

  • Make an converter to make sure every date strings are stored as 1 timezone
  • Use an additional timestamp field which is deduced directly from each DateString, store it beside DateString field and do query on that timestamp field.

An example of a input with timezone selector

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: