From 2a9808cc2732bb4828c06501cf8c09f23b63f397 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 26 Sep 2015 16:25:31 -0700 Subject: [PATCH] Fix #939 --- release-notes/CREDITS | 4 ++ release-notes/VERSION | 2 + .../jackson/databind/ObjectMapper.java | 3 +- .../jackson/databind/SerializerProvider.java | 19 +++--- .../jackson/databind/cfg/BaseSettings.java | 65 +++++++++++++++---- .../databind/ser/std/StdKeySerializers.java | 1 + .../jackson/databind/util/StdDateFormat.java | 2 +- .../deser/TestMapDeserialization.java | 1 - .../jackson/failing/EnumMap749Test.java | 51 --------------- 9 files changed, 71 insertions(+), 77 deletions(-) delete mode 100644 src/test/java/com/fasterxml/jackson/failing/EnumMap749Test.java diff --git a/release-notes/CREDITS b/release-notes/CREDITS index b89c3e31e3..bd188c3de3 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -328,3 +328,7 @@ Sadayuki Furuhashi (frsyuki@github) Sergio Mira (Sergio-Mira@github) * Contributed #940: Add missing `hashCode()` implementations for `JsonNode` types that did not have them (2.6.3) + +Andreas Piebe (anpieber@github) + * Reported #939: Regression: DateConversionError in 2.6.x + (2.6.3) diff --git a/release-notes/VERSION b/release-notes/VERSION index ff3d4446b9..2577dca1f2 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -10,6 +10,8 @@ Project: jackson-databind (reported by scubasau@github) #938: Regression: `StackOverflowError` with recursive types that contain `Map.Entry` (reported by jloisel@github) +#939: Regression: DateConversionError in 2.6.x + (reported by Andreas P, anpieber@github) #940: Add missing `hashCode()` implementations for `JsonNode` types that did not have them (contributed by Sergio M) #941: Deserialization from "{}" to ObjectNode field causes "out of END_OBJECT token" error diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 169799682c..caa09c304c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -276,8 +276,7 @@ public boolean useForType(JavaType t) STD_VISIBILITY_CHECKER, null, TypeFactory.defaultInstance(), null, StdDateFormat.instance, null, Locale.getDefault(), -// TimeZone.getDefault() - TimeZone.getTimeZone("GMT"), + null, // to indicate "use default TimeZone" Base64Variants.getDefaultVariant() // 2.1 ); diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java index 7a657c3ed7..4e56315308 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java @@ -997,14 +997,13 @@ public final void defaultSerializeField(String fieldName, Object value, JsonGene * Note: date here means "full" date, that is, date AND time, as per * Java convention (and not date-only values like in SQL) */ - public final void defaultSerializeDateValue(long timestamp, JsonGenerator jgen) + public final void defaultSerializeDateValue(long timestamp, JsonGenerator gen) throws IOException { - // [JACKSON-87]: Support both numeric timestamps and textual if (isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { - jgen.writeNumber(timestamp); + gen.writeNumber(timestamp); } else { - jgen.writeString(_dateFormat().format(new Date(timestamp))); + gen.writeString(_dateFormat().format(new Date(timestamp))); } } @@ -1015,10 +1014,8 @@ public final void defaultSerializeDateValue(long timestamp, JsonGenerator jgen) * Note: date here means "full" date, that is, date AND time, as per * Java convention (and not date-only values like in SQL) */ - public final void defaultSerializeDateValue(Date date, JsonGenerator gen) - throws IOException + public final void defaultSerializeDateValue(Date date, JsonGenerator gen) throws IOException { - // [JACKSON-87]: Support both numeric timestamps and textual if (isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { gen.writeNumber(date.getTime()); } else { @@ -1031,8 +1028,7 @@ public final void defaultSerializeDateValue(Date date, JsonGenerator gen) * based on {@link SerializationFeature#WRITE_DATE_KEYS_AS_TIMESTAMPS} * value (and if using textual representation, configured date format) */ - public void defaultSerializeDateKey(long timestamp, JsonGenerator gen) - throws IOException + public void defaultSerializeDateKey(long timestamp, JsonGenerator gen) throws IOException { if (isEnabled(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)) { gen.writeFieldName(String.valueOf(timestamp)); @@ -1242,11 +1238,14 @@ protected final DateFormat _dateFormat() */ DateFormat df = _config.getDateFormat(); _dateFormat = df = (DateFormat) df.clone(); - // 11-Jun-2015, tatu: Plus caller may have actually changed default TimeZone to use + // [databind#939]: 26-Sep-2015, tatu: With 2.6, formatter has been (pre)configured + // with TimeZone, so we should NOT try overriding it unlike with earlier versions + /* TimeZone tz = getTimeZone(); if (tz != df.getTimeZone()) { df.setTimeZone(tz); } + */ return df; } } diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java index 7291b5d358..9b1b4115f9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java @@ -26,6 +26,16 @@ public final class BaseSettings // for 2.6 private static final long serialVersionUID = 1L; + /** + * We will use a default TimeZone as the baseline. + */ + private static final TimeZone DEFAULT_TIMEZONE = + // TimeZone.getDefault() + /* [databind#915] 26-Sep-2015, tatu: Should be UTC, plan to change + * it so for 2.7 + */ + TimeZone.getTimeZone("GMT"); + /* /********************************************************** /* Configuration settings; introspection, related @@ -109,11 +119,11 @@ public final class BaseSettings protected final Locale _locale; /** - * Default {@link java.util.TimeZone} used with serialization formats. - * Default value is {@link TimeZone#getDefault()}, which is typically the - * local time zone (unless overridden for JVM). + * Default {@link java.util.TimeZone} used with serialization formats, + * if (and only if!) explicitly set by use; otherwise `null` to indicate + * "use default", which currently (Jackson 2.6) means "GMT" *

- * Note that if a new value is set, time zone is also assigned to + * Note that if a new value is set, timezone is also assigned to * {@link #_dateFormat} of this object. */ protected final TimeZone _timeZone; @@ -231,6 +241,11 @@ public BaseSettings withDateFormat(DateFormat df) { if (_dateFormat == df) { return this; } + // 26-Sep-2015, tatu: Related to [databind#939], let's try to force TimeZone if + // (but only if!) it has been set explicitly. + if ((df != null) && hasExplicitTimeZone()) { + df = _force(df, _timeZone); + } return new BaseSettings(_classIntrospector, _annotationIntrospector, _visibilityChecker, _propertyNamingStrategy, _typeFactory, _typeResolverBuilder, df, _handlerInstantiator, _locale, _timeZone, _defaultBase64); @@ -264,14 +279,11 @@ public BaseSettings with(TimeZone tz) if (tz == null) { throw new IllegalArgumentException(); } - DateFormat df = _dateFormat; - if (df instanceof StdDateFormat) { - df = ((StdDateFormat) df).withTimeZone(tz); - } else { - // we don't know if original format might be shared; better create a clone: - df = (DateFormat) df.clone(); - df.setTimeZone(tz); + if (tz == _timeZone) { + return this; } + + DateFormat df = _force(_dateFormat, tz); return new BaseSettings(_classIntrospector, _annotationIntrospector, _visibilityChecker, _propertyNamingStrategy, _typeFactory, _typeResolverBuilder, df, _handlerInstantiator, _locale, @@ -334,10 +346,39 @@ public Locale getLocale() { } public TimeZone getTimeZone() { - return _timeZone; + TimeZone tz = _timeZone; + return (tz == null) ? DEFAULT_TIMEZONE : tz; } + /** + * Accessor that may be called to determine whether this settings object + * has been explicitly configured with a TimeZone (true), or is still + * relying on the default settings (false). + * + * @since 2.7 + */ + public boolean hasExplicitTimeZone() { + return (_timeZone != null); + } + public Base64Variant getBase64Variant() { return _defaultBase64; } + + /* + /********************************************************** + /* Helper methods + /********************************************************** + */ + + private DateFormat _force(DateFormat df, TimeZone tz) + { + if (df instanceof StdDateFormat) { + return ((StdDateFormat) df).withTimeZone(tz); + } + // we don't know if original format might be shared; better create a clone: + df = (DateFormat) df.clone(); + df.setTimeZone(tz); + return df; + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java index 82067abe0f..8c8bf06600 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java @@ -83,6 +83,7 @@ public static JsonSerializer getStdKeySerializer(JavaType keyType) { /** * @deprecated since 2.7 */ + @Deprecated public static JsonSerializer getDefault() { return DEFAULT_KEY_SERIALIZER; } diff --git a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java index fa8e331479..402744c53b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java @@ -149,7 +149,7 @@ public StdDateFormat withTimeZone(TimeZone tz) { if (tz == null) { tz = DEFAULT_TIMEZONE; } - if (tz.equals(_timezone)) { + if ((tz == _timezone) || tz.equals(_timezone)) { return this; } return new StdDateFormat(tz, _locale); diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java index 9c3ff9eada..29ea9efe36 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.deser.TestCollectionDeserialization.XBean; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; @SuppressWarnings("serial") diff --git a/src/test/java/com/fasterxml/jackson/failing/EnumMap749Test.java b/src/test/java/com/fasterxml/jackson/failing/EnumMap749Test.java deleted file mode 100644 index 4f88e58ce1..0000000000 --- a/src/test/java/com/fasterxml/jackson/failing/EnumMap749Test.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.fasterxml.jackson.failing; - -import java.util.*; - -import com.fasterxml.jackson.databind.*; - -/** - * Failing tests for [databind#749]: problems using (or not) of `toString()` for Enum keys - * of EnumMap, EnumSet. - */ -public class EnumMap749Test - extends BaseMapTest -{ - protected static enum LC749Enum { - A, B, C; - private LC749Enum() { } - @Override - public String toString() { return name().toLowerCase(); } - } - - /* - /********************************************************** - /* Tests - /********************************************************** - */ - - // [databind#749] - - public void testEnumMapSerDefault() throws Exception { - final ObjectMapper mapper = new ObjectMapper(); - EnumMap m = new EnumMap(LC749Enum.class); - m.put(LC749Enum.A, "value"); - assertEquals("{\"A\":\"value\"}", mapper.writeValueAsString(m)); - } - - public void testEnumMapSerDisableToString() throws Exception { - final ObjectMapper mapper = new ObjectMapper(); - ObjectWriter w = mapper.writer().without(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); - EnumMap m = new EnumMap(LC749Enum.class); - m.put(LC749Enum.A, "value"); - assertEquals("{\"A\":\"value\"}", w.writeValueAsString(m)); - } - - public void testEnumMapSerEnableToString() throws Exception { - final ObjectMapper mapper = new ObjectMapper(); - ObjectWriter w = mapper.writer().with(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); - EnumMap m = new EnumMap(LC749Enum.class); - m.put(LC749Enum.A, "value"); - assertEquals("{\"a\":\"value\"}", w.writeValueAsString(m)); - } -}