Sunday, February 26, 2017

Using Joda time to handle date+time+timezone

I have been working on a client-server system where the client and server are in different timezones. The client is on London time, the server is in Los Angeles, a difference of 8 hours. This means that at the end of the business day in LA it already the next day in London. The API that I have to use contains functions with parameters of type Calendar. As we know, Calendar is a date+time+timezone triple, and the timezone defaults. This means that if the timezone is not explicitly specified then it will change its meaning as it goes over the wire.

I have been using the Joda datetime package to help me and after a bit of struggling eventually came up with the example program below which shows the construction of Joda DateTime objects for a specific date+time+timezone which is displayed correctly in both timezones. The program also shows how to construct Calendar objects from them for the correct timezone.

package jodaexample;

import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

 * @author marlowa
 * This example shows a time of 18:30 PST which is 8 hours behind UTC.
 * This means the date+time+timezone is 01:30 the previous day in UTC,
 * or 02:30 in BST.
public class example {

    public static void main(String[] args) {
        System.out.println("Joda timezone example program.");
        DateTimeZone mytimezone = DateTimeZone.forID("America/Los_Angeles");
        DateTime mydatetime = new DateTime(2017, 3, 31, 18, 30, 0, mytimezone);
        String formatString = "yyyy-MM-dd HH:mm:ss z '('Z')'";
        DateTimeFormatter dtf = DateTimeFormat.forPattern(formatString);
        System.out.println("DateTime (local, i.e. behind UTC) = "+dtf.print(mydatetime));
        Calendar datetimeInAmerica = mydatetime.toGregorianCalendar();
        SimpleDateFormat sdfInAmerica = new SimpleDateFormat(formatString);
        sdfInAmerica.setCalendar(datetimeInAmerica); // to set the timezone.
        System.out.println("Calendar inAmerica                = "+sdfInAmerica.format(datetimeInAmerica.getTime()));
        long dateTimeMilliseconds = mydatetime.getMillis(); 
        int millisecondsOffset = mytimezone.getOffset(dateTimeMilliseconds);
        System.out.println(String.format("Milliseconds = %d, offset = %d", dateTimeMilliseconds, millisecondsOffset));
        long millisecondsInTimezone = dateTimeMilliseconds+millisecondsOffset;
        System.out.println("millisecondsInTimezone            = "+millisecondsInTimezone);
        long millisecondsInUTC = mytimezone.convertLocalToUTC(millisecondsInTimezone, false);
        DateTime dateTimeUTC = new DateTime(millisecondsInUTC, DateTimeZone.UTC);
        System.out.println("DateTime (UTC)                    = "+dtf.print(dateTimeUTC));
        Calendar datetimeInLondon = dateTimeUTC.toGregorianCalendar();
  SimpleDateFormat sdfInLondon = new SimpleDateFormat(formatString);
  sdfInAmerica.setCalendar(datetimeInLondon); // to set the timezone.
  System.out.println("Calendar inLondon                 = "+sdfInLondon.format(datetimeInAmerica.getTime()));