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
This commit is contained in:
Jesse Wilson 2011-02-10 01:06:22 +00:00
parent 4fc0577933
commit 6e81cfdbb4
2 changed files with 144 additions and 20 deletions

View File

@ -44,6 +44,7 @@ import java.util.Properties;
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 @@ final class DefaultTypeAdapters {
}
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 @@ final class DefaultTypeAdapters {
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) {
}
try {
return enUsFormat.parse(json.getAsString());
} catch (ParseException ignored) {
}
try {
return iso8601Format.parse(json.getAsString());
} catch (ParseException e) {
throw new JsonSyntaxException(json.getAsString(), e);
}
} catch (ParseException e) {
throw new JsonSyntaxException(e);
}
}
@ -332,7 +352,7 @@ final class DefaultTypeAdapters {
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 @@ final class DefaultTypeAdapters {
throw new JsonParseException(e);
}
}
public JsonElement serialize(InetAddress src, Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(src.getHostAddress());

View File

@ -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 @@ import java.util.Date;
*/
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 @@ public class DefaultDateTypeAdapterTest extends TestCase {
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));
}
}