Date, Time and Timezone in Java

If this is the first time you program with time in Java, chances are you almost blow your head off, cry or unknowingly created very subtle bug (and this article might not help much :))

The java.util.Date Object

The first concept to stick to is the Date object does not have timezone offset added into it. All it does is just storing millisecond offset since epoch (1 January 1970) in UTC (UTC is almost similar to GMT, but in programming world we always use UTC). Period.

So if you live in Sydney, Australia (UTC+10 — let’s assume no DST) and the time now is 1 January 1970, 10:00:00.000, if you created a new java.util.Date object, the millisecond representation of this object would be 0

Date d = new Date();
long epochOffsetUTCMillis = d.getTime(); // this is 0

Same goes if you live if New York City, USA (assume UTC-5), and the time now is 31 December 1969 19:00:00.000

Representing / creating java.util.Date as / from a String

The most commong thing you would do with Date object would be to convert to/from a String. Converting from Date to String is called formatting and String to Date is called parsing. To parse/format you first need to:

  1. Create a DateFormat object
  2. Decide what pattern to format / parse to / from
  3. Decide which time zone to format / parse to / from (default uses your local time zone)

Examples:

  • Formatting current time using default time zone (assuming current time is 17 July 2013 09:12)
    DateFormat df = new SimpleDateFormat("dd MMM yyyy HH:mm");
    // below will produce "17 July 2013 09:12"
    String timeNow = df.format(new Date());
    
  • Formatting current time using UTC time zone (assuming local time is 17 July 2013 09:12 and local time zone is UTC-5)
    DateFormat df = new SimpleDateFormat("dd MMM yyyy HH:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
    // below will produce "17 July 2013 14:12"
    String timeNowUTC = df.format(new Date());
    
  • Parsing “1 Jan 1970 10:00” in UTC+10 time zone
    DateFormat df = new SimpleDateFormat("dd MMM yyyy HH:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC+10"));
    // Below date object offset from epoch is 0
    Date d = df.parse("1 Jan 1970 10:00");
    

Checking, Adding and Removing Day, Month, Hour, Minute etc

Now this get a bit tricky, to check / manipulate particular field of a date (day, hour, month etc) you need a java.util.Calendar instance. Remember working with calendar object is also dependant on time zone. 0 offset from UTC epoch will look like Thursday, 1 January 1970 10:00:00 if you’re located at UTC+10, and Wednesday, 31 December 1969 19:00:00 if you’re located at UTC-5.

Examples:

  • Say “hello world” if today in local time is Monday
    Calendar cal = Calendar.getInstance();
    if(cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY)
      System.out.println("hello world");
    
  • Say “hello world” if hour now in UTC is 5
    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    if(cal.get(Calendar.HOUR_OF_DAY) == 5)
      System.out.println("hello world");
    
  • Get the time 3 days ago from now in local time
    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.DATE, -3);
    Date threeDaysAgo = cal.getTime();
    
  • Check what day your birthday falls into
    Date myBirthday = //.. set it to your birthday (see date parsing above)
    Calendar cal = Calendar.getInstance();
    cal.setTime(myBirthday);
    // If your birthday falls on Monday this will be 2, Tuesday 3 and so on
    // see http://docs.oracle.com/javase/6/docs/api/java/util/Calendar.html
    int dayOfMyBirthday = cal.get(Calendar.DAY_OF_WEEK);
    

Unit Testing Your Time Related Code

This is probably the most important thing to do. I found the best practice is provide method overload of current time so you can easily unit test your code. In the example below I had a method that returns previous business day based on current time, note how I overload it with a currentTime parameter

public Date getPrevBusinessDay() {
  return getPrevBusinessDay(new Date());
}
public Date getPrevBusinessDay(Date currentTime) {
  Calendar cal = Calendar.getInstance();
  cal.setTime(currentTime);
  if(cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) cal.add(Calendar.DATE, -3);
  else cal.add(Calendar.DATE, -1);
  return cal.getTime();
}

You can then write a unit test for code above as such

@Test
void testPrevBusinessDay() {
  DateFormat df = new SimpleDateFormat("dd MMM yyyy");
  
  assertEquals(df.parse("17 July 2013"), getPrevBusinessDay(df.parse("18 July 2013")));
  assertEquals(df.parse("05 July 2013"), getPrevBusinessDay(df.parse("08 July 2013")));
}
Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s