Reputation: 2845
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
Reputation: 79075
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 aZoneId
. TheZoneOffset
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:
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.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
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