Reputation: 1058
I'm receiving a UTC timestamp string from an external API, and I need to store it as a LocalDateTime
. In other words, if the timestamp is within a period when daylight saving is active, it should be adjusted to DST (usually by an hour).
I parse the incoming string to an OffsetDateTime
, which I then convert to a ZonedDateTime
, and then to an Instant
. At this point, the DST time is correctly adjusted. But when I create a LocalDateTime
from the Instant
, it loses the adjustment.
@Test
public void testDates() {
final DateTimeFormatter OFFSET_FORMAT = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXX");
final ZoneId zoneId = TimeZone.getDefault().toZoneId();
final String summerTime = "2019-09-11T10:00:00.000+0000";
final String winterTime = "2019-12-11T10:00:00.000+0000";
OffsetDateTime odtSummer = OffsetDateTime.parse(summerTime, OFFSET_FORMAT);
OffsetDateTime odtWinter = OffsetDateTime.parse(winterTime, OFFSET_FORMAT);
ZonedDateTime zdtSummer = odtSummer.toLocalDateTime().atZone(zoneId);
ZonedDateTime zdtWinter = odtWinter.toLocalDateTime().atZone(zoneId);
Instant instSummer = zdtSummer.toInstant();
Instant instWinter = zdtWinter.toInstant();
System.out.println("instSummer = " + instSummer); // instSummer = 2019-09-11T09:00:00Z
System.out.println("instWinter = " + instWinter); // instWinter = 2019-12-11T10:00:00Z
LocalDateTime ldtSummer = LocalDateTime.ofInstant(instSummer, zoneId);
LocalDateTime ldtWinter = LocalDateTime.ofInstant(instWinter, zoneId);
System.out.println("ldtSummer = " + ldtSummer); // ldtSummer = 2019-09-11T10:00
System.out.println("ldtWinter = " + ldtWinter); // ldtWinter = 2019-12-11T10:00
}
How should I do this? I don't want to resort to something ugly like re-parsing Instant.toString()
.
Upvotes: 2
Views: 1302
Reputation: 159086
When you do odtSummer.toLocalDateTime()
, you're discarding the fact that the input date/time is UTC, so you've lost the information right there.
Instead, convert the OffsetDateTime
to a ZonedDateTime
the desired time zone, by calling atZoneSameInstant(zoneId)
.
Then get the LocalDateTime
from that, by calling toLocalDateTime()
.
FYI: Use ZoneId.systemDefault()
to get default time zone, not TimeZone.getDefault().toZoneId()
.
final DateTimeFormatter OFFSET_FORMAT = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXX");
final ZoneId zoneId = ZoneId.of("America/New_York"); // or ZoneId.systemDefault()
final String summerTime = "2019-09-11T10:00:00.000+0000";
final String winterTime = "2019-12-11T10:00:00.000+0000";
OffsetDateTime odtSummer = OffsetDateTime.parse(summerTime, OFFSET_FORMAT); // 2019-09-11T10:00Z
OffsetDateTime odtWinter = OffsetDateTime.parse(winterTime, OFFSET_FORMAT); // 2019-12-11T10:00Z
ZonedDateTime zdtSummer = odtSummer.atZoneSameInstant(zoneId);
ZonedDateTime zdtWinter = odtWinter.atZoneSameInstant(zoneId);
System.out.println("zdtSummer = " + zdtSummer); // 2019-09-11T06:00-04:00[America/New_York]
System.out.println("zdtWinter = " + zdtWinter); // 2019-12-11T05:00-05:00[America/New_York]
LocalDateTime ldtSummer = zdtSummer.toLocalDateTime();
LocalDateTime ldtWinter = zdtWinter.toLocalDateTime();
System.out.println("ldtSummer = " + ldtSummer); // 2019-09-11T06:00
System.out.println("ldtWinter = " + ldtWinter); // 2019-12-11T05:00
Upvotes: 3
Reputation: 1499860
The problem is the way you're converting the inputs to ZonedDateTime
values
ZonedDateTime zdtSummer = odtSummer.toLocalDateTime().atZone(zoneId);
ZonedDateTime zdtWinter = odtWinter.toLocalDateTime().atZone(zoneId);
Here you're saying "take the local date time version of the OffsetDateTime
, and pretend that was actually a local value in the given time zone". So you end up with "10am local time in the time zone" rather than "10am UTC, converted to the local time zone".
You wrote that "At this point, the DST time is correctly adjusted" - but it's not. You started with a value of "2019-09-11T10:00:00.000+0000", but when you print the Instant
it's printing "2019-09-11T09:00:00Z". 10am UTC and 9am UTC are not the same instant.
Instead, you should convert the OffsetDateTime
to an Instant
- as that's what you've really parsed - and then put that in the relevant time zone:
ZonedDateTime zdtSummer = odtSummer.toInstant().atZone(zoneId);
ZonedDateTime zdtWinter = odtWinter.toInstant().atZone(zoneId);
Or use the OffsetDateTime.atZoneSameInstant
ZonedDateTime zdtSummer = odtSummer.atZoneSameInstant(zoneId);
ZonedDateTime zdtWinter = odtSummer.atZoneSameInstant(zoneId);
Note that there's then no point in going from that back to an instant to get the LocalDateTime
- just use toLocalDateTime
. If you want all the relevant types, here's the appropriate code:
OffsetDateTime odtSummer = OffsetDateTime.parse(summerTime, OFFSET_FORMAT);
OffsetDateTime odtWinter = OffsetDateTime.parse(winterTime, OFFSET_FORMAT);
Instant instSummer = odtSummer.toInstant();
Instant instWinter = odtWinter.toInstant();
ZonedDateTime zdtSummer = instSummer.atZone(zoneId);
ZonedDateTime zdtWinter = instWinter.atZone(zoneId);
LocalDateTime ldtSummer = zdtSummer.toLocalDateTime();
LocalDateTime ldtWinter = zdtWinter.toLocalDateTime();
If you don't need the Instant
, just:
OffsetDateTime odtSummer = OffsetDateTime.parse(summerTime, OFFSET_FORMAT);
OffsetDateTime odtWinter = OffsetDateTime.parse(winterTime, OFFSET_FORMAT);
ZonedDateTime zdtSummer = odtSummer.atZoneSameInstant(zoneId);
ZonedDateTime zdtWinter = odtWinter.atZoneSameInstant(zoneId);
LocalDateTime ldtSummer = zdtSummer.toLocalDateTime();
LocalDateTime ldtWinter = zdtWinter.toLocalDateTime();
Upvotes: 5