Monday, February 4, 2008

Be wary of SimpleDateFormat

Java's SimpleDateFormat class provides a convenient method of parsing arbitrary strings into Date objects, and formatting Dates back into strings. However, like the rest of the standard Date/Time classes in the core API, there are a couple of important things you need to be aware of when working with this class. Ignore these caveats and there is a good chance you will be spending an inordinate amount of time debugging obscure issues with dates!

Can you guess the output of the following program?

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateTest {

public static void main(String[] args) throws Exception {
String fmt =
"yyyyMMdd";

String testDate = "20080530";

Date dt = (new SimpleDateFormat(fmt)).parse(testDate);

System.out.println(dt.toString());

testDate = "2008-05-30";

dt = (new SimpleDateFormat(fmt)).parse(testDate);
System.
out.println(dt.toString());
}
}

Believe it or not, it is:

Fri May 30 00:00:00 EDT 2008
Wed Dec 05 00:00:00 EST 2007

SimpleDateFormat will not throw an error if it receives string input which does not conform to its specified format string; it will instead silently construct an incorrect date! One possible solution (although I have seen cases where this also will not work) is:

DateFormat df = new SimpleDateFormat(fmt);
df.setLenient(false);
System.
out.println(df.parse(testDate));

Resulting in:

Exception in thread "main" java.text.ParseException: Unparseable date: "2008-05-30"
at java.text.DateFormat.parse(Unknown Source)
at sandbox.DateTest.main(
DateTest.java:23)

This should serve as a good example as any of the importance of good unit tests!

Lastly, it is also important to know that SimpleDateFormat is not thread-safe! This means that you must not use it as a member variable of a multithreaded service class, for example (Servlet, MessageDrivenBean, etc).

Since it can be expensive to keep instantiating a new SimpleDateFormat on each service request, a good practice is to store an instance in a ThreadLocal variable:


private ThreadLocal<DateFormat> myDateFormat = new ThreadLocal<DateFormat>(){
@Override protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};


2 comments:

Erik van Oosten said...

Advice: use Joda-time.

zak said...

Take a look at the spiffy framework. Its designed so you'll avoid pitfalls like that.

http://spiffyframework.sourceforge.net/