From 08b5f33ee628b10c3ec51fb520544bc328977bfb Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Mon, 5 May 2025 01:04:33 +0900 Subject: [PATCH 1/6] wip --- .../joda/cfg/JacksonJodaDateFormat.java | 22 +++++++++ .../DateTimeOwnZoneSerialization92Test.java | 48 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java diff --git a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java index 60ed3b41..3a9c4e1e 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java +++ b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java @@ -248,6 +248,28 @@ public DateTimeFormatter createFormatter(SerializerProvider ctxt) return formatter; } + /** + * Creates a formatter with the specified timezone from the value if any. + * + * [dataformat-joda#92] DateTime serialization result is not same as Java 8 ZonedDateTime + * + * @since 2.19.1 + */ + public DateTimeFormatter createFormatter(SerializerProvider ctxt, DateTimeZone valueTimeZone) + { + DateTimeFormatter formatter = createFormatterWithLocale(ctxt); + if (!_explicitTimezone) { + TimeZone tz = ctxt.getTimeZone(); + if ((tz != null) && !tz.equals(_jdkTimezone)) { + formatter = formatter.withZone(DateTimeZone.forTimeZone(tz)); + } + } + if (valueTimeZone != null && !valueTimeZone.equals(_jdkTimezone)) { + formatter = formatter.withZone(valueTimeZone); + } + return formatter; + } + public DateTimeFormatter createFormatterWithLocale(SerializerProvider ctxt) { DateTimeFormatter formatter = _formatter; diff --git a/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java new file mode 100644 index 00000000..25f4f3fd --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java @@ -0,0 +1,48 @@ +package com.fasterxml.jackson.datatype.joda.ser; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.joda.JodaTestBase; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Reproduces jackson-datatype-joda#92: + * DateTime loses its own zone during serialization (normalised to UTC), + * so the JSON differs from Java 8 ZonedDateTime output. + * + * Expected (zone kept): "2017‑01‑01T01:01:01.000+08:00" + * Actual (current bug): "2016‑12‑31T17:01:01.000Z" + */ +public class DateTimeOwnZoneSerialization92Test + extends JodaTestBase +{ + // Wrapper matches style used in other failing tests + static class Wrapper { + @JsonFormat(shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS ZZZ") // default ISO pattern + public DateTime value; + Wrapper(DateTime v) { value = v; } + } + + private final ObjectMapper MAPPER = mapperWithModuleBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // string mode + .build(); + + @Test + public void dateTimeShouldRetainItsOwnZone() throws Exception { + DateTime europe = new DateTime( + 2017, 1, 1, 12, 1, 1, 0, + DateTimeZone.forID("Europe/Budapest") // UTC+8 + ); + + String actual = MAPPER.writeValueAsString(new Wrapper(europe)); + + // This assertion FAILS today, demonstrating the bug + assertEquals("{\"value\":\"2017-01-01T11:01:01.000 Europe/Budapest\"}", actual); + } +} \ No newline at end of file From 0b21900116b847ec4e5e5a2d21149e6ced655890 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Mon, 5 May 2025 02:17:58 +0900 Subject: [PATCH 2/6] Clean up behavior --- .../joda/cfg/JacksonJodaDateFormat.java | 12 ++--- .../datatype/joda/ser/DateTimeSerializer.java | 2 +- .../DateTimeOwnZoneSerialization92Test.java | 54 ++++++++----------- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java index 3a9c4e1e..aa830c18 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java +++ b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java @@ -236,16 +236,12 @@ public DateTimeFormatter rawFormatter() { return _formatter; } + /** + * @deprecated since 2.19.1 Use {@link #createFormatter(SerializerProvider, DateTimeZone)} instead + */ public DateTimeFormatter createFormatter(SerializerProvider ctxt) { - DateTimeFormatter formatter = createFormatterWithLocale(ctxt); - if (!_explicitTimezone) { - TimeZone tz = ctxt.getTimeZone(); - if ((tz != null) && !tz.equals(_jdkTimezone)) { - formatter = formatter.withZone(DateTimeZone.forTimeZone(tz)); - } - } - return formatter; + return createFormatter(ctxt, null); } /** diff --git a/src/main/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeSerializer.java b/src/main/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeSerializer.java index be2ffbf2..788b35a8 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeSerializer.java +++ b/src/main/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeSerializer.java @@ -52,7 +52,7 @@ public void serialize(DateTime value, JsonGenerator gen, SerializerProvider prov if (numeric) { gen.writeNumber(value.getMillis()); } else { - gen.writeString(_format.createFormatter(provider).print(value)); + gen.writeString(_format.createFormatter(provider, value.getZone()).print(value)); } } else { // and then as per [datatype-joda#44], optional TimeZone inclusion diff --git a/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java index 25f4f3fd..07621f2c 100644 --- a/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java +++ b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java @@ -1,48 +1,40 @@ package com.fasterxml.jackson.datatype.joda.ser; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.joda.JodaTestBase; +import java.util.TimeZone; + import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.joda.JodaTestBase; + import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Reproduces jackson-datatype-joda#92: - * DateTime loses its own zone during serialization (normalised to UTC), - * so the JSON differs from Java 8 ZonedDateTime output. - * - * Expected (zone kept): "2017‑01‑01T01:01:01.000+08:00" - * Actual (current bug): "2016‑12‑31T17:01:01.000Z" - */ +// [dataformat-joda#92] DateTime serialization result is not same as Java 8 ZonedDateTime public class DateTimeOwnZoneSerialization92Test - extends JodaTestBase + extends JodaTestBase { - // Wrapper matches style used in other failing tests - static class Wrapper { - @JsonFormat(shape = JsonFormat.Shape.STRING, - pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS ZZZ") // default ISO pattern - public DateTime value; - Wrapper(DateTime v) { value = v; } - } - - private final ObjectMapper MAPPER = mapperWithModuleBuilder() - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // string mode - .build(); + private final ObjectMapper MAPPER = mapperWithModuleBuilder().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build(); @Test public void dateTimeShouldRetainItsOwnZone() throws Exception { - DateTime europe = new DateTime( - 2017, 1, 1, 12, 1, 1, 0, - DateTimeZone.forID("Europe/Budapest") // UTC+8 - ); + DateTime jodaZonedDateTime = new DateTime(2023, 10, 1, 12, 2, 3, 123, DateTimeZone.forID("Asia/Shanghai")); - String actual = MAPPER.writeValueAsString(new Wrapper(europe)); + // with WRITE_DATES_WITH_CONTEXT_TIME_ZONE + assertEquals("\"2023-10-01T12:02:03.123+08:00\"", + MAPPER.writer() + .with(TimeZone.getTimeZone("UTC")) + .with(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE) + .writeValueAsString(jodaZonedDateTime)); - // This assertion FAILS today, demonstrating the bug - assertEquals("{\"value\":\"2017-01-01T11:01:01.000 Europe/Budapest\"}", actual); + // without WRITE_DATES_WITH_CONTEXT_TIME_ZONE + assertEquals("\"2023-10-01T12:02:03.123+08:00\"", + MAPPER.writer() + .with(TimeZone.getTimeZone("UTC")) + .without(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE) + .writeValueAsString(jodaZonedDateTime)); } + } \ No newline at end of file From 49b05a1b92762b785023b0dc80cb487f37a75452 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Mon, 5 May 2025 02:34:04 +0900 Subject: [PATCH 3/6] FIx serializationfeature overwrite --- .../datatype/joda/cfg/JacksonJodaDateFormat.java | 6 ++++-- .../ser/DateTimeOwnZoneSerialization92Test.java | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java index aa830c18..7b8e5b83 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java +++ b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java @@ -260,8 +260,10 @@ public DateTimeFormatter createFormatter(SerializerProvider ctxt, DateTimeZone v formatter = formatter.withZone(DateTimeZone.forTimeZone(tz)); } } - if (valueTimeZone != null && !valueTimeZone.equals(_jdkTimezone)) { - formatter = formatter.withZone(valueTimeZone); + if (!ctxt.isEnabled(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE)) { + if (valueTimeZone != null && !valueTimeZone.equals(_jdkTimezone)) { + formatter = formatter.withZone(valueTimeZone); + } } return formatter; } diff --git a/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java index 07621f2c..5f46632d 100644 --- a/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java +++ b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeOwnZoneSerialization92Test.java @@ -22,19 +22,19 @@ public class DateTimeOwnZoneSerialization92Test public void dateTimeShouldRetainItsOwnZone() throws Exception { DateTime jodaZonedDateTime = new DateTime(2023, 10, 1, 12, 2, 3, 123, DateTimeZone.forID("Asia/Shanghai")); - // with WRITE_DATES_WITH_CONTEXT_TIME_ZONE - assertEquals("\"2023-10-01T12:02:03.123+08:00\"", - MAPPER.writer() - .with(TimeZone.getTimeZone("UTC")) - .with(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE) - .writeValueAsString(jodaZonedDateTime)); - // without WRITE_DATES_WITH_CONTEXT_TIME_ZONE assertEquals("\"2023-10-01T12:02:03.123+08:00\"", MAPPER.writer() .with(TimeZone.getTimeZone("UTC")) .without(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE) .writeValueAsString(jodaZonedDateTime)); + + // with WRITE_DATES_WITH_CONTEXT_TIME_ZONE + assertEquals("\"2023-10-01T04:02:03.123Z\"", + MAPPER.writer() + .with(TimeZone.getTimeZone("UTC")) + .with(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE) + .writeValueAsString(jodaZonedDateTime)); } } \ No newline at end of file From 7b1fd64658efacd35ba0e09d8b2daa1d3cf65d99 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Mon, 5 May 2025 02:40:13 +0900 Subject: [PATCH 4/6] Update DateTimeSerializationWithOffsets146Test.java --- .../DateTimeSerializationWithOffsets146Test.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) rename src/test/java/com/fasterxml/jackson/datatype/joda/{tofix => ser}/DateTimeSerializationWithOffsets146Test.java (79%) diff --git a/src/test/java/com/fasterxml/jackson/datatype/joda/tofix/DateTimeSerializationWithOffsets146Test.java b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeSerializationWithOffsets146Test.java similarity index 79% rename from src/test/java/com/fasterxml/jackson/datatype/joda/tofix/DateTimeSerializationWithOffsets146Test.java rename to src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeSerializationWithOffsets146Test.java index 151733ca..7fcec28e 100644 --- a/src/test/java/com/fasterxml/jackson/datatype/joda/tofix/DateTimeSerializationWithOffsets146Test.java +++ b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/DateTimeSerializationWithOffsets146Test.java @@ -1,26 +1,25 @@ -package com.fasterxml.jackson.datatype.joda.tofix; +package com.fasterxml.jackson.datatype.joda.ser; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.joda.JodaTestBase; -import com.fasterxml.jackson.datatype.joda.testutil.failure.JacksonTestFailureExpected; import org.joda.time.*; -// [datatype-joda#146]: disable overwriting of timezone import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -public class DateTimeSerializationWithOffsets146Test extends JodaTestBase +// [datatype-joda#146]: disable overwriting of timezone +public class DateTimeSerializationWithOffsets146Test + extends JodaTestBase { // [datatype-joda#146] - @JacksonTestFailureExpected @Test public void testLocalDateSerDefault() throws Exception { - final String inputStr = "2024-12-01T12:00:00+02:00"; + final String inputStr = "2024-12-01T12:00:00.000+02:00"; final DateTime inputValue = DateTime.parse(inputStr); final ObjectMapper mapper = mapperWithModuleBuilder() .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) From d7ecc3748ad26c14d79bc08fdaa82c40d9d4b3da Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 5 May 2025 18:39:04 -0700 Subject: [PATCH 5/6] Add release notes, update javadocs --- release-notes/CREDITS-2.x | 5 +++++ release-notes/VERSION-2.x | 5 +++++ .../jackson/datatype/joda/cfg/JacksonJodaDateFormat.java | 5 +++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 9f20c8f0..30cc2236 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -123,3 +123,8 @@ Joo Hyuk Kim (@JooHyukKim) (2.18.4) * Fixed #98: `JsonFormat` timezone attribute effect overwritten if pattern attribute present (2.19.1) + * Fixed #92: `DateTime` serialization result is not same as Java 8 `ZonedDateTime` + (2.20.0) + * Fixed #146: `DateTime` can't be serialized with its own zone + (2.20.0) + diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index a6f5871e..5bc4fb1b 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -6,6 +6,11 @@ Project: jackson-datatype-joda 2.20.0 (not yet released) +#92: `DateTime` serialization result is not same as Java 8 `ZonedDateTime` + (fixed by Joo-Hyuk K) +#146: `DateTime` can't be serialized with its own zone + (`WRITE_DATES_WITH_CONTEXT_TIME_ZONE` not respected) + (fixed by Joo-Hyuk K) - Generate SBOMs [JSTEP-14] 2.19.1 (not yet released) diff --git a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java index b09c0da8..a33b4632 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java +++ b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java @@ -243,8 +243,9 @@ public DateTimeFormatter rawFormatter() { } /** - * @deprecated since 2.19.1 Use {@link #createFormatter(SerializerProvider, DateTimeZone)} instead + * @deprecated since 2.20 Use {@link #createFormatter(SerializerProvider, DateTimeZone)} instead */ + @Deprecated // since 2.20 public DateTimeFormatter createFormatter(SerializerProvider ctxt) { return createFormatter(ctxt, null); @@ -255,7 +256,7 @@ public DateTimeFormatter createFormatter(SerializerProvider ctxt) * * [dataformat-joda#92] DateTime serialization result is not same as Java 8 ZonedDateTime * - * @since 2.19.1 + * @since 2.20 */ public DateTimeFormatter createFormatter(SerializerProvider ctxt, DateTimeZone valueTimeZone) { From f0bc7a2b3d2d3c29ad8ffdf735435a6f59f7977f Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Tue, 6 May 2025 13:41:03 +0900 Subject: [PATCH 6/6] Null check and valid type comparison Apply review --- .../jackson/datatype/joda/cfg/JacksonJodaDateFormat.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java index a33b4632..b24b646d 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java +++ b/src/main/java/com/fasterxml/jackson/datatype/joda/cfg/JacksonJodaDateFormat.java @@ -268,7 +268,8 @@ public DateTimeFormatter createFormatter(SerializerProvider ctxt, DateTimeZone v } } if (!ctxt.isEnabled(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE)) { - if (valueTimeZone != null && !valueTimeZone.equals(_jdkTimezone)) { + if ((valueTimeZone != null) + && ((_jdkTimezone == null) || !valueTimeZone.toTimeZone().equals(_jdkTimezone))) { formatter = formatter.withZone(valueTimeZone); } }