DateTime and TimeZone pains

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.

Comments (2)

  1. James Mc Millan - Reply

    April 2, 2008 at 6:33 pm

    Hello

    You have an unfortunate fundamental problem: 1206558819059L is not 20:13 UTC but is actually 19:13 UTC (or 20:13 UTC+0100). The 20:13 you are seeing on timestamp.toString() is the time in JDBC escape format, which is in your local time (Use toGMTString() to see what I mean, even though it is deprecated).

    If you agree with me, then to convert to the time in the local time zone, you use:
    String dateTimeString = new DateTime(timestamp.getTime(),
    DateTimeZone.forOffsetHours(1)).toString();

    as you first suggested.

    What you were doing in your test case that works is interpreting the timestamp as being the milliseond value in your current TimeZone, and then converting that to an actual DateTime in UTC. What would also have given the same result is:
    new DateTime(timestamp.getTime()).withZoneRetainFields(DateTimeZone.UTC).withZone(DateTimeZone.forOffsetHours(1));

    Lastly, if you already have a dateTime and want to print it in a specific timezone you can do:
    DateTime dateTime = new DateTime(timestamp.getTime());
    String dateTimeString = ISODateTimeFormat.dateTime().withZone(DateTimeZone.forOffsetHours(1)).print(dateTime);

    Hope that all makes sense 😛

    James

  2. Esteban - Reply

    March 20, 2014 at 7:49 pm

    It's an remarkable post in favor of all the online people; they will get advantage from it I am sure.

Add a Comment