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
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:
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:
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
ReplyDeleteHi Collin,
ReplyDeleteThis 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
Hi Blaise
ReplyDeleteThanks 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);
When using DateTime how would I set up this in my schema?
ReplyDeleteI have used the following type with XMLGregorianCalendar:
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:
Delete- XML Schema to Java - Generating XmlAdapters
Feel free to send me a note through my contact page:
- Contact Me
-Blaise
Thank you man. Big time saver
ReplyDeleteExcellent example. work well on my domain package to serialized my object using JodaTime
ReplyDelete