Skip to content

Keep the later offset of a ZonedDateTime in an overlap - #1532

Merged
theigl merged 1 commit into
EsotericSoftware:kryo-6from
CCCLL:fix-zoned-date-time-overlap
Oct 20, 2026
Merged

theigl merged 1 commit into
EsotericSoftware:kryo-6from
CCCLL:fix-zoned-date-time-overlap

Conversation

@CCCLL

@CCCLL CCCLL commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #1531.

ZonedDateTimeSerializer wrote the date, time and zone of a ZonedDateTime and read it with ZonedDateTime.of, which takes the earlier offset in an overlap, when clocks are set back. A ZonedDateTime with the later offset, eg 2026-11-01T01:30-05:00[America/New_York], was read as 2026-11-01T01:30-04:00[America/New_York], an instant one hour earlier, without an exception.

Like for locales with a script or extensions (#1334, #1525), a null zone ID, which Kryo 5 never wrote, now marks an offset. It is written before the zone ID only for a ZonedDateTime with the later offset in an overlap. The offset is read with ZonedDateTime.ofLocal, which uses it if it is valid for the date and time in the zone, and else the earlier offset as before, eg if the zone's rules changed. Other ZonedDateTimes are written as before, so Kryo 6 reads all ZonedDateTimes written by Kryo 5 and the golden files are unchanged. With Format.KRYO5, they are written like Kryo 5 wrote them, without the offset. MIGRATION.md has an entry.

To find out if a ZonedDateTime has the later offset, writing it now looks up the zone's transition for its date and time. In a rough loop on JDK 25, writing a ZonedDateTime with a region zone took about 31 ns instead of 21 ns. Reading is unchanged. Always writing the offset would avoid the lookup, but it would make every ZonedDateTime longer and still need a marker to read the golden files.

Tests: TimeSerializersTest.testZonedDateTimeOverlap round trips both offsets of an overlap and checks that an offset that is not valid for the date and time is not used. Kryo5RoundTripTest.testZonedDateTimeLaterOffsetAtOverlap checks that the Kryo 5 format writes the same bytes as Kryo 5 and that Kryo 5 reads them. The tests fail without the fix, and also when the format check, the read of the offset or the check for the later offset is removed.

I also checked all zones, around every transition from 1900 to 2100 and at random instants, 838,235 values with Output and ByteBufferOutput, 117,381 of them with the later offset in an overlap: Kryo 6 round trips each value, writes the same bytes as Kryo 5 except for the later offsets, and writes the same bytes as Kryo 5 with the Kryo 5 format.

mvn -pl main test passes on JDK 17, 21 and 25 (built with JDK 25), including the Android API check. mvn -pl main formatter:validate passes.

master has the same bug, but the fix changes the format, so this is for kryo-6 only.

ZonedDateTimeSerializer wrote the date, time and zone of a ZonedDateTime
and read it with ZonedDateTime.of, which takes the earlier offset in an
overlap, when clocks are set back. A ZonedDateTime with the later offset,
eg 2026-11-01T01:30-05:00[America/New_York], was read with the earlier
one, -04:00, an instant one hour earlier.

The offset of such a ZonedDateTime is written now too, after a null zone
ID, which Kryo 5 never wrote, and read with ZonedDateTime.ofLocal, which
uses it if it is valid. Other ZonedDateTimes are written as before, so
Kryo 6 reads all ZonedDateTimes written by Kryo 5. With the Kryo 5
format, they are written like Kryo 5 wrote them.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No unresolved blocking issues were identified.

No open issues 🎉

What changed in this PR

Preserves the later offset when serializing ZonedDateTime values in overlaps while maintaining Kryo 5 compatibility.

Changes:

  • Adds overlap-offset preservation for Kryo 6.
  • Adds round-trip and compatibility tests.
  • Documents the format change.
File Description
test/​com/​esotericsoftware/​kryo/​serializers/​TimeSerializersTest.java Tests overlap round trips and invalid offsets.
test/​com/​esotericsoftware/​kryo/​Kryo5RoundTripTest.java Verifies Kryo 5 format compatibility.
src/​com/​esotericsoftware/​kryo/​serializers/​TimeSerializers.java Preserves later overlap offsets.
MIGRATION.md Documents the format and behavior change.

Review effort: Lite

@theigl
theigl merged commit 2423734 into EsotericSoftware:kryo-6 Oct 20, 2026
4 checks passed
@theigl

theigl commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Thanks again @CCCLL!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants