From fb821d7c51822cb87dceb09dceb2388e56ac0497 Mon Sep 17 00:00:00 2001 From: Abdullah <89297042+AzazelSensei@users.noreply.github.com> Date: Mon, 21 Sep 2026 01:20:54 +0000 Subject: [PATCH 1/2] Document implicit UTC vs ZonedDateTime serialization #303 ObjectMapper default TimeZone is UTC but hasExplicitTimeZone() stays false until setTimeZone is called, so ZonedDateTime keeps its own zone unless WRITE_DATES_WITH_CONTEXT_TIME_ZONE has an explicit timezone. --- datetime/README.md | 2 ++ .../jackson/datatype/jsr310/JavaTimeModule.java | 9 +++++++++ .../datatype/jsr310/ser/InstantSerializerBase.java | 9 +++++++++ 3 files changed, 20 insertions(+) diff --git a/datetime/README.md b/datetime/README.md index 3892400e..6b5ea593 100644 --- a/datetime/README.md +++ b/datetime/README.md @@ -37,6 +37,8 @@ For TimeZone handling, `ADJUST_DATES_TO_CONTEXT_TIME_ZONE` (default: true) speci 'SerializedProvider#getTimeZone()' should be used to adjust Date/Time values on deserialization, even if the value itself contains timezone information. The resultant ZoneId will be [normalized](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#normalized--) where applicable. If the value is `OffsetDateTime.MIN` or `OffsetDateTime.MAX`, the Date/Time value will not be adjusted. If disabled, it will only be used if the value itself does not contain any TimeZone information. +On serialization, `ObjectMapper`'s default TimeZone is UTC, but that default is **implicit** (`SerializationConfig.hasExplicitTimeZone()` is false until `ObjectMapper.setTimeZone(...)` or `ObjectWriter.with(TimeZone)` is called). `ZonedDateTime` and `OffsetDateTime` therefore keep the zone already on the value when written as ISO-8601 strings. `SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE` (enabled by default since 2.13) only converts to the mapper TimeZone when that timezone was set explicitly. Calling `mapper.setTimeZone(TimeZone.getTimeZone("UTC"))` is enough to make UTC take effect for those types. + Finally, there are two features that apply to array handling. `UNWRAP_SINGLE_VALUE_ARRAYS` (default: false) allows auto-conversion from single-element arrays to non-JSON-array values. If the JSON value contains more than one element in the array, deserialization will still fail. `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` (default: false) determines whether empty Array value ("[ ]" in JSON) is accepted as null value for regular POJOs ("beans") with data-binding diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java index 80ede099..46943009 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java @@ -61,6 +61,15 @@ * local dates and times, periods, durations, zones, and more. All {@code java.time} types * have built-in translation to and from ISO-8601 formats. *
+ * On serialization, {@code ObjectMapper}'s default TimeZone is UTC, but that + * default is implicit ({@code SerializationConfig.hasExplicitTimeZone()} is + * false until {@code ObjectMapper.setTimeZone} or {@code ObjectWriter.with(TimeZone)} + * is called). {@link ZonedDateTime} and {@link OffsetDateTime} therefore keep + * the zone already on the value when written as ISO-8601 strings. + * {@link com.fasterxml.jackson.databind.SerializationFeature#WRITE_DATES_WITH_CONTEXT_TIME_ZONE} + * (enabled by default since 2.13) only converts to the mapper TimeZone when + * that timezone was set explicitly. + *
* Granularity of timestamps is controlled through the companion features * {@link com.fasterxml.jackson.databind.SerializationFeature#WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS} and * {@link com.fasterxml.jackson.databind.DeserializationFeature#READ_DATE_TIMESTAMPS_AS_NANOSECONDS}. For serialization, timestamps are diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java index 81c3ebd8..2f986ff5 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java @@ -42,6 +42,15 @@ /** * Base class for serializers used for {@link java.time.Instant} and * other {@link Temporal} subtypes. + *
+ * When writing ISO-8601 strings, the mapper {@link java.util.TimeZone} is
+ * applied only if it was set explicitly
+ * ({@code SerializationConfig.hasExplicitTimeZone()} is true) and
+ * {@link com.fasterxml.jackson.databind.SerializationFeature#WRITE_DATES_WITH_CONTEXT_TIME_ZONE}
+ * is enabled (the default since 2.13). {@code ObjectMapper}'s documented
+ * default of UTC is implicit until {@code setTimeZone} or
+ * {@code ObjectWriter.with(TimeZone)} is called, so {@link java.time.ZonedDateTime}
+ * keeps the zone already on the value.
*/
@SuppressWarnings("serial")
public abstract class InstantSerializerBase
- * On serialization, {@code ObjectMapper}'s default TimeZone is UTC, but that
- * default is implicit ({@code SerializationConfig.hasExplicitTimeZone()} is
- * false until {@code ObjectMapper.setTimeZone} or {@code ObjectWriter.with(TimeZone)}
- * is called). {@link ZonedDateTime} and {@link OffsetDateTime} therefore keep
- * the zone already on the value when written as ISO-8601 strings.
- * {@link com.fasterxml.jackson.databind.SerializationFeature#WRITE_DATES_WITH_CONTEXT_TIME_ZONE}
- * (enabled by default since 2.13) only converts to the mapper TimeZone when
- * that timezone was set explicitly.
+ * Note that {@code ObjectMapper}'s default TimeZone of UTC is implicit, and as such
+ * does not override the zone already on {@link ZonedDateTime} and {@link OffsetDateTime}
+ * values written as ISO-8601 Strings: see
+ * {@link com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializerBase} for details.
*
* Granularity of timestamps is controlled through the companion features
* {@link com.fasterxml.jackson.databind.SerializationFeature#WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS} and
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java
index 2f986ff5..c33706f9 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java
@@ -43,14 +43,23 @@
* Base class for serializers used for {@link java.time.Instant} and
* other {@link Temporal} subtypes.
*
- * When writing ISO-8601 strings, the mapper {@link java.util.TimeZone} is
- * applied only if it was set explicitly
- * ({@code SerializationConfig.hasExplicitTimeZone()} is true) and
- * {@link com.fasterxml.jackson.databind.SerializationFeature#WRITE_DATES_WITH_CONTEXT_TIME_ZONE}
- * is enabled (the default since 2.13). {@code ObjectMapper}'s documented
- * default of UTC is implicit until {@code setTimeZone} or
- * {@code ObjectWriter.with(TimeZone)} is called, so {@link java.time.ZonedDateTime}
- * keeps the zone already on the value.
+ * When writing ISO-8601 Strings, the context {@link java.util.TimeZone} is applied
+ * only if all of the following hold:
+ *
+ *
+ * Otherwise the zone (or offset) already on the value being written is retained as-is.
*/
@SuppressWarnings("serial")
public abstract class InstantSerializerBase