2011-10-02

Representing Date and Time in computer programs. Part 2 (Java)

In this series of posts I will focus on how to do common date and time operations in Java world. Basically, I could give a link to Java Internationalization Trail, but it doesn't actually cover many Java-related technologies.

Part 1 – A brief look at the history: java.util.Date

This was Sun's first (failed) attempt to represent Date and Time. The class is as old as the whole JDK - it was here from JDK 1.0. Most of methods in this class are deprecated therefore I am not going to spend too much time on this topic.

Creating Date instances

To create Date instance and initialize it with a current time one would have used:

// Current time
Date now = new Date();

Although this constructor is still valid (as of JDK 7), I would strongly recommend using Calendar instead.
Another example of constructor that is not deprecated wold be:

// Unix time of the epoch - number of milliseconds
// since January 1st, 1970 in relation to GMT
Date epoch = new Date(1311211111011L);

Please note that this constructor takes parameter in relation to GMT and not UTC. There is a subtle but important difference between them. If you stand a chance, please always use UTC.

All the other constructors are deprecated:

// Sun Apr 24 2011 15:21:33 local time zone  
Date then = new Date(111, 3, 24, 15, 21, 33);

Please note that months are zero-indexed, so 0 is January, 1 is February and so on. This is very confusing, especially for beginners. I agree it does not make any sense but who am I to judge Sun developers?
Also, years need to be provided in relation to 1900, so for 2011 you need to pass 111. Now, that really sucks.

Formatting and parsing

Date class has built-in formatting method called toLocaleString(). That is the one you should never, ever use. It is deprecated for a reason, and the reason is it is simply invalid.
Date class also contains deprecated parse(String) method and you should not use it for the same reason. Instead, you should use DateFormat class and its derivatives.

DateFormat, SimpleDateFormat and FastDateFormat

DateFormat is an abstract class and its concrete implementation is SimpleDateFormat. To obtain default formatting/parsing style for default locale (the one you can get via Locale.getDefault() which is not valid for web applications), you can use:

  • DateFormat.getDateInstance() – to format just date part
  • DateFormat.getTimeInstance() – to format just time part
  • DateFormat.getDateTimeInstance() – to format both date and time

For web applications, you somehow need to know what is end user's preferred Locale. I will talk about this in depth in separate post someday as it is really hard to do correctly but to cut long story short you need something along the lines of web browser's Accept Language. When you have it, you can obtain formatter object by calling DateFormat.getDateInstance(int, Locale) method (or other similar method). Now, on the first parameter (int). This is related to date style. DateFormat class has several built-in style constants:

  • DateFormat.FULL – style containing all possible entries (for this language, often resolves to long format)
  • DateFormat.LONG – long style, full month names, full hours, etc.
  • DateFormat.MEDIUM – medium style, could contain abbreviated month names, 2 digit year, etc.
  • DateFormat.SHORT – shortest style form, usually the least information that allows to identify date and time
  • DateFormat.DEFAULT – default format for given Locale and the one that should be always used

It might sound as a pretty strong claim that you should always use default format but I wrote that for a reason. First of all, default is... well, default. This usually resolves to some kind of standardized date format for given Locale. Therefore it is something that International User should be able to understand naturally, without using additional brain cycles. Another thing is the fact that other (non-default) format definitions are often invalid (for example long format for Polish and Russian Locale) and default format should be always correct (unfortunately, should does not mean is). OK, let me give you coding example:

Date now = new Date();
// it is quite important to pass a country as format might differ
Locale polishLocale = new Locale("pl", "PL");
DateFormat dateFormatter = DateFormat.getDateInstance(
            DateFormat.DEFAULT, polishLocale);
// prints something like 2011-04-23
System.out.println(dateFormatter.format(now));
DateFormat timeFormatter = DateFormat.getTimeInstance(
            DateFormat.DEFAULT, polishLocale);
// prints something like 17:05:32
System.out.println(timeFormatter.format(now));
DateFormat dateTimeFormatter = DateFormat.getDateTimeInstance(
            DateFormat.DEFAULT, DateFormat.DEFAULT, polishLocale);
// prints something like 2011-04-25 18:58:20
System.out.println(dateTimeFormatter.format(now));
try {      
    Date parsedDate = dateFormatter.parse("2011-04-25");
    // Mon Apr 25 00:00:00 CEST 2011
    System.out.println(parsedDate);
    Date parsedTime = timeFormatter.parse("09:11:55");
    // Thu Jan 01 09:11:55 CET 1970
    System.out.println(parsedTime);
    Date parsedDateTime = dateTimeFormatter.parse("11-04-25 18:33:44");
    // Sat Apr 25 18:33:44 CET 11
    System.out.println(parsedDateTime);            
}
catch (ParseException pe) {
    pe.printStackTrace();
}

Notice how easy it is to get wrong results while parsing. Although for time parsing it actually make sense that it is time of the epoch related (this way you can perform time-related calculations using Date's getTime() and setTime() methods), it is just plainly wrong to parse "11-04-25" as year 11. No exception will be thrown, mind you. Quite painful gotcha. Of course it makes sense but still could result in programming error.
BTW. More experienced programmers know that DateFormat contains setLenient() method which allows you to control parse behavior – it should throw an exception if date is non-parse-able. The problem is, the default value is true (throw the exception) and no exception was thrown. Still it makes perfect sense but...

OK, now you know how to (more or less) correctly handle built-in Date and Time formats. But what to do when you need arbitrary format, for example ISO8601?
You can use either built-in SimpleDateFormat class or use Apache Commons Lang's FastDateFormat. Personally I would recommend the latter:

Date now = new Date();
String iso8601Pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'";
SimpleDateFormat iso8601Formatter =
    new SimpleDateFormat(iso8601Pattern);
TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
iso8601Formatter.setTimeZone(utcTimeZone);

Date now = new Date();
// prints something like 2011-04-28T18:27:08Z
System.out.println(iso8601Formatter.format(now));

FastDateFormat fastDateFormat =
    FastDateFormat.getInstance(iso8601Pattern, utcTimeZone);
// again prints out valid ISO8601 Date/Time String
System.out.println(fastDateFormat.format(now));


Converting time zones

In the previous paragraph, you probably noticed that I have been using java.util.TimeZone as the date formatter parameter. Frankly, if you just want to display correct time to end user you do not need anything else. Following example shows current time (in Tokyo time zone) formatted for Japanese user:

DateFormat dateFormat =
    DateFormat.getDateTimeInstance(
        DateFormat.DEFAULT,
        DateFormat.DEFAULT,
        Locale.JAPAN);
dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println(dateFormat.format(new Date()));

OK, but what can you do if you need to perform Date calculations, for example you need to know what is a current Unix time of the epoch in some arbitrary time zone (as oppose to GMT)? You can use getTime() to obtain GMT-based time of the epoch and then use TimeZone class to obtain the target time zone offset:

TimeZone pacificTimeZone = TimeZone.getTimeZone("America/Los_Angeles");
long currentTime = new Date().getTime();
long convertedTime = currentTime +
    pacificTimeZone.getOffset(currentTime);

Performing date calculations like this literally sucks. How would you approach adding arbitrary number of days? By using getTime() and adding milliseconds? I will write a wrapper to do that - I can almost hear you saying it. Well, turns out somebody already did.

Apache Common Lang's DateUtils

Generally, Apache Commons Lang is always worth referencing and I usually have this on my projects' Class Path. If you need to perform many Date and Time related calculations, DateUtils class is the one that could save your day.

That concludes first part of Java Date and Time formatting, stay tuned for more (I trick myself into thinking I will actually finish these articles).