Blog

DateTime and TimeZone pains

31 Mar, 2008

For as long as Java has been around, java.util.Date and java.util.Calendar have been nuisances. This will hopefully very soon be a thing of the past with the addition of JSR-310, the Date and Time API, to the Java API. At the basis of JSR-310 lies the Joda time library, which has been around for quite some time as a replacement for the standard Date and Calendar classes. However that this API is not without its own peculiarities need not come as a surprise, given the complexity of the human interpretation of time all over the world.

In our current project we have a value in the database that contains a timestamp in the UTC or "Zulu" timezone. This timestamp is fetched from the database using a java.sql.Timestamp instance. Our application however deals with org.joda.time.DateTime instances in the local time zone, which for the Netherlands are either CET (UTC+0100) or CEST (UTC+0200) depending on Daylight Savings Time.
So in a nutshell our problem encompasses converting a UTC java.sql.Timestamp to an org.joda.time.DateTime in the correct time zone, which represents the correct time. Let’s start off with writing a small skeleton JUnit test:

public class DateTimePainsTest extends TestCase {
    private static final String WRONG_TIME = "2008-03-26T20:13:39.059+01:00";
    private static final String CORRECT_TIME = "2008-03-26T21:13:39.059+01:00";
    private long millis;
    private Timestamp timestamp;
    protected void setUp() throws Exception {
        millis = new Date(1206558819059L).getTime(); // 2008-03-26T20:13:39.059+00:00
        timestamp = new Timestamp(millis);
    }
    public void testMillisAndTimestamp() {
        assertEquals(millis, timestamp.getTime());
        assertEquals("2008-03-26 20:13:39.059", timestamp.toString());
    }
}

Now we want to convert this timestamp to a DateTime object representing the following string: 2008-03-26T21:13:39.059+01:00. We first try the most simple option we can think of:

public void testDateTimeConversion1() {
        DateTime dateTime = new DateTime(timestamp.getTime(),
                DateTimeZone.forOffsetHours(1));
        assertEquals(CORRECT_TIME, dateTime.toString());
}

This test fails. In order to make the test succeed, we have to expect WRONG_TIME in there. Though according to the JavaDoc of DateTime this should have been a step in the right direction:
DateTime(long instant, DateTimeZone zone)
Constructs an instance set to the milliseconds from 1970-01-01T00:00:00Z using ISOChronology in the specified time zone.

Maybe we should first force the time zone to UTC before converting the DateTime to a European time zone. Let’s give that a try:

public void testDateTimeConversion2() {
        DateTime dateTime = new DateTime(timestamp.getTime(), DateTimeZone.UTC)
                .withZone(DateTimeZone.forOffsetHours(1));
        assertEquals(CORRECT_TIME, dateTime.toString());
}

This test also fails, substituting WRONG_TIME for CORRECT_TIME yields a green bar, but an undesired result, so we should take a different approach. LocalDateTime is a Joda time class which does not take into account any time zone information. May we can convert this to a DateTime in the correct time zone. Let’s write a new test case:

public void testDateTimeConversion3() {
        DateTime dateTime = new LocalDateTime(timestamp)
                .toDateTime(DateTimeZone.forOffsetHours(1));
        assertEquals(CORRECT_TIME, dateTime.toString());
}

Again, this test fails. We need to expect the WRONG_TIME in there to get a green bar. What seems to do the trick is converting the LocalDateTime to a DateTime in the UTC time zone, and then assigning the correct time zone to that. Lo and behold, the following test case works:

public void testDateTimeConversion4() {
        DateTime try3 = new LocalDateTime(timestamp).toDateTime(DateTimeZone.UTC)
                .withZone(DateTimeZone.forOffsetHours(1));
        assertEquals(CORRECT_TIME, try3.toString());
}

Though this solution works, I think it is not the most optimal solution. Does anyone know of a better solution? If so, please add a comment.

Explore related posts