2

I have a date as input = 2021-03-12T10:42:01.000Z.... and I want to transform into this format:

String pattern = "yyyy-MM-dd'T'HH:mm:ssZ";


public String getDate(XMLGregorianCalendar input) {
    DateFormat f = new SimpleDateFormat(pattern);
    input.toGregorianCalendar().setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
    String output = f.format(input.toGregorianCalendar().getTime());
    System.out.println(output);
}

2021-03-12T12:42:01+0200

Basically, it's adding 2hs more. Maybe it's related with the time zone, I didn't test it in another computer. I have 2 questions:

  • Why this is happening
  • What can I do to avoid it? It's a legacy app so I don't want to do a big change

Thanks

Julio
  • 429
  • 2
  • 16
  • 1
    Also not that `input.toGregorianCalendar()` most likely (depending on the implementation) gives you a new instance with every call. Therefore your statement to set the timezone probably does not change anything. However, it is not the source of the different hour in the output. – Matt Jun 02 '21 at 06:50
  • Which result had you wanted? `2021-03-12T10:42:01+0000`? Your string is in UTC, denoted by the trailing `Z`. Will that always be the case, or can you have other offsets, for example `-03`, `-0100` or `+02:00`, instead? – Ole V.V. Jun 02 '21 at 17:04
  • Both your input and your output are in [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), so is your conversion really necessary? If the receiver of your string accepts ISO 8601, I suggest you just give them `input.toString()`. – Ole V.V. Jun 02 '21 at 17:06

2 Answers2

3

Basically, it's adding 2hs more

Not really. It's giving you the output for the same instant in time, but in your system local time zone - because you're creating a SimpleDateFormat without specifying a time zone (or a culture):

DateFormat f = new SimpleDateFormat(pattern);

Personally I'd recommend avoiding using java.text.SimpleDateFormat entirely, preferring the java.time types and formatters. But if you definitely want to use SimpleDateFormat, just make sure you set the time zone to UTC (assuming you always want UTC) and ideally set the culture as well (e.g. to Locale.ROOT).

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I think I'm setting the time zone in the next line – Julio Jun 02 '21 at 06:50
  • 1
    @Julio: You're not setting it on the *formatter* though. The value you provide to the formatter is just a `java.util.Date`, which doesn't have an associated time zone - it's just an instant in time. The formatter uses whatever time zone has been set *on the formatter* to represent that instant in time. – Jon Skeet Jun 02 '21 at 06:53
  • ah ok, makes sense. Thanks so much – Julio Jun 02 '21 at 06:53
  • @Julio You need `f.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));` – Matt Jun 02 '21 at 06:54
  • @Matt – What about the trailing 'Z' in the pattern? For my understanding, it is the timezone (Zulu = UTC). – tquadrat Jun 02 '21 at 06:58
  • Yeah, I tried that and it worked. Thanks Matt – Julio Jun 02 '21 at 06:58
  • 1
    @tquadrat Yes, the timezone on `input` is most likely already defined since the trailing `Z` is shown. This is valid under the assumption that the implementation (`XMLGregorianCalendar` is abstract) handles this properly. In the XML-type, there is no concept of timezones. This is why we can set it explicitly or use the `#toGregorianCalendar(TimeZone, ..., ...)` method to convert in the timezone we want. – Matt Jun 02 '21 at 07:07
3

The Answer by Jon Skeet is correct, and smart. You appear to be seeing simply a time zone adjustment. Your two strings 2021-03-12T10:42:01.000Z & 2021-03-12T12:42:01+0200 represent the very same moment. The 12 noon hour, if two hours ahead of UTC, is the same as 10 AM hour with an offset-from-UTC of zero hours-minutes-seconds.

And, as mentioned in that other Answer, you really should avoid using the terrible date-time classes bundled with the earliest versions of Java.

tl;dr

myXMLGregorianCalendar     // Legacy class, representing a moment as seen in some time zone.
.toGregorianCalendar()     // Another legacy class, also representing a moment as seen in some time zone.
.toZonedDateTime()         // A modern *java.time* class, representing a moment as seen in some time zone.
.toInstant()               // Another *java.time* class, for representing a moment as seen in UTC.
.truncatedTo(              // Lopping off some smaller part of the date-time value.
    ChronoUnit.SECONDS     // Specifying whole seconds as our granularity of truncation, so lopping off any fractional second.
)                          // Returns another `Instant` object, rather than altering (mutating) the original, per immutable objects pattern.
.toString()                // Generating text representing the content of our `Instant` object, using standard ISO 8601 format.

java.time

The modern approach uses the java.time classes that years ago supplanted SimpleDateFormat, XMLGregorianCalendar , GregorianCalendar, and such.

Convert legacy <——> modern

You can easily convert from the legacy types to java.time. Look for new to/from methods on the old classes.

ZonedDateTime zdt = myXMLGregorianCalendar.toGregorianCalendar().toZonedDateTime() ;

Adjust to offset of zero

Adjust from whatever time zone to UTC by extracting an Instant. This class represents a moment as seen in UTC, always in UTC.

Instant instant = zdt.toInstant() ; 

Understand that zdt and instant both represent the same moment, the same point on the timeline, but differ in their wall-clock time.

Truncation

Given the formatting pattern seen in your Question, you seem want to work with a granularity of whole seconds. To lop off any fractional second, truncate to seconds.

Instant truncated = instant.truncatedTo( ChronoUnit.SECONDS ) ;

ISO 8601

Your desired text format is defined in the ISO 8601 standard. That standard is used by default in java.time for parsing/generating strings. So no need to specify any formatting pattern.

String output = truncated.toString() ;
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • This is the way forward. The OP needs the string in `yyyy-MM-dd'T'HH:mm:ssZ` format. It would be useful for him and future visitors if you add a section regarding the format. – Arvind Kumar Avinash Jun 05 '21 at 14:11
  • @ArvindKumarAvinash Thanks for offering a suggestion. But I don’t know what you mean specifically. What more is there to say beyond my last paragraph? – Basil Bourque Jun 05 '21 at 16:54
  • @ArvindKumarAvinash Thank you for the suggestion. I added code and prose explaining truncation to whole seconds. – Basil Bourque Jun 07 '21 at 05:50