Olayinka
Olayinka

Reputation: 2845

ZonedDateTime parses successfully but output string is different

I am using the ZonedDateTime to parse and get current time according to the time zone.

When I parse the following String, the parse succeeds but the output String is different. Why is that?

String dateTimeString = "2016-05-04T12:58:22+01:00[Europe/Paris]";
ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString, ISO_ZONED_DATE_TIME);
System.out.println(dateTimeString);
System.out.println(dateTime.toString());

Output

2016-05-04T12:58:22+01:00[Europe/Paris]
2016-05-04T12:58:22+02:00[Europe/Paris]

At what point did it decide to change +1 to +2 and why wasn't an exception thrown?

I understand that the parameter in the parenthese [Europe/Paris] is optional but here it takes precedence over the offset.

On the other hand the following code

String dateTimeString = "2016-05-04T12:58:22+01:00";
ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString, ISO_ZONED_DATE_TIME);
System.out.println(dateTimeString);
System.out.println(dateTime.toString());

produces output

2016-05-04T12:58:22+01:00
2016-05-04T12:58:22+01:00

Upvotes: 3

Views: 374

Answers (2)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79075

Feature; not a bug.

The ZonedDateTime#parse gives higher precedence to ZoneId than ZoneOffset. The documentation mentions it clearly:

In terms of design, this class should be viewed primarily as the combination of a LocalDateTime and a ZoneId. The ZoneOffset is a vital, but secondary, piece of information, used to ensure that the class represents an instant, especially during a daylight savings overlap.

It can be further understood by looking into the decompiled source code:

class ZonedDateTime

public static ZonedDateTime parse(CharSequence text) {
    return parse(text, DateTimeFormatter.ISO_ZONED_DATE_TIME);
}

public static ZonedDateTime parse(CharSequence text, DateTimeFormatter formatter) {
    Objects.requireNonNull(formatter, "formatter");
    return formatter.parse(text, ZonedDateTime::from);
}

public static ZonedDateTime from(TemporalAccessor temporal) {
    if (temporal instanceof ZonedDateTime) {
        return (ZonedDateTime) temporal;
    }
    try {
        ZoneId zone = ZoneId.from(temporal);
        if (temporal.isSupported(INSTANT_SECONDS)) {
            long epochSecond = temporal.getLong(INSTANT_SECONDS);
            int nanoOfSecond = temporal.get(NANO_OF_SECOND);
            return create(epochSecond, nanoOfSecond, zone);
        } else {
            LocalDate date = LocalDate.from(temporal);
            LocalTime time = LocalTime.from(temporal);
            return of(date, time, zone);
        }
    } catch (DateTimeException ex) {
        throw new DateTimeException("Unable to obtain ZonedDateTime from TemporalAccessor: " +
                temporal + " of type " + temporal.getClass().getName(), ex);
    }
}

class ZoneId

public static ZoneId from(TemporalAccessor temporal) {
    ZoneId obj = temporal.query(TemporalQueries.zone());
    if (obj == null) {
        throw new DateTimeException("Unable to obtain ZoneId from TemporalAccessor: " +
                temporal + " of type " + temporal.getClass().getName());
    }
    return obj;
}

class TemporalQuery

public static TemporalQuery<ZoneId> zone() {
    return TemporalQueries.ZONE;
}

static final TemporalQuery<ZoneId> ZONE = new TemporalQuery<>() {
    @Override
    public ZoneId queryFrom(TemporalAccessor temporal) {
        ZoneId zone = temporal.query(ZONE_ID);
        return (zone != null ? zone : temporal.query(OFFSET));// <----- Look at this line
    }

    @Override
    public String toString() {
        return "Zone";
    }
};

Also, the whole point of ZonedDateTime is that you include ZoneId in it so that it can automatically give you the correct date-time considering Summer Time / Daylight Saving Time e.g. in absence of the ZoneId information, the following code will give you the same output for both print statements:

import java.time.ZonedDateTime;

public class Main {
    public static void main(String[] args) {
        String dateTimeString = "2016-05-04T12:58:22+01:00";
        ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString);
        System.out.println(dateTimeString);
        System.out.println(dateTime.toString());
    }
}

Output:

2016-05-04T12:58:22+01:00
2016-05-04T12:58:22+01:00

Again, it's a feature; not a bug because you asked the system to parse the date-time string with a fixed ZoneOffset information. For a simple analogy, you can think of ZoneOffset to be evaluated literally while ZoneId to be evaluated variably.

A couple of things which may be useful for you and the future visitors:

  1. The date-time string, 2016-05-04T12:58:22+01:00[Europe/Paris] is already in the default format used by ZonedDateTime#parse and therefore, you do not need to pass a DateTimeFormatter to it as an argument.
  2. System.out.println prints the string returned by toString method of the parameter object automatically and therefore you do not need to call toString explicitly.

Thus your code,

import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;

import java.time.ZonedDateTime;

public class Main {
    public static void main(String[] args) {
        String dateTimeString = "2016-05-04T12:58:22+01:00[Europe/Paris]";
        ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString, ISO_ZONED_DATE_TIME);
        System.out.println(dateTime.toString());
    }
}

can be simply written as

import java.time.ZonedDateTime;

public class Main {
    public static void main(String[] args) {
        String dateTimeString = "2016-05-04T12:58:22+01:00[Europe/Paris]";
        ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString);
        System.out.println(dateTime);
    }
}

Upvotes: 1

spa
spa

Reputation: 5185

The problem is, that 2016-05-04T12:58:22+01:00[Europe/Paris] is not a correct time, as we have CEST (Central European Summer Time, Daylight Saving Time) in May which starts at the last Sunday in March. It is +2h compared to UTC. So indeed 2016-05-04T12:58:22+02:00[Europe/Paris] is correct.

As you said, [Europe/Paris] seems to take precendence. Not sure if there should be an exception in respect to the spec, but I doubt it.

To put it differently 2016-05-04T12:58:22+01:00 can not be in the time zone Europe/Paris.

Upvotes: 6

Related Questions