Daylight savings
Many countries in the world adopt what is called Daylight Saving Time (DST), the practice of advance the clock by an hour in the summer (well, not exactly in the summer in all countries but let's bear with this) when the daylight savings time starts.
When the daylight time ends, clocks are set back by an hour. This is done to make better use of natural daylight.
ZonedDateTime is fully aware of DST.
For an example, let's take a country where DST is fully observed, like Italy (UTC/GMT +2).
In 2015, DST started in Italy on March, 29th and ended on October, 25th. This means that on:
March, 29 2015 at 2:00:00 A.M. clocks were turned forward 1 hour to
March, 29 2015 at 3:00:00 A.M. local daylight time instead
(So a time like March, 29 2015 2:30:00 A.M. didn't actually exist!)
October, 25 2015 at 3:00:00 A.M. clocks were turned backward 1 hour to
October, 25 2015 at 2:00:00 A.M. local daylight time instead
(So a time like October, 25 2015 2:30:00 A.M. actually existed twice!)
If we create an instance of LocalDateTime with this date/time and print it:
LocalDateTime ldt = LocalDateTime.of(2015, 3, 29, 2, 30);
System.out.println(ldt);
The result will be:
2015-03-29T02:30
But if we create an instance of ZonedDateTime for Italy (notice that the format uses a city, not a country) and printed:
ZonedDateTime zdt = ZonedDateTime.of(
2015, 3, 29, 2, 30, 0, 0, ZoneId.of("Europe/Rome"));
System.out.println(zdt);
The result will be just like in the real world when using DST:
2015-03-29T03:30+02:00[Europe/Rome]
But be careful. We have to use a regional ZoneId, a ZoneOffset won't do the trick because this class doesn't have the zone rules information to account for DST:
ZonedDateTime zdt1 = ZonedDateTime.of(
2015, 3, 29, 2, 30, 0, 0, ZoneOffset.ofHours(2));
ZonedDateTime zdt2 = ZonedDateTime.of(
2015, 3, 29, 2, 30, 0, 0, ZoneId.of("UTC+2"));
System.out.println(zdt1); System.out.println(zdt2);
The result will be:
2015-03-29T02:30+02:00[UTC+02:00] 2015-03-29T02:30+02:00
When we create an instance of ZonedDateTime for Italy, we have to add an hour to see the effect (otherwise we we'll be creating the ZonedDateTime at 3:00 of the new time):
ZonedDateTime zdt = ZonedDateTime.of(
2015, 10, 25, 2, 30, 0, 0, ZoneId.of("Europe/Rome"));
ZonedDateTime zdt2 = zdt.plusHours(1);
System.out.println(zdt);
System.out.println(zdt2);
The result will be:
2015-10-25T02:30+02:00[Europe/Rome] 2015-10-25T02:30+01:00[Europe/Rome]
We also need to be careful when adjusting the time across the DST boundary with a version of the methods plus() and minus() that takes a TemporalAmount implementation, in other words, a Period or a Duration. This is because both differ in their treatment of daylight savings time.
Consider one hour before the beginning of DST in Italy:
ZonedDateTime zdt = ZonedDateTime.of(
2015, 3, 29, 1, 0, 0, 0, ZoneId.of("Europe/Rome"));
When we add a Duration of one day:
System.out.println(zdt.plus(Duration.ofDays(1)));
The result is:
2015-03-30T02:00+02:00[Europe/Rome]
When we add a Period of one day:
System.out.println(zdt.plus(Period.ofDays(1)));
The result is:
2015-03-30T01:00+02:00[Europe/Rome]
The reason is that Period adds a conceptual date, while Duration adds exactly one day (24 hours or 86, 400 seconds) and when it crosses the DST boundary, one hour is added, and the final time is 02:00 instead of 01:00.
=======================================================================
Java 8 introduce java.time.LocalDateTime:
LocalDateTime localDateTimeBeforeDST = LocalDateTime
.of(2018, 3, 25, 1, 55);
assertThat(localDateTimeBeforeDST.toString())
.isEqualTo("2018-03-25T01:55");
We can observe how a LocalDateTime is conforming to the ISO-8601 rules.
It's completely unaware of Zones and Offsets, though, that's why we need to convert it into a fully DST-aware java.time.ZonedDateTime:
ZoneId italianZoneId = ZoneId.of("Europe/Rome");
ZonedDateTime zonedDateTimeBeforeDST = localDateTimeBeforeDST
.atZone(italianZoneId);
assertThat(zonedDateTimeBeforeDST.toString())
.isEqualTo("2018-03-25T01:55+01:00[Europe/Rome]");
As we can see, now the date incorporates two fundamental trailing pieces of information: +01:00 is the ZoneOffset, while [Europe/Rome] is the ZoneId.
let's trigger DST through the addition of ten minutes:
ZonedDateTime zonedDateTimeAfterDST = zonedDateTimeBeforeDST
.plus(10, ChronoUnit.MINUTES);
assertThat(zonedDateTimeAfterDST.toString())
.isEqualTo("2018-03-25T03:05+02:00[Europe/Rome]");
Again, we see how both the time and the zone offset are shifting forward, and still keeping the same distance:
Long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES
.between(zonedDateTimeBeforeDST,zonedDateTimeAfterDST);
assertThat(deltaBetweenDatesInMinutes)
.isEqualTo(10);