Search code examples
javadatedate-format

Java - Error when serializing/deserializing the date


I'm having trouble to figure out what is this date format: 2019-02-28T12:17:46.279+0000. I have tried different date formats to get this result but nothing worked. Closest pattern was: yyyy-MM-dd'T'HH:mm:ss.SSSZ But with this pattern output was like this: 2019-02-28T12:17:46.279-0000 (- is after seconds instead of +)

I get this exception:

Caused by: java.lang.IllegalArgumentException: 2019-02-28T12:17:46.279+0000
    at org.apache.xerces.jaxp.datatype.XMLGregorianCalendarImpl$Parser.skip(XMLGregorianCalendarImpl.java:2932)
    at org.apache.xerces.jaxp.datatype.XMLGregorianCalendarImpl$Parser.parse(XMLGregorianCalendarImpl.java:2898)
    at org.apache.xerces.jaxp.datatype.XMLGregorianCalendarImpl.<init>(XMLGregorianCalendarImpl.java:478)
    at org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl.newXMLGregorianCalendar(DatatypeFactoryImpl.java:230)
    at __redirected.__DatatypeFactory.newXMLGregorianCalendar(__DatatypeFactory.java:132)
    at javax.xml.bind.DatatypeConverterImpl.parseDate(DatatypeConverterImpl.java:519)
    at javax.xml.bind.DatatypeConverter.parseDate(DatatypeConverter.java:431)
    at eu.europa.ec.my.custom.package.model.mapper.XsdDateTimeConverter.unmarshal(XsdDateTimeConverter.java:23)

My XsdDateTimeConverter class looks like this:

public class XsdDateTimeConverter {

    public static Date unmarshal(String dateTime) {
        return DatatypeConverter.parseDate(dateTime).getTime();
    }

    public static String marshalDate(Date date) {
        final Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return DatatypeConverter.printDate(calendar);
    }

    public static String marshalDateTime(Date dateTime) {
        final Calendar calendar = Calendar.getInstance();
        calendar.setTime(dateTime);
        return DatatypeConverter.printDateTime(calendar);
    }
}

And parsed date in my postgres db looks like this:

move_timestamp timestamp(6) with time zone

2019-02-28 12:17:46.279+00

In my rest method I use ObjectMapper like this.

MyCustomResponseDto responseDto = customService.getCustomResponseDto(query);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
String strValue = mapper.writeValueAsString(responseDto);
return Response.ok(strValue).build();

I guess what I really wanted is what is the right pattern for this date. I can go in this page: http://www.sdfonlinetester.info/ and enter my pattern (e.g. yyyy-MM-dd'T'HH:mm:ss.SSSZ) and it gives you an actual date output for that pattern. I need the other way around. I want to enter my date and it will give me the right pattern for it.


Solution

  • tl;dr

    OffsetDateTime.parse( 
        "2019-02-28T12:17:46.279+0000" , 
        DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" , Locale.ROOT )
    )
    

    java.time

    You are using terrible Calendar class that was supplanted years ago by the java.time classes.

    ISO 8601

    Your input string is in standard ISO 8601 format, designed for human-readable machine-parseable textual representations of date-time values. That is a good thing.

    The java.time classes use ISO 8601 formats by default when parsing/generating strings.

    OffsetDateTime

    You should be able to simply parse with OffsetDateTime.

    OffsetDateTime.parse( "2019-02-28T12:17:46.279+0000" )
    

    …but unfortunately the optional COLON character being omitted from the offset (+00:00) is a problem. The OffsetDateTime class has a minor bug where it refuses to parse without that character. The bug is discussed here and here.

    The ISO 8601 standard permits the colon’s absence, but practically you should always include it. The OffsetDateTime class is not alone; I have seen other libraries that break when the colon or padding zeros are absent. I suggest asking the publisher of your data to use the full ISO 8601 format including the colon.

    The workaround for the OffsetDateTime bug is to define a DateTimeFormatter explicitly.

    DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" , Locale.ROOT ) ;
    

    Then parse.

    String input = "2019-02-28T12:17:46.279+0000" ;
    OffsetDateTime odt = OffsetDateTime.parse( input , f ) ;
    

    To generate text in full standard ISO 8601 format, simply call toString.

    String output = odt.toString() ;
    

    See this code run live at IdeOne.com.

    output: 2019-02-28T12:17:46.279Z

    The Z on the end means UTC, that is +0000 or +00:00. Pronounced “Zulu”. Very commonly used, more immediately readable than the numeric offset.

    If you want same output format as your input, use the same DateTimeFormatter.

    String output = odt.format( f ) ;