Converting Java Dates to Xml Schema dateTime, with timezone
One would think this might be easy. One would be wrong.
One would not be reckoning with the cryptic Java Calendar and Date classes, officially one of the worst features of the Java API
This is the same Calendar class in which January is month zero, and calendar.set(Calendar.MONTH, 1) gives you February.
Think I’m joking? I wish I was.
If you think the above is unintuitive, check out this gem of a comment from the java.uil.Calendar class:
* Field number ... indicating the month. This is a calendar-specific value. The first month of the year is
* JANUARY which is 0; the last depends on the number of months in a year.
*/
public final static int MONTH = 2;
How many times in your recent memory has the number of months in a year varied?
In any case, that is enough of a moan.
I was trying to convert a Date to a String in a XSD friendly dateTime format.
This requires the timezone to be appended in the form +|-00:00, which indicates time relative to GMT
The problem is you can’t get SimpleDateFormat to do this –>
There is no way I could see to format dates to include the colon delimiter between the timezone hours and timezone minutes.
So, I knocked up a utility XsdDatetimeFormat.java class to do this.
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.IllegalFormatException;
/**
* @author ebbuttn
*
* A utility class to facilitate converting Java Date instances to an XML Schema dateTime
*
* Formats Date instance to XSD compatible dateTime Strings with specified timezone -->
* e.g. 2002-10-10T12:00:00-05:00 (noon on 10 October 2002, Eastern Standard Time in the U.S.)
*
* http://www.w3.org/TR/xmlschema-2/#isoformats
*/
public class DateToXsdDatetimeFormatter {
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
public DateToXsdDatetimeFormatter () {}
public DateToXsdDatetimeFormatter (TimeZone timeZone) {
simpleDateFormat.setTimeZone(timeZone);
}
/**
* Parse a xml date string in the format produced by this class only.
* This method cannot parse all valid xml date string formats -
* so don't try to use it as part of a general xml parser
*/
public synchronized Date parse(String xmlDateTime) throws ParseException {
if ( xmlDateTime.length() != 25 ) {
throw new ParseException("Date not in expected xml datetime format", 0);
}
StringBuilder sb = new StringBuilder(xmlDateTime);
sb.deleteCharAt(22);
return simpleDateFormat.parse(sb.toString());
}
public synchronized String format(Date xmlDateTime) throws IllegalFormatException {
String s = simpleDateFormat.format(xmlDateTime);
StringBuilder sb = new StringBuilder(s);
sb.insert(22, ':');
return sb.toString();
}
public synchronized void setTimeZone(String timezone) {
simpleDateFormat.setTimeZone(TimeZone.getTimeZone(timezone));
}
}
And a test….:
import java.util.Calendar;
import java.util.TimeZone;
import java.util.Date;
import java.text.ParseException;
/**
* @author ebbuttn
*/
public class TestDateToXsdDatetimeFormatter extends TestCase {
private Calendar c;
public void setUp() {
c = Calendar.getInstance(TimeZone.getTimeZone("PST"));
c.set(Calendar.YEAR, 2050);
c.set(Calendar.MONTH, 11); //months start from zero!
c.set(Calendar.DAY_OF_MONTH, 6);
c.set(Calendar.HOUR_OF_DAY, 21);
c.set(Calendar.MINUTE, 30);
c.set(Calendar.SECOND, 15);
c.set(Calendar.MILLISECOND, 0);
}
public void testParse() throws ParseException {
DateToXsdDatetimeFormatter xdf = new DateToXsdDatetimeFormatter ();
Date d = xdf.parse("2050-12-06T21:30:15-08:00");
assertEquals(c.getTime(), d);
}
public void testFormat() throws ParseException {
DateToXsdDatetimeFormatter xdf = new DateToXsdDatetimeFormatter ();
xdf.setTimeZone("PST");
String s = xdf.format(c.getTime());
assertEquals("2050-12-06T21:30:15-08:00", s);
}
}
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
October 2nd, 2008 at 9:58 pm
The lexical space of dateTime consists of finite-length sequences of characters of the form: ‘-’? yyyy ‘-’ mm ‘-’ dd ‘T’ hh ‘:’ mm ‘:’ ss (‘.’ s+)? (zzzzzz)?
(refer to http://www.w3.org/TR/xmlschema-2/#dateTime)
Your if ( xmlDateTime.length() != 25 ) line breaks that.
October 3rd, 2008 at 11:01 am
That’s quite true and a good point. The class should make it clearer that that it only supports parsing date values in its ‘expected format’ – which is the format it uses itself when it generates xml. So that means it is cannot be used as part of a general xml parser, which does limit its usefulness. But it wasn’t written to solve that problem, I think I only added the parse method to help with some testing. It is really only intended to help with formatting Dates to xml Strings – a one way conversion.
Your comment was helpful and the original code was a bit misleading, so I will tidy it up to make it clearer that it can’t be used to parse xml dates in general.
November 24th, 2008 at 2:39 pm
“How many times in your recent memory has the number of months in a year varied?”
Arabic calendar has 11 months in a year
November 24th, 2008 at 3:17 pm
Ha ha, it just goes to prove I can’t write anything in this blog without someone proving me wrong. Having said that I still find it unintuitive that January is month zero rather than month one in the Calendar API.
However can you send me more details of the Arabic calendar with 11 months, I’m interested to learn more? According to the wikipedia page the arabic/islamic calendar seems to also have 12 months. http://en.wikipedia.org/wiki/Arabic_calendar
Perhaps this is not the calendar you are referring to. In any case, the point is a good one – I suppose there may well be other calendars in which there are not exactly 12 months, which one might want to represent.
However design of the Calendar class is a bit confusing again here. The references to ‘January’ in the javadocs for the MONTH field, and the existence of a JANUARY constant (as well as constants for the other Gregorian calendar months) does makes the abstract Calendar class appear to be aimed at the Gregorian Calendar primarily. If it is supposed to be non-calendar-specific then perhaps all Gregorian calendar concepts should have been pushed down into the Gregorian Calendar subclass. From the class docs it does seem as if the intention is that the Calendar class can be subclassed to add support for other calendar types – but then having Gregorian specific concepts in the superclass is misleading.