Locale-awareness for date parsing and formatting:
- always format in en_US for best interchange
- always parse in en_US, system locale and ISO-8601
diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java
index 22a154f..4b3fa42 100644
--- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java
+++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java
@@ -44,6 +44,7 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
+import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;
@@ -287,29 +288,40 @@
}
static class DefaultDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
- private final DateFormat format;
+ private final DateFormat enUsFormat;
+ private final DateFormat localFormat;
+ private final DateFormat iso8601Format;
DefaultDateTypeAdapter() {
- this.format = DateFormat.getDateTimeInstance();
+ this(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US),
+ DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
}
- DefaultDateTypeAdapter(final String datePattern) {
- this.format = new SimpleDateFormat(datePattern);
+ DefaultDateTypeAdapter(String datePattern) {
+ this(new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern));
}
- DefaultDateTypeAdapter(final int style) {
- this.format = DateFormat.getDateInstance(style);
+ DefaultDateTypeAdapter(int style) {
+ this(DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style));
}
- public DefaultDateTypeAdapter(final int dateStyle, final int timeStyle) {
- this.format = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
+ public DefaultDateTypeAdapter(int dateStyle, int timeStyle) {
+ this(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US),
+ DateFormat.getDateTimeInstance(dateStyle, timeStyle));
+ }
+
+ public DefaultDateTypeAdapter(DateFormat enUsFormat, DateFormat localFormat) {
+ this.enUsFormat = enUsFormat;
+ this.localFormat = localFormat;
+ this.iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ this.iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
}
// These methods need to be synchronized since JDK DateFormat classes are not thread-safe
// See issue 162
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
- synchronized (format) {
- String dateFormatAsString = format.format(src);
+ synchronized (localFormat) {
+ String dateFormatAsString = enUsFormat.format(src);
return new JsonPrimitive(dateFormatAsString);
}
}
@@ -319,12 +331,20 @@
if (!(json instanceof JsonPrimitive)) {
throw new JsonParseException("The date should be a string value");
}
- try {
- synchronized (format) {
- return format.parse(json.getAsString());
+ synchronized (localFormat) {
+ try {
+ return localFormat.parse(json.getAsString());
+ } catch (ParseException ignored) {
}
- } catch (ParseException e) {
- throw new JsonSyntaxException(e);
+ try {
+ return enUsFormat.parse(json.getAsString());
+ } catch (ParseException ignored) {
+ }
+ try {
+ return iso8601Format.parse(json.getAsString());
+ } catch (ParseException e) {
+ throw new JsonSyntaxException(json.getAsString(), e);
+ }
}
}
@@ -332,7 +352,7 @@
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(DefaultDateTypeAdapter.class.getSimpleName());
- sb.append('(').append(format.getClass().getSimpleName()).append(')');
+ sb.append('(').append(localFormat.getClass().getSimpleName()).append(')');
return sb.toString();
}
}
@@ -454,7 +474,7 @@
throw new JsonParseException(e);
}
}
-
+
public JsonElement serialize(InetAddress src, Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(src.getHostAddress());
diff --git a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java
index bdc1417..e585531 100644
--- a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java
+++ b/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java
@@ -17,12 +17,12 @@
package com.google.gson;
import com.google.gson.DefaultTypeAdapters.DefaultDateTypeAdapter;
-
-import junit.framework.TestCase;
-
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import junit.framework.TestCase;
/**
* A simple unit test for the {@link DefaultDateTypeAdapter} class.
@@ -31,6 +31,100 @@
*/
public class DefaultDateTypeAdapterTest extends TestCase {
+ public void testFormattingInEnUs() {
+ testFormattingAlwaysEmitsUsLocale(Locale.US);
+ }
+
+ public void testFormattingInFr() {
+ testFormattingAlwaysEmitsUsLocale(Locale.FRANCE);
+ }
+
+ private void testFormattingAlwaysEmitsUsLocale(Locale locale) {
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ 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("1/1/70 12:00 AM",
+ new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
+ assertFormatted("Jan 1, 1970 12:00:00 AM",
+ new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM));
+ assertFormatted("January 1, 1970 12:00:00 AM UTC",
+ new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG));
+ assertFormatted("Thursday, January 1, 1970 12:00:00 AM UTC",
+ new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL));
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ public void testParsingDatesFormattedWithSystemLocale() {
+ 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("01/01/70 00:00",
+ new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
+ assertParsed("1 janv. 1970 00:00:00",
+ new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM));
+ assertParsed("1 janvier 1970 00:00:00 UTC",
+ new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG));
+ assertParsed("jeudi 1 janvier 1970 00 h 00 UTC",
+ new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL));
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ public void testParsingDatesFormattedWithUsLocale() {
+ 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("1/1/70 0:00 AM",
+ new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
+ assertParsed("Jan 1, 1970 0:00:00 AM",
+ new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM));
+ assertParsed("January 1, 1970 0:00:00 AM UTC",
+ new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG));
+ assertParsed("Thursday, January 1, 1970 0:00:00 AM UTC",
+ new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL));
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ public void testFormatUsesDefaultTimezone() {
+ 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());
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
public void testDateSerialization() throws Exception {
int dateStyle = DateFormat.LONG;
DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle);
@@ -57,4 +151,14 @@
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());
+ }
+
+ 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));
+ }
}