May 30, 2011

JAXB and Joda-Time: Dates and Times

Joda-Time provides an alternative to the Date and Calendar classes currently provided in Java SE.  Since they are provided in a separate library JAXB does not provide a default mapping for these classes.  We can supply the necessary mapping via XmlAdapters.  In this post we will cover the following Joda-Time types:  DateTime, DateMidnight, LocalDate, LocalTime, LocalDateTime.



Java Model

The following domain model will be used for this example:

package blog.jodatime;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;

@XmlRootElement
@XmlType(propOrder={
    "dateTime",
    "dateMidnight",
    "localDate",
    "localTime",
    "localDateTime"})
public class Root {

    private DateTime dateTime;
    private DateMidnight dateMidnight;
    private LocalDate localDate;
    private LocalTime localTime;
    private LocalDateTime localDateTime;

    public DateTime getDateTime() {
        return dateTime;
    }

    public void setDateTime(DateTime dateTime) {
        this.dateTime = dateTime;
    }

    public DateMidnight getDateMidnight() {
        return dateMidnight;
    }

    public void setDateMidnight(DateMidnight dateMidnight) {
        this.dateMidnight = dateMidnight;
    }

    public LocalDate getLocalDate() {
        return localDate;
    }

    public void setLocalDate(LocalDate localDate) {
        this.localDate = localDate;
    }

    public LocalTime getLocalTime() {
        return localTime;
    }

    public void setLocalTime(LocalTime localTime) {
        this.localTime = localTime;
    }

    public LocalDateTime getLocalDateTime() {
        return localDateTime;
    }

    public void setLocalDateTime(LocalDateTime localDateTime) {
        this.localDateTime = localDateTime;
    }

}

XmlAdapters

Since Joda-Time and XML Schema both represent data and time information according to ISO 8601 the implementation of the XmlAdapters is quite trivial.

DateTimeAdapter

package blog.jodatime;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.joda.time.DateTime;

public class DateTimeAdapter 
    extends XmlAdapter<String, DateTime>{

    public DateTime unmarshal(String v) throws Exception {
        return new DateTime(v);
    }

    public String marshal(DateTime v) throws Exception {
        return v.toString();
    }

}

DateMidnightAdapter

package blog.jodatime;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.joda.time.DateMidnight;

public class DateMidnightAdapter 
    extends XmlAdapter<String, DateMidnight> {

    public DateMidnight unmarshal(String v) throws Exception {
        return new DateMidnight(v);
    }

    public String marshal(DateMidnight v) throws Exception {
        return v.toString();
    }

}

LocalDateAdapter

package blog.jodatime;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.joda.time.LocalDate;

public class LocalDateAdapter 
    extends XmlAdapter<String, LocalDate>{

    public LocalDate unmarshal(String v) throws Exception {
        return new LocalDate(v);
    }

    public String marshal(LocalDate v) throws Exception {
        return v.toString();
    }

}

LocalTimeAdapter

package blog.jodatime;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.joda.time.LocalTime;

public class LocalTimeAdapter 
    extends XmlAdapter<String, LocalTime> {

    public LocalTime unmarshal(String v) throws Exception {
        return new LocalTime(v);
    }

    public String marshal(LocalTime v) throws Exception {
        return v.toString();
    }

}

LocalDateTimeAdapter

package blog.jodatime;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.joda.time.LocalDateTime;

public class LocalDateTimeAdapter 
    extends XmlAdapter<String, LocalDateTime>{

    public LocalDateTime unmarshal(String v) throws Exception {
        return new LocalDateTime(v);
    }

    public String marshal(LocalDateTime v) throws Exception {
        return v.toString();
    }

}

Registering the XmlAdapters

We will use the @XmlJavaTypeAdapters annotation to register the Joda-Time types at the package level.  This means that whenever these types are found on a field/property on a class within this package the XmlAdapter will automatically be applied.

@XmlJavaTypeAdapters({
    @XmlJavaTypeAdapter(type=DateTime.class, 
        value=DateTimeAdapter.class),
    @XmlJavaTypeAdapter(type=DateMidnight.class, 
        value=DateMidnightAdapter.class),
    @XmlJavaTypeAdapter(type=LocalDate.class, 
        value=LocalDateAdapter.class),
    @XmlJavaTypeAdapter(type=LocalTime.class, 
        value=LocalTimeAdapter.class),
    @XmlJavaTypeAdapter(type=LocalDateTime.class, 
        value=LocalDateTimeAdapter.class)
})
package blog.jodatime;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;

Demo

To run the following demo you will need the Joda-Time jar on your classpath.  It can be obtained here:
package blog.jodatime;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;

public class Demo {

    public static void main(String[] args) throws Exception {
        Root root = new Root();
        root.setDateTime(new DateTime(2011, 5, 30, 11, 2, 30, 0));
        root.setDateMidnight(new DateMidnight(2011, 5, 30));
        root.setLocalDate(new LocalDate(2011, 5, 30));
        root.setLocalTime(new LocalTime(11, 2, 30));
        root.setLocalDateTime(new LocalDateTime(2011, 5, 30, 11, 2, 30));

        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }

}

Output

The following is the output from our demo code:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <dateTime>2011-05-30T11:02:30.000-04:00</dateTime>
    <dateMidnight>2011-05-30T00:00:00.000-04:00</dateMidnight>
    <localDate>2011-05-30</localDate>
    <localTime>11:02:30.000</localTime>
    <localDateTime>2011-05-30T11:02:30.000</localDateTime>
</root>

Further Reading

If you enjoyed this post, you may also be interested in:

7 comments:

  1. How do you handle exceptions with this? I have a LocalDate birthdate field and when I send '1980x-01-2' it just seems to absorb the exception and leave the field null

    ReplyDelete
  2. Hi Collin,

    This behaviour will vary between JAXB implementations. EclipseLink JAXB (MOXy) for example will throw a conversion exception.

    I gave a slightly more detailed answer to a similar question on Stack Overflow:
    - JaxB binding XMLGregorianCalendar

    -Blaise

    ReplyDelete
  3. Hi Blaise

    Thanks for the useful post, I didn't realize that joda was using the correct ISO standard by default!

    One minor note though - the sources for XmlAdapter say that they 'value' in the marshal/unmarshal methods can be null, so the converter should cleanly handle those situations, otherwise you may get some NullPointerExceptions.

    Also, the LocalDateTimeAdapter will fail to parse a string that contains a timezone (with an error message like this: Invalid format: "2001-10-26T21:32:52+02:00" is malformed at "+02:00"). I've modified my sources to first use a DateTime instance,

    if (dateTime == null) {return null;}
    return new DateTime(dateTime).toLocalDateTime();

    instead of

    return new LocalDateTime(dateTime);

    ReplyDelete
  4. When using DateTime how would I set up this in my schema?

    I have used the following type with XMLGregorianCalendar:

    ReplyDelete
    Replies
    1. I believe part of your question has been lost due to the formatting restrictions of the comment area. You may find the following post useful:
      - XML Schema to Java - Generating XmlAdapters

      Feel free to send me a note through my contact page:
      - Contact Me

      -Blaise

      Delete
  5. Thank you man. Big time saver

    ReplyDelete
  6. Excellent example. work well on my domain package to serialized my object using JodaTime

    ReplyDelete