diff --git a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java b/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java index bb1a9bdb..95eb42be 100644 --- a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java @@ -16,7 +16,7 @@ package com.google.gson; -import java.lang.reflect.Type; +import java.io.IOException; import java.sql.Timestamp; import java.text.DateFormat; import java.text.ParseException; @@ -26,6 +26,9 @@ import java.util.Date; import java.util.Locale; import com.google.gson.internal.bind.util.ISO8601Utils; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; /** * This type adapter supports three subclasses of date: Date, Timestamp, and @@ -34,34 +37,45 @@ import com.google.gson.internal.bind.util.ISO8601Utils; * @author Inderjeet Singh * @author Joel Leitch */ -final class DefaultDateTypeAdapter implements JsonSerializer, JsonDeserializer { +final class DefaultDateTypeAdapter extends TypeAdapter { - // TODO: migrate to streaming adapter - private static final String SIMPLE_NAME = "DefaultDateTypeAdapter"; - + + private final Class dateType; private final DateFormat enUsFormat; private final DateFormat localFormat; - DefaultDateTypeAdapter() { - this(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US), + DefaultDateTypeAdapter(Class dateType) { + this(dateType, + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US), DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)); } - DefaultDateTypeAdapter(String datePattern) { - this(new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern)); + DefaultDateTypeAdapter(Class dateType, String datePattern) { + this(dateType, new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern)); } - DefaultDateTypeAdapter(int style) { - this(DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style)); + DefaultDateTypeAdapter(Class dateType, int style) { + this(dateType, DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style)); } public DefaultDateTypeAdapter(int dateStyle, int timeStyle) { - this(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US), + this(Date.class, + DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US), DateFormat.getDateTimeInstance(dateStyle, timeStyle)); } - DefaultDateTypeAdapter(DateFormat enUsFormat, DateFormat localFormat) { + public DefaultDateTypeAdapter(Class dateType, int dateStyle, int timeStyle) { + this(dateType, + DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US), + DateFormat.getDateTimeInstance(dateStyle, timeStyle)); + } + + DefaultDateTypeAdapter(final Class dateType, DateFormat enUsFormat, DateFormat localFormat) { + if ( dateType != Date.class && dateType != java.sql.Date.class && dateType != Timestamp.class ) { + throw new IllegalArgumentException("Date type must be one of " + Date.class + ", " + Timestamp.class + ", or " + java.sql.Date.class + " but was " + dateType); + } + this.dateType = dateType; this.enUsFormat = enUsFormat; this.localFormat = localFormat; } @@ -69,43 +83,43 @@ final class DefaultDateTypeAdapter implements JsonSerializer, JsonDeserial // These methods need to be synchronized since JDK DateFormat classes are not thread-safe // See issue 162 @Override - public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { + public void write(JsonWriter out, Date value) throws IOException { synchronized (localFormat) { - String dateFormatAsString = enUsFormat.format(src); - return new JsonPrimitive(dateFormatAsString); + String dateFormatAsString = enUsFormat.format(value); + out.value(dateFormatAsString); } } @Override - public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - if (!(json instanceof JsonPrimitive)) { + public Date read(JsonReader in) throws IOException { + if (in.peek() != JsonToken.STRING) { throw new JsonParseException("The date should be a string value"); } - Date date = deserializeToDate(json); - if (typeOfT == Date.class) { + Date date = deserializeToDate(in.nextString()); + if (dateType == Date.class) { return date; - } else if (typeOfT == Timestamp.class) { + } else if (dateType == Timestamp.class) { return new Timestamp(date.getTime()); - } else if (typeOfT == java.sql.Date.class) { + } else if (dateType == java.sql.Date.class) { return new java.sql.Date(date.getTime()); } else { - throw new IllegalArgumentException(getClass() + " cannot deserialize to " + typeOfT); + // This must never happen: dateType is guarded in the primary constructor + throw new AssertionError(); } } - private Date deserializeToDate(JsonElement json) { + private Date deserializeToDate(String s) { synchronized (localFormat) { try { - return localFormat.parse(json.getAsString()); + return localFormat.parse(s); } catch (ParseException ignored) {} try { - return enUsFormat.parse(json.getAsString()); + return enUsFormat.parse(s); } catch (ParseException ignored) {} try { - return ISO8601Utils.parse(json.getAsString(), new ParsePosition(0)); + return ISO8601Utils.parse(s, new ParsePosition(0)); } catch (ParseException e) { - throw new JsonSyntaxException(json.getAsString(), e); + throw new JsonSyntaxException(s, e); } } } diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index cebed7e7..e6893860 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -572,19 +572,26 @@ public final class GsonBuilder { serializeSpecialFloatingPointValues, longSerializationPolicy, factories); } + @SuppressWarnings("unchecked") private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, List factories) { DefaultDateTypeAdapter dateTypeAdapter; + TypeAdapter timestampTypeAdapter; + TypeAdapter javaSqlDateTypeAdapter; if (datePattern != null && !"".equals(datePattern.trim())) { - dateTypeAdapter = new DefaultDateTypeAdapter(datePattern); + dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, datePattern); + timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, datePattern); + javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, datePattern); } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) { - dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle, timeStyle); + dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, dateStyle, timeStyle); + timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, dateStyle, timeStyle); + javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, dateStyle, timeStyle); } else { return; } - factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Date.class), dateTypeAdapter)); - factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Timestamp.class), dateTypeAdapter)); - factories.add(TreeTypeAdapter.newFactory(TypeToken.get(java.sql.Date.class), dateTypeAdapter)); + factories.add(TypeAdapters.newFactory(Date.class, dateTypeAdapter)); + factories.add(TypeAdapters.newFactory(Timestamp.class, timestampTypeAdapter)); + factories.add(TypeAdapters.newFactory(java.sql.Date.class, javaSqlDateTypeAdapter)); } } diff --git a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java index 5ce65d74..b3ee5a6f 100644 --- a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java +++ b/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java @@ -16,6 +16,7 @@ package com.google.gson; +import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -44,10 +45,10 @@ public class DefaultDateTypeAdapterTest extends TestCase { Locale defaultLocale = Locale.getDefault(); Locale.setDefault(locale); try { - assertFormatted("Jan 1, 1970 12:00:00 AM", new DefaultDateTypeAdapter()); - assertFormatted("1/1/70", new DefaultDateTypeAdapter(DateFormat.SHORT)); - assertFormatted("Jan 1, 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM)); - assertFormatted("January 1, 1970", new DefaultDateTypeAdapter(DateFormat.LONG)); + assertFormatted("Jan 1, 1970 12:00:00 AM", new DefaultDateTypeAdapter(Date.class)); + assertFormatted("1/1/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT)); + assertFormatted("Jan 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM)); + assertFormatted("January 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG)); assertFormatted("1/1/70 12:00 AM", new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); assertFormatted("Jan 1, 1970 12:00:00 AM", @@ -62,16 +63,16 @@ public class DefaultDateTypeAdapterTest extends TestCase { } } - public void testParsingDatesFormattedWithSystemLocale() { + public void testParsingDatesFormattedWithSystemLocale() throws Exception { TimeZone defaultTimeZone = TimeZone.getDefault(); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); Locale defaultLocale = Locale.getDefault(); Locale.setDefault(Locale.FRANCE); try { - assertParsed("1 janv. 1970 00:00:00", new DefaultDateTypeAdapter()); - assertParsed("01/01/70", new DefaultDateTypeAdapter(DateFormat.SHORT)); - assertParsed("1 janv. 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM)); - assertParsed("1 janvier 1970", new DefaultDateTypeAdapter(DateFormat.LONG)); + assertParsed("1 janv. 1970 00:00:00", new DefaultDateTypeAdapter(Date.class)); + assertParsed("01/01/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT)); + assertParsed("1 janv. 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM)); + assertParsed("1 janvier 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG)); assertParsed("01/01/70 00:00", new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); assertParsed("1 janv. 1970 00:00:00", @@ -86,16 +87,16 @@ public class DefaultDateTypeAdapterTest extends TestCase { } } - public void testParsingDatesFormattedWithUsLocale() { + public void testParsingDatesFormattedWithUsLocale() throws Exception { TimeZone defaultTimeZone = TimeZone.getDefault(); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); Locale defaultLocale = Locale.getDefault(); Locale.setDefault(Locale.US); try { - assertParsed("Jan 1, 1970 0:00:00 AM", new DefaultDateTypeAdapter()); - assertParsed("1/1/70", new DefaultDateTypeAdapter(DateFormat.SHORT)); - assertParsed("Jan 1, 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM)); - assertParsed("January 1, 1970", new DefaultDateTypeAdapter(DateFormat.LONG)); + assertParsed("Jan 1, 1970 0:00:00 AM", new DefaultDateTypeAdapter(Date.class)); + assertParsed("1/1/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT)); + assertParsed("Jan 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM)); + assertParsed("January 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG)); assertParsed("1/1/70 0:00 AM", new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); assertParsed("Jan 1, 1970 0:00:00 AM", @@ -110,14 +111,14 @@ public class DefaultDateTypeAdapterTest extends TestCase { } } - public void testFormatUsesDefaultTimezone() { + public void testFormatUsesDefaultTimezone() throws Exception { TimeZone defaultTimeZone = TimeZone.getDefault(); TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); Locale defaultLocale = Locale.getDefault(); Locale.setDefault(Locale.US); try { - assertFormatted("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter()); - assertParsed("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter()); + assertFormatted("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter(Date.class)); + assertParsed("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter(Date.class)); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -125,7 +126,7 @@ public class DefaultDateTypeAdapterTest extends TestCase { } public void testDateDeserializationISO8601() throws Exception { - DefaultDateTypeAdapter adapter = new DefaultDateTypeAdapter(); + DefaultDateTypeAdapter adapter = new DefaultDateTypeAdapter(Date.class); assertParsed("1970-01-01T00:00:00.000Z", adapter); assertParsed("1970-01-01T00:00Z", adapter); assertParsed("1970-01-01T00:00:00+00:00", adapter); @@ -135,38 +136,41 @@ public class DefaultDateTypeAdapterTest extends TestCase { public void testDateSerialization() throws Exception { int dateStyle = DateFormat.LONG; - DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle); + DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, dateStyle); DateFormat formatter = DateFormat.getDateInstance(dateStyle, Locale.US); Date currentDate = new Date(); - String dateString = dateTypeAdapter.serialize(currentDate, Date.class, null).getAsString(); - assertEquals(formatter.format(currentDate), dateString); + String dateString = dateTypeAdapter.toJson(currentDate); + assertEquals(toLiteral(formatter.format(currentDate)), dateString); } public void testDatePattern() throws Exception { String pattern = "yyyy-MM-dd"; - DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(pattern); + DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, pattern); DateFormat formatter = new SimpleDateFormat(pattern); Date currentDate = new Date(); - String dateString = dateTypeAdapter.serialize(currentDate, Date.class, null).getAsString(); - assertEquals(formatter.format(currentDate), dateString); + String dateString = dateTypeAdapter.toJson(currentDate); + assertEquals(toLiteral(formatter.format(currentDate)), dateString); } public void testInvalidDatePattern() throws Exception { try { - new DefaultDateTypeAdapter("I am a bad Date pattern...."); + new DefaultDateTypeAdapter(Date.class, "I am a bad Date pattern...."); fail("Invalid date pattern should fail."); } catch (IllegalArgumentException expected) { } } private void assertFormatted(String formatted, DefaultDateTypeAdapter adapter) { - assertEquals(formatted, adapter.serialize(new Date(0), Date.class, null).getAsString()); + assertEquals(toLiteral(formatted), adapter.toJson(new Date(0))); } - private void assertParsed(String date, DefaultDateTypeAdapter adapter) { - assertEquals(date, new Date(0), adapter.deserialize(new JsonPrimitive(date), Date.class, null)); - assertEquals("ISO 8601", new Date(0), adapter.deserialize( - new JsonPrimitive("1970-01-01T00:00:00Z"), Date.class, null)); + private void assertParsed(String date, DefaultDateTypeAdapter adapter) throws IOException { + assertEquals(date, new Date(0), adapter.fromJson(toLiteral(date))); + assertEquals("ISO 8601", new Date(0), adapter.fromJson(toLiteral("1970-01-01T00:00:00Z"))); + } + + private static String toLiteral(String s) { + return '"' + s + '"'; } }