fix: support java.time.ZonedDateTime serialize/deserialize (#588) - #624
Merged
Merged
Conversation
) Jackson is the default serializer of the agent, and it throws InvalidDefinitionException("Java 8 date/time type `java.time.ZonedDateTime` not supported by default") for every response containing a ZonedDateTime, so those cases can not be recorded at all. Gson had no adapter either and fell back to reflection, writing the internal fields of ZonedDateTime (dateTime/offset/zone) instead of a readable time string. Add ZonedDateTimeAdapter for both serializers, as the other java.time types already do, without introducing the jackson-datatype-jsr310 dependency. The zone id is kept in the serialized value (2020-06-09T09:00:00.000+08:00[Asia/Shanghai]) so that the deserialized value keeps the original zone instead of degrading to a fixed offset, and the offset still disambiguates the local time repeated in a DST overlap. Same as LocalDateTime, nanosecond precision is used on jdk11+. The offset is written as ZZZZZ and the year as uuuu instead of the ZZZ/yyyy used by the patterns of the other types: ZZZ drops the seconds of the offset and the offset overrides the zone when the instant is computed on deserialize, so a zone that still had a sub-minute offset came back shifted (Instant.EPOCH in Africa/Monrovia drifted 30s, and the shift accumulated over record-replay-record), and yyyy is year-of-era, which turned a year before 1 AD into a positive one.
DZQOX
force-pushed
the
fix/588-zoneddatetime-serializer
branch
from
August 22, 2026 15:45
7015bdb to
bf523a6
Compare
|
zwobill
approved these changes
Aug 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #588
What's wrong
Jacksonis the default serializer of the agent, and it has no serializer registered forjava.time.ZonedDateTime, sojackson-databindfalls into its "unsupported JDK type" branch and throws on every response that contains aZonedDateTime:The whole case can not be recorded, not just that one field.
ZonedDateTimeis a very common response field in Spring applications (RespResult#timestamp, audit fields of JHipster-style entities...).Gsondoes not throw, but it has no adapter either, so it serializes the internal fields ofZonedDateTimeinstead of a time string, which is neither readable nor consistent with the other time types:{"dateTime":"2026-08-22 22:58:53.285079907","offset":{"totalSeconds":28800},"zone":{"id":"Asia/Shanghai"}}What this PR does
Adds
ZonedDateTimeAdapterfor both serializers, in the same style as the existingOffsetDateTimeAdapter/LocalDateTimeAdapter, so no new dependency (jackson-datatype-jsr310) is needed — as suggested by @lucas-myx in the issue.Registered in all 5 serializers:
JacksonSerializer,JacksonSerializerWithType,JacksonRequestSerializer,GsonSerializer,GsonRequestSerializer.Serialized form keeps the zone id:
Three details worth reviewing:
The zone id is kept (
'['VV']'), instead of only the offset likeOffsetDateTime. OtherwiseZoneId.of("Asia/Shanghai")comes back as the fixed offset+08:00after the mock is deserialized, and application code that formats/queries the zone would behave differently during replay. The offset is still written, so a local time that appears twice in a DST overlap is restored to the correct instant.Nanosecond precision on jdk11+, the same as
TimePatternConstants.localDateTimeFormatdoes forLocalDateTime, so that the round trip is lossless.The offset is
ZZZZZand the year isuuuu, which differs from theZZZ/yyyyof the patterns used by the other time types. This is deliberate, the two shorter forms lose data here:ZZZrenders+HHMMand drops the seconds of the offset. On deserialize the offset overrides the zone when the instant is computed, and the zone then re-derives the local time, so a zone that still had a sub-minute offset comes back shifted and the shift accumulates over record → replay → record.Africa/Monroviawas-00:44:30until 1972, so plainInstant.EPOCH.atZone(...)drifts by 30s;ZoneOffset.ofTotalSeconds(8 * 3600 + 30)reproduces it on a present-day date.yyyyis year-of-era with no era field in the pattern, so a year before 1 AD silently comes back as a positive one (-100→101).Both are covered by the parameterized test.
Tests
JacksonSerializerTest#testZonedDateTime/GsonSerializerTest#testZonedDateTime: round trip + zone id is not degraded to an offset + the re-serialized json is identical.JacksonSerializerTest#testZonedDateTimeSerializedFormat: pins the serialized bytes of both patterns literally, and asserts the jdk8/jdk11 branch stays bound to them. The serialized form is the contract between record and replay, a round-trip-only test would let a self-consistent format change ship silently, and CI never runs the tests on jdk8.GsonSerializerTest#testZonedDateTimeSameFormatAsJackson: the two serializers must emit the same bytes, a case can be recorded with one and replayed with the other.zonedDateTimeadded toTimeTestInfo/ZeroSecondTimeTestInfo, which covers the existing serialize/deserialize, with-type and request(denoising) test flows.Known limitation, not introduced here
Mock data recorded on jdk8 can not be replayed on jdk11+ and vice versa, because the pattern switches between
.SSSand.SSSSSSSSSand java.time parsesSwith an exact digit count. This is pre-existing behaviour shared withLocalDateTimeandLocalTime, which switch the same way, so fixing it means giving all of them a variable width fraction (appendFraction(NANO_OF_SECOND, 0, 9, true)) and is left out of this PR.