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:
- 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.
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);
}
}