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