Time is relative

Introduction

Albert Einstein postulated that time is relative and he has been proven right, but what is time actually? Time is a unit which we use to split our daily live into pieces. Time has multiple different units which translate more or less to astronomic events.

We made it quite easy for ourselves. We named the rotation of the earth around itself day which is basically defined by sunrise and sunset and we named the rotation of the earth around the sun year. These units are the basic elements of what we call time, but that was not enough.

We needed a finer granularity, so we made up months, weeks, hours, minutes,  seconds and so forth. Most of todays month and week concept was defined by the romans.

For everyone who is really interested in how todays date and time units got established and restructured over time I really recommend to read this how stuff works article.

Time is complicated

Leaps

The main problem is that the units we chose do not match the actual astronomic events exactly, which brings in the problem of leap years and even leap seconds. Leaps allow us to re-establish accuracy by adding a day every four years and from time to time a second.

Time zones

Despite those basic problems in accuracy we made it a little more complicated by introducing time zones, because a day should still be something that starts with sunrise and ends with sunset. So depending on how far away you are from Greenwich (the meridian) you might add or remove a few hours to get your local time.

Daylight saving

That is something the modern society invented for the sake of having more daylight. That allows us to be more productive, have a longer day and it even lets us safe electricity.

Calendar

Historically not all parts of the world evolved in the same way, so we actually now have despite the ISO standardized calendar (ISO8601) a lot of other systems to calculate and define time. For example: Buddhist, Ethiopic and Islamic.

Format and localization

Regional differences do not only come in different calendars, but also in different formats of displaying time, even in the same calendar. For example Germans usually use something like 31.12.2012 while the Americans prefer something like 12/31/2012.

What about java.util.Date?

This class was the first attempt in Java to create a class which can store time information. As it is with all the first drafts, they usually are not perfect and need to be redesigned. Actually the Date class was so fundamentally broken that it only lasted in the Java version 1.0. Most of the public API of this class has been deprecated with version 1.1, this means four of six constructors and 18 of 27 methods. Anyhow this class is still widely used but mostly in combination with the next draft which they called Calendar, but let’s take a step back and have a look at the Date class.

Weird offsets

The first thing you will have a problem with, when working directly with Date is that it has some really weird offsets. Let’s have a look at the constructor; if you would like to initiate a Date object for the last day of the year 2012 (31.12.2012) you would need to call the constructor like this:

new Date(112, 11, 31);

Isn’t that great? So the year is something completely absurd and the month is minus one, because it starts at 0 while the days start at 1. Actually this constructor was not so bad until the year 2000, because if you would take the last day of the year 1985 it would look like:

new Date(85, 11, 31);

So now you can see where this weird year - 1900 formula comes from. I guess they did not think this API would last that long. By the way Java 1.0 was released on the 1th October 1992.

Accuracy

Date stores the time internally as a long which represents the milliseconds since the beginning of the UNIX epoch, which was on 01.01.1970 at 00:00:00:000. It also exposes this accuracy in milliseconds as public API, but there is no constructor with which you can specify milliseconds, despite the one which takes a UNIX epoch timestamp. This means that the internal logic of Date will automatically convert every specified Date to 0 milliseconds.

Internationalization

As it is stated in the class documentation Date is not capable of internationalization. The methods Date.toGMTString() and Date.toLocaleString() are both deprecated and refer to DateFormat.format(Date). Sadly the class DateFormat is abstract and declares in its class documentation that all subclasses are inherently not thread safe. Currently there is only one subclass in the JDK which is called SimpleDateFormat.

Time zones

Date actually supports time zones, but in a very hard to work with manner. It has a method which will return the time zone offset it has from UTC. Wait, how does it know that? The answer is simple, it just takes the default timezone of your system, which you can set  by calling TimeZone.setDefault(TimeZone). This seems to be ok in environments where you have only one working thread or only one time zone to take care of, but as soon as you want to go multithreaded with different time zones you get into trouble.

Actually it is recommended to use SimpleDateFormat to display the time localized to a specific time zone, but watch out for thread-safety.

Calendars

Date does not support different kinds of calendar systems, actually it is hardcoded to the gregorian calendar.

General class design

Despite the already mentioned problems there are some more which concern more or less basic API design principles. What would you expect from a class called Date? Would you expect it to contain time? I would not, but sadly it does. So the name is in best case confusing and in the worst case just plain wrong.

Additionally Date objects are mutable which is again an issue in multithreaded environments. Even in single threaded environment you should make defensive copies on every method call which either takes or returns a Date object which is associated with the internal state of an object.

What about java.util.Calendar?

So is Calendar an improvement to Date? Definitely, but it still has some flaws.

Improvements

  • it does not have the weird year offset
  • it supports millisecond precision in constructors
  • it has time zone support
  • it has, as the name already implies, calendar support (well kind of …)

Remaining problems

Sadly the month offset is still there. So January is still equal to 0, but at least there is now a variety of constants to use like Calendar.JANUARY.

Although the Calendar class is supposed to support different calendars it only comes with one which is Gregorian. At least that is what you can see from the Java documentation, but actually there are two more which are hard-coded in the Calendar class. These two are Buddhist and Japanese Imperial. So how can you retrieve an instance of such a calendar when there is no public class to instantiate them?

The answer is: through the static method Calendar.getInstance(Locale). So if you would like to get a Buddhist calendar you must do the following: Calendar.getInstance(new Locale("th", "TH")). The sad thing is that the developers hard-coded it actually to the locale for Thailand, which makes me think they assumed that only Thailand has a Buddhist calendar which is just plain wrong. So this so called calendar API is actually not really providing calendars, nor allows it to create custom calendars easily.

Besides, Calendar is still mutable and does not assert on invalid dates. Time zones are now supported, but you are always forced to have a time zone.

Last but not least what would you actually expect from a class called Calendar? Would you expect it to contain date information? Maybe. Would you expect it to contain time information? Not so much. Would you expect it to contain time zone information? Most certainly not.

This is a class which is not well designed and you can already tell that by reading the name and the class documentation.

What about java.util.TimeZone?

The abstract class TimeZone tries to accomplish to cover the aspects of handling time zones and daylight saving. The interface feels a little unhandy especially when working with Date objects. For example the method TimeZone.getRawOffset() returns signed milliseconds while the method Date.getTimeZoneOffset() returns unsigned minutes.

You can query available time zones by calling TimeZone.getAvailableIDs(). If you try to retrieve a TimeZone that does not match one of those IDs the TimeZone class will automatically fall back to GMT. So for example this will always be true:

TimeZone gmt = TimeZone.getTimeZone("GMT");
TimeZone foo = TimeZone.getTimeZone("foo");
gmt.equals(foo); // will return true

If you find yourself in the position of not finding your desired time zone in the available IDs you can specify a custom time zone through SimpleTimeZone class, which has quite a tricky interface. This means if you do not read the entire class documentation, you will not be able to initialize a correct SimpleTimeZone object.

Despite these strange things the TimeZone class also has a quite unintuitive implementation, if not plain false, when it comes to daylight saving. The following tables show the time zone database definition of daylight saving transitions for CST/CDT and compares it to the implementation in the JDK.

Time zone database definition for daylight saving period start CST/CDT

When local standard time is about to reach Sunday, March 11, 2012 at 2:00:00 AM clocks are turned forward 1 hour to Sunday, March 11, 2012 at 3:00:00 AM to local daylight time.

Time DST UTC offset Time Zone
0:00:00 AM No UTC-6h CST
1:59:59 AM No UTC-6h CST
2:00:00 AM→ 3:00:00 AM +1h UTC-5h CDT
3:00:01 AM +1h UTC-5h CDT

So that means when the time zone transitions from CST to CDT one hour is skipped. The day on which the daylight saving period starts has no 2 AM, because the clock automatically jumps from 1:59:59 AM to 3:00:00 AM. In return this means that it should not be possible to initialize a Date or Calendar instance for this concrete moment in time for this specific time zone.

Let’s have a look how it is implemented in the JDK.

Behavior implemented by JDK for daylight saving period start CST/CDT

Time DST UTC offset Time Zone
0:00:00 AM No UTC-6h CST
1:59:59 AM No UTC-6h CST
2:00:00 AM → 3:00:00 AM +1h UTC-5h CDT
3:00:01 AM +1h UTC-5h CDT

The table tries to show that if you initialize a Date object with 2 AM for the day on which the daylight saving period starts it will be converted automatically to 3 AM without further notice. I would rather prefer to get an exception when trying to initialize and invalid moment in time, but at least that behavior is consistent with the rest of the public interface of Date and Calendar. One could say that the JDK implementations try to patronize the user by picking some default or next best value, if the given one is not valid.

Let’s have a look now at how the transition from daylight saving to non daylight saving is specified and implemented.

Time zone database definition for daylight saving period end CST/CDT

When local standard time is about to reach Sunday, November 4, 2012 at 2:00:00 AM clocks are turned backward 1 hour to Sunday, November 4, 2012 at 1:00:00 AM to local daylight time.

Time DST UTC offset Time Zone
0:00:00 AM +1h UTC-5h CDT
1:00:00 AM +1h UTC-5h CDT
1:59:59 AM +1h UTC-5h CDT
2:00:00 AM→ 1:00:00 AM No UTC-6h CST
1:00:01 AM No UTC-6h CST
2:00:00 AM No UTC-6h CST

The specification states that the last moment in daylight saving is 1:59:59 AM (milliseconds omitted) and that the clock switches from that time directly back to 1 AM. This means that there are actually two 1 AM instants in time for that specific date, one in CDT and one in CST. So how does the JDK handle this case?

Behavior implemented by JDK for daylight saving period end CST/CDT

Time DST UTC offset Time Zone
0:00:00 AM +1h UTC-5h CDT
0:59:59 AM +1h UTC-5h CDT
1:00:00 AM No UTC-6h CST
2:00:00 AM No UTC-6h CST

So instead of the specified switch from 2 AM to 1 AM and having the 1 AM twice the implementation switches from 0:59:59 (milliseconds omitted) CDT directly to 1:00:00 CST which effectively skips one hour. This means it is not possible to define any instant in CDT on that date for 1 AM, if you do it anyways it will automatically be converted to 1 AM in CST. This can be quite a surprise, because the actual switch will always be one hour earlier as specified by the time zone database.

Joda Time

So can we do better than Date and Calendar? Yes, we can! There is that nice little API called Joda Time, which by the way is not pronounced Yoda but Joda.

Basic concepts

It separates the concerns of date, time, time zones, calendars and mutability/immutability. In the Joda date-time-continuum the basic concept of storing time is called an instant. An instant represents a concrete and valid moment in time with millisecond precision, so if you try to initialize and invalid instant you get an exception. Internally such an instant is stored as the number of milliseconds since the UNIX epoch, which is similar to the JDK approach.

In fact, the Joda Time is fully compatible and interoperable with the JDK classes like Calendar, Date and TimeZone. This means you can initialize Joda Time classes from JDK classes and vice versa.

In order to represent things which are not a concrete instant the Joda Time API uses so called partials. Partials are things that can become an instant by adding the missing parts which are required by an instant. So for example the partial LocalTime, which is a time object without date and time zone information, can be extended with those to become an instant.

So basically you convert every instant to a partial through convenience methods like toLocalTime() and at the same time you can convert every partial to an instant by adding the missing parts.

Convenience

Joda Time is very convenient to use, not only because of those nice conversion APIs, but also because it finally provides a month system which is based on 1 and not on 0. This combined with the parameter validation makes a much better and intuitive API as everything else which comes from the JDK.

Additionally it provides a lot of utility methods and classes which allow you to perform very easy transformation and different representation of data.

It also comes with thread safety through the provided immutable classes and through a thread safe formatter.

Joda Time is widely accepted as the new standard for handling time. This means though not all frameworks and libraries support Joda Time natively, extensions are available or can be written easily.

Calendars

In the Joda API calendars are implemented as chronologies. The API comes with the following calendars: Buddhist, Coptic, Ethiopic, Gregorian/Julian, Gregorian, Islamic, ISO and Julian. Additionally it provides some wrapper chronologies to add additional features like lenient, strict, zoned or limited chronologies.

Time zones

Time zone implementation is more convenient than the JDK implementation. Actually Joda Time comes with its own time zone class called DateTimeZone.

Behavior implemented by Joda API for daylight saving period start CST/CDT

Time DST UTC offset Time Zone
0:00:00 AM No UTC-6h CST
1:59:59 AM No UTC-6h CST
2:00:00 AM invalid invalid invalid
3:00:00 AM +1h UTC-5h CDT

So the Joda API will raise an exception if you try to initialize 2 AM on the day of daylight saving start in CST. Programmatically this problem can be solved by initializing the instant as LocalDateTime and check if it is in a daylight saving gap for a specified time zone. If the instant is located in such a gap one can convert that instant to be either the next or previous hour. This gives great flexibility and makes the behavior explicit and customizable to the application’s needs.

Behavior implemented by Joda API for daylight saving period end CST/CDT

Time DST UTC offset Time Zone
0:00:00 AM +1h UTC-5h CDT
0:59:59 AM +1h UTC-5h CDT
1:00:00 AM +1h UTC-5h CDT
2:00:00 AM No UTC-6h CST

So the Joda Time API tries a different approach compared to the JDK. It takes the 1 AM in CDT instead of CST so that the period ends with 2 AM as specified by the time zone database, though there is no 1 AM is CST.

Tips

There a few things which are quite useful to know when working with Joda Time.

  1. If you want to add milliseconds to an object you would naturally use plusMilliseconds(int), but integer is very often the wrong type when working with milliseconds so you should use plus(long) instead.
  2. The default chronology in Joda Time is ISOChronology. It is used when no chronology is explicitly specified, for example when initializing a DateTime object. This can be confusing when initializing Joda classes from GregorianCalendar instances because they will have the GJChronology set. So for example those two objects are not equal:
DateTime dateTimeWithGJ = new DateTime(new GregorianCalendar(2012, 11, 31, 0, 0, 0, 0));
DateTime dateTimeWithISO = new DateTime(2012, 12, 31, 0, 0, 0, 0);
dateTimeWithGJ.equals(dateTimeWithISO); // will return false

What about JSR-310?

JSR-310 is the request to finally solve the Date/Calendar debacle in Java by adopting to the ideas and principles provided by Joda Time API. Actually one of the three lead developers is Stephen Colebourne, who is the founder and lead of Joda Time and other Joda APIs. Current ETA of that JSR is Java SE 8.

Overview

JDK Joda Time
Convenience
Precision
Immutability
Calendar
Internationalization
Parameter validation
Time Zone
Daylight saving
Leap years
Leap seconds