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:

8 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
  7. Excelled article!
    May be worthwhile to note to prevent WTF moments:
    1. you can put the above classes to ANY package you like, e.g. com.mycompany.utils.jaxb
    2. you MUST create a file from "Registering the XmlAdapters" section named "package-info.java" in the package where you want to USE the adapter utility classes
    3. you have to define package-info.java in EACH and EVERY package you want to use, not inherited.
    (Fix the imports of course)

    ReplyDelete