Search code examples
javadatedatetimesoapxmlgregoriancalendar

Java String date to XMLGregorianCalendar whit format yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'


I have a soap webservice with a date tag with the following format

2019-12-01T04:00:00.0000000Z (seven zeros)

Now that I am implementing the client I cannot create an XMLGregorianCalendar with seven zeros, I only have three zeros left

My code

    DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'");
    Date date = df.parse("2019-12-01T04:00:00.0000000Z");       
    GregorianCalendar gcal = new GregorianCalendar();
    gcal.setTime(date);
    gcal.setTimeZone(TimeZone.getTimeZone("UTC"));

    XMLGregorianCalendar fecha = DatatypeFactory.newInstance().newXMLGregorianCalendar(gcal);

    System.out.println("fecha ->"+fecha.toString());

output

    fecha ->2019-12-01T07:00:00.000Z

How can I make the XMLGregorianCalendar keep me with seven zeros in the format?


Solution

  • You probably don’t need 7 decimals

    Generally a SOAP web service doesn’t require any particular number of decimals on the seconds of a time. It needs XML with date and time in XML format. XML’s date and time format is inspired from ISO 8601 format (links at the bottom). The W3Schools page on Date and Time Data Types doesn’t even mention the possibility of specifying any fraction of second:

    The dateTime is specified in the following form "YYYY-MM-DDThh:mm:ss" …

    Fraction of second is allowed, though, “to an arbitrary level of precision” to quote W3C’s XML Schema Part 2: Datatypes Second Edition. So if your web service insists on exactly 7 decimals, it’s a peciliar and unusual restriction. You may want to challenge it.

    Alternatives to XMLGregorianCalendar

    The XMLGregorianCalendar class is old now. It was used exactly for producing ISO 8601 format for XML documents as used with SOAP and in many other places. The classes of java.time, the modern Java date and time API, produce ISO 8601 format too. We prefer to use these (unless very special requirements necessitate XMLGregorianCalendar).

        OffsetDateTime odt = OffsetDateTime.of(2019, 12, 1, 4, 0, 0, 0, ZoneOffset.UTC);
        System.out.println(odt);
    

    This snippet outputs:

    2019-12-01T04:00Z

    If you do need 7 decimals, use a formatter:

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSX");
        System.out.println(odt.format(formatter));
    

    2019-12-01T04:00:00.0000000Z

    Of course, if you already got your string, 2019-12-01T04:00:00.0000000Z, as in your question, just put it into your XML document directly. If you need to validate it first, pass it to Instant.parse() and see if you get a DateTimeFormatException.

    If you really cannot avoid the need for an XMLGregorianCalendar with 7 decimals on the seconds, there are two ways to produce one:

    1. DatatypeFactory.newXMLGregorianCalendar​(String) as used in the other answer:

          XMLGregorianCalendar fecha = DatatypeFactory.newInstance()
                  .newXMLGregorianCalendar("2019-12-01T04:00:00.0000000Z");
          System.out.println(fecha);
      

      2019-12-01T04:00:00.0000000Z

    2. Passing a BigDecimal to DatatypeFactory:

          XMLGregorianCalendar fecha = DatatypeFactory.newInstance()
                  .newXMLGregorianCalendar(BigInteger.valueOf(2019), 12, 1, 
                          4, 0, 0, new BigDecimal("0.0000000"), 0);
      

    In any case, under all circumstances I recommend you don’t use SimpleDateFormat and Date. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. The same is true for GregorianCalendar. We might sometimes use it with XMLGregorianCalendar because conversion between the two exist (as used in your question). GregorianCalendaronly has millisecond precision, so you will never get 7 decimals through such a conversion.

    Bugs in your code

    There are two bugs in your code.

    1. Hardcoding Z as a literal in your format pattern string is wrong. Z is an offset (of zero) from UTC and needs to be parsed as such, or you will get a wrong time on the vast majority of JVMs.
    2. SimpleDateFormat too only supports milliseconds, exactly three decimals in the seconds. When the fraction is all zeroes, you won’t notice the error, but as soon as someone puts in a non-zero digit somewhere, you will get an incorrect result. There is no way that SimpleDateFormat can handle 2 or 4 or 7 fractional digits correctly.

    Links