diff --git a/release-notes/VERSION b/release-notes/VERSION index fee9799a..8b1962e3 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -6,6 +6,8 @@ Project: jackson-datatype-joda 2.8.0 (not yet released) +#83: WRITE_DATES_WITH_ZONE_ID feature not working when applied on @JsonFormat annotation + (reported by mandyWW@github) #87: Add support for JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE (contributed by Alexey B) - Support "config overrides" for `@JsonFormat.Value` added in databind 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 215b1fd4..802e3316 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 @@ -7,8 +7,10 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializerProvider; /** @@ -27,8 +29,20 @@ public class JacksonJodaDateFormat extends JacksonJodaFormatBase protected final boolean _explicitTimezone; + /** + * Flag for JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE + * + * @since 2.8 + */ protected final Boolean _adjustToContextTZOverride; + /** + * Flag for JsonFormat.Feature.WRITE_DATES_WITH_ZONE_ID + * + * @since 2.8 + */ + protected final Boolean _writeZoneId; + public JacksonJodaDateFormat(DateTimeFormatter defaultFormatter) { super(); @@ -37,16 +51,29 @@ public JacksonJodaDateFormat(DateTimeFormatter defaultFormatter) _jdkTimezone = (tz == null) ? null : tz.toTimeZone(); _explicitTimezone = false; _adjustToContextTZOverride = null; + _writeZoneId = null; } public JacksonJodaDateFormat(JacksonJodaDateFormat base, - Boolean useTimestamp, Boolean adjustToContextTZOverride) + Boolean useTimestamp) { super(base, useTimestamp); _formatter = base._formatter; _jdkTimezone = base._jdkTimezone; _explicitTimezone = base._explicitTimezone; + _adjustToContextTZOverride = base._adjustToContextTZOverride; + _writeZoneId = base._writeZoneId; + } + + public JacksonJodaDateFormat(JacksonJodaDateFormat base, + Boolean adjustToContextTZOverride, Boolean writeZoneId) + { + super(base); + _formatter = base._formatter; + _jdkTimezone = base._jdkTimezone; + _explicitTimezone = base._explicitTimezone; _adjustToContextTZOverride = adjustToContextTZOverride; + _writeZoneId = writeZoneId; } public JacksonJodaDateFormat(JacksonJodaDateFormat base, @@ -57,6 +84,7 @@ public JacksonJodaDateFormat(JacksonJodaDateFormat base, _jdkTimezone = base._jdkTimezone; _explicitTimezone = base._explicitTimezone; _adjustToContextTZOverride = base._adjustToContextTZOverride; + _writeZoneId = base._writeZoneId; } public JacksonJodaDateFormat(JacksonJodaDateFormat base, TimeZone jdkTimezone) @@ -66,6 +94,7 @@ public JacksonJodaDateFormat(JacksonJodaDateFormat base, TimeZone jdkTimezone) _jdkTimezone = jdkTimezone; _explicitTimezone = true; _adjustToContextTZOverride = base._adjustToContextTZOverride; + _writeZoneId = base._writeZoneId; } public JacksonJodaDateFormat(JacksonJodaDateFormat base, Locale locale) @@ -75,6 +104,7 @@ public JacksonJodaDateFormat(JacksonJodaDateFormat base, Locale locale) _jdkTimezone = base._jdkTimezone; _explicitTimezone = base._explicitTimezone; _adjustToContextTZOverride = base._adjustToContextTZOverride; + _writeZoneId = base._writeZoneId; } /* @@ -83,11 +113,25 @@ public JacksonJodaDateFormat(JacksonJodaDateFormat base, Locale locale) /********************************************************** */ + public JacksonJodaDateFormat with(JsonFormat.Value ann) { + JacksonJodaDateFormat format = this; + format = format.withLocale(ann.getLocale()); + format = format.withTimeZone(ann.getTimeZone()); + format = format.withFormat(ann.getPattern().trim()); + Boolean adjustTZ = ann.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + Boolean writeZoneId = ann.getFeature(JsonFormat.Feature.WRITE_DATES_WITH_ZONE_ID); + if ((adjustTZ != _adjustToContextTZOverride) + || (writeZoneId != _writeZoneId)) { + format = new JacksonJodaDateFormat(format, adjustTZ, writeZoneId); + } + return format; + } + public JacksonJodaDateFormat withUseTimestamp(Boolean useTimestamp) { if ((_useTimestamp != null) && _useTimestamp.equals(useTimestamp)) { return this; } - return new JacksonJodaDateFormat(this, useTimestamp, _adjustToContextTZOverride); + return new JacksonJodaDateFormat(this, useTimestamp); } public JacksonJodaDateFormat withFormat(String format) { @@ -121,12 +165,26 @@ public JacksonJodaDateFormat withLocale(Locale locale) { return new JacksonJodaDateFormat(this, locale); } + /** + * @since 2.8 + */ public JacksonJodaDateFormat withAdjustToContextTZOverride(Boolean adjustToContextTZOverride) { // minor efficiency check to avoid recreation if no change: if (adjustToContextTZOverride == _adjustToContextTZOverride) { return this; } - return new JacksonJodaDateFormat(this, _useTimestamp, adjustToContextTZOverride); + return new JacksonJodaDateFormat(this, adjustToContextTZOverride, _writeZoneId); + } + + /** + * @since 2.8 + */ + public JacksonJodaDateFormat withWriteZoneId(Boolean writeZoneId) { + // minor efficiency check to avoid recreation if no change: + if (writeZoneId == _writeZoneId) { + return this; + } + return new JacksonJodaDateFormat(this, _adjustToContextTZOverride, writeZoneId); } /* @@ -201,7 +259,7 @@ public DateTimeFormatter createParser(DeserializationContext ctxt) } } if (!_explicitTimezone) { - if (isAdjustDatesToContextTimeZone(ctxt)) { + if (shouldAdjustToContextTimeZone(ctxt)) { TimeZone tz = ctxt.getTimeZone(); if (tz != null && !tz.equals(_jdkTimezone)) { formatter = formatter.withZone(DateTimeZone.forTimeZone(tz)); @@ -213,9 +271,20 @@ public DateTimeFormatter createParser(DeserializationContext ctxt) return formatter; } - private boolean isAdjustDatesToContextTimeZone(DeserializationContext ctxt) { - return (_adjustToContextTZOverride != null) ? _adjustToContextTZOverride : - ctxt.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + /** + * @since 2.8 + */ + public boolean shouldAdjustToContextTimeZone(DeserializationContext ctxt) { + return (_adjustToContextTZOverride != null) ? _adjustToContextTZOverride : + ctxt.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + } + + /** + * @since 2.8 + */ + public boolean shouldWriteWithZoneId(SerializerProvider ctxt) { + return (_writeZoneId != null) ? _writeZoneId : + ctxt.isEnabled(SerializationFeature.WRITE_DATES_WITH_ZONE_ID); } /** diff --git a/src/main/java/com/fasterxml/jackson/datatype/joda/deser/JodaDateDeserializerBase.java b/src/main/java/com/fasterxml/jackson/datatype/joda/deser/JodaDateDeserializerBase.java index df76b144..e6003310 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/joda/deser/JodaDateDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/datatype/joda/deser/JodaDateDeserializerBase.java @@ -53,11 +53,7 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, format = format.withUseTimestamp(useTimestamp); } // for others, safe to call, null/empty just ignored - format = format.withFormat(ann.getPattern().trim()); - format = format.withLocale(ann.getLocale()); - format = format.withTimeZone(ann.getTimeZone()); - format = format.withAdjustToContextTZOverride( - ann.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)); + format = format.with(ann); if (format != _format) { return withFormat(format); } 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 e456cd4f..5c9868f2 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 @@ -36,7 +36,7 @@ public boolean isEmpty(SerializerProvider prov, DateTime value) { public void serialize(DateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException { // First: simple, non-timezone-included output - if (!provider.isEnabled(SerializationFeature.WRITE_DATES_WITH_ZONE_ID)) { + if (!writeWithZoneId(provider)) { if (_useTimestamp(provider)) { gen.writeNumber(value.getMillis()); } else { diff --git a/src/main/java/com/fasterxml/jackson/datatype/joda/ser/JodaDateSerializerBase.java b/src/main/java/com/fasterxml/jackson/datatype/joda/ser/JodaDateSerializerBase.java index 63013492..4a92eb43 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/joda/ser/JodaDateSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/datatype/joda/ser/JodaDateSerializerBase.java @@ -67,10 +67,7 @@ public JsonSerializer createContextual(SerializerProvider prov, if (useTimestamp != null) { format = format.withUseTimestamp(useTimestamp); } - // for others, safe to call, null/empty just ignored - format = format.withFormat(ann.getPattern().trim()); - format = format.withLocale(ann.getLocale()); - format = format.withTimeZone(ann.getTimeZone()); + format = format.with(ann); if (format != _format) { return withFormat(format); } @@ -125,4 +122,11 @@ protected void _acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaTy protected boolean _useTimestamp(SerializerProvider provider) { return _format.useTimestamp(provider, _featureForNumeric); } + + /** + * @since 2.8 + */ + protected boolean writeWithZoneId(SerializerProvider provider) { + return _format.shouldWriteWithZoneId(provider); + } } diff --git a/src/test/java/com/fasterxml/jackson/datatype/joda/JodaSerializationTest.java b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/JodaSerializationTest.java similarity index 99% rename from src/test/java/com/fasterxml/jackson/datatype/joda/JodaSerializationTest.java rename to src/test/java/com/fasterxml/jackson/datatype/joda/ser/JodaSerializationTest.java index 8bf6708c..443a222b 100644 --- a/src/test/java/com/fasterxml/jackson/datatype/joda/JodaSerializationTest.java +++ b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/JodaSerializationTest.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.datatype.joda; +package com.fasterxml.jackson.datatype.joda.ser; import java.io.IOException; import java.text.SimpleDateFormat; @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.joda.JodaTestBase; public class JodaSerializationTest extends JodaTestBase { diff --git a/src/test/java/com/fasterxml/jackson/datatype/joda/ser/WriteZoneIdTest.java b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/WriteZoneIdTest.java new file mode 100644 index 00000000..1b73c2a1 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/datatype/joda/ser/WriteZoneIdTest.java @@ -0,0 +1,43 @@ +package com.fasterxml.jackson.datatype.joda.ser; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.joda.JodaTestBase; + +public class WriteZoneIdTest extends JodaTestBase +{ + static class DummyClassWithDate { + @JsonFormat(shape = JsonFormat.Shape.STRING, + pattern = "dd-MM-yyyy hh:mm:ss Z", + with = JsonFormat.Feature.WRITE_DATES_WITH_ZONE_ID) + public DateTime date; + + DummyClassWithDate() { } + + public DummyClassWithDate(DateTime date) { + this.date = date; + } + } + + public void testJacksonAnnotatedPOJOWithDateWithTimezoneToJson() throws Exception + { + ObjectMapper mapper = jodaMapper(); + String ZONE_ID = "Asia/Krasnoyarsk"; + + DummyClassWithDate input = new DummyClassWithDate(new DateTime(2015, 11, 23, 22, 06, 39, + DateTimeZone.forID(ZONE_ID))); + // 30-Jun-2016, tatu: Exact time seems to vary a bit based on DST, so let's actually + // just verify appending of timezone id itself: + String json = mapper.writeValueAsString(input); + if (!json.contains("\"23-11-2015")) { + fail("Should contain time prefix, did not: "+json); + } + String match = String.format("[%s]", ZONE_ID); + if (!json.contains(match)) { + fail("Should contain zone id "+match+", does not: "+json); + } + } +}