First Sin
First Sin

Reputation: 94

Jackson incorrectly serializes XMLGregorianCalendar

There is an XMLGregorianCalendar object that contains the value "2021-01-18T18:43:26.884Z" (This is its output in toString()). When I try to serialize this date with Jackson, I get a date 3 hours later in the output:

XMLGregorianCalendar date = ...;
ObjectMapper mapper = new ObjectMapper();

String out = mapper.writeValueAsString(obj); // Output: 1610995406884 (Converted to Date: Mon Jan 18 21:43:26 MSK 2021)

How can I solve this problem?

Upvotes: 2

Views: 1956

Answers (2)

Thomas Fritsch
Thomas Fritsch

Reputation: 10127

There is no problem at all.

The time "2021-01-18T18:43:26.884Z" in your XMLGregorianCalendar is 18:43 in the GMT timezone (Greenwich mean time, London) or UTC+0 (because of the trailing Z).

In the other hand you have a Date object with the string representation "Mon Jan 18 21:43:26 MSK 2021", which is 21:43 in the MSK timezone (Moscow Standard Time) or UTC+3. The Date class chose this timezone for formatting the output simply because your computer is located near Moscow.

So both are actually the same point in time, just only stringified for two different timezones.

Upvotes: 3

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 78965

java.util.Date represents the number of milliseconds since the epoch

The java.util.Date object is not a real date-time object like the modern date-time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). When you print an object of java.util.Date, its toString method returns the date-time in the JVM's timezone, calculated from this milliseconds value. If you need to print the date-time in a different timezone, you will need to set the timezone to SimpleDateFormat and obtain the formatted string from it.

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(1610995406884L);
        Date date = calendar.getTime();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
        sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
        System.out.println(sdf.format(date));
        sdf.setTimeZone(TimeZone.getTimeZone("Europe/Moscow"));
        System.out.println(sdf.format(date));
    }
}

Output:

2021-01-18T18:43:26.884
2021-01-18T21:43:26.884

Note that the date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API.

Convert the legacy, java.util.Date to the modern java.time.Instant using java.util.Date#toInstant:

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(1610995406884L);
        Date date = calendar.getTime();
        Instant instant = date.toInstant();
        System.out.println(instant);

        // You can convert Instant to other types e.g.
        ZonedDateTime zdt = instant.atZone(ZoneId.of("Europe/Moscow"));
        // Print default format i.e. the value of zdt#toString
        System.out.println(zdt);
        // Custom format
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss'['z']'");
        String formatted = dtf.format(zdt);
        System.out.println(formatted);
    }
}

Output:

2021-01-18T18:43:26.884Z
2021-01-18T21:43:26.884+03:00[Europe/Moscow]
2021-01-18T21:43:26[MSK]

The Z stands for Zulu and represents UTC (or GMT).

Upvotes: 2

Related Questions