Removing data hacks from ICU root.txt

Previously, the upstream ICU data was modified
to support special short codes (e.g. PST, CET) so that
they were recognized when parsing/formatting in all
locales with SimpleDateFormat.parse()/format().

In an effort to more-closely replicate ICU this change
does *not* introduce special casing for short /
abbreviated names from Java APIs.

This may have impact for applications that use
Date.toString() (but not Date.parse()),
SimpleDateFormat.parse(), SimpleDateFormat.format(),
and anything that uses TimeZone methods that deal in
formatting and zone strings (e.g. getZoneStrings(),
getDisplayName()).

Details:

Date.parse() is unaffected because it handles abbreviated
names only, is not internationalized and contains a set
of recognized strings. Date.toString() is affected because
it relies on SimpleDateFormat for formatting.

ICU still supports abbreviated / short names for
locales where those terms are considered unambiguous for
residents of that locale. For example, "PST" is still parsed /
formatted in Locale.US when using SimpleDateFormat.
However, with this change "PST" will not be parsed/formatted
other locales such as Locale.FRANCE, Locale.UK.

If SimpleDateFormat.format() / TimeZone.getDisplayName() cannot
find a short / abbreviated name for a timezone in the
specified/defaulted locale, then a GMT offset is output as per
the docs.

Of particular note are methods that rely on Locale.getDefault()
and/or Locale.getTimeZone(). Most user-facing usecases are
expected to be unaffected. For example, US users will continue
to see / enter PST. Applications that were previously
parsing a date string containing (for example) PST but relying
on the default locale will start seeing failures.

Most of the changes are in tests that were hardcoding
expected strings or relying on the default locale.

This change is associated with a change in external/icu4c:
I04acd15c62d49c0cf30cc63f60db320bdb8e22e9

This commit also includes minor test changes related to
parsing/formatting dates where the default device locale
is assumed to be US (or other English-speaking locale).
Date-related tests that were relying  on the default locale
and broke when it was set to a non-English script are now
explicitly setting it.

Note: The tests all rely on the CtsTestRunner / Vogar
resetting the default Locale / TimeZone between each test.

Bug: 11413756
Change-Id: I9ae6397cf5335ef325aedb6472d0d66a6127e1dd
diff --git a/luni/src/main/java/java/text/SimpleDateFormat.java b/luni/src/main/java/java/text/SimpleDateFormat.java
index f832a6e..259bfe0 100644
--- a/luni/src/main/java/java/text/SimpleDateFormat.java
+++ b/luni/src/main/java/java/text/SimpleDateFormat.java
@@ -1168,6 +1168,8 @@
         if (foundGMT) {
             offset += 3;
         }
+
+        // Check for an offset, which may have been preceded by "GMT"
         char sign;
         if (offset < string.length() && ((sign = string.charAt(offset)) == '+' || sign == '-')) {
             ParsePosition position = new ParsePosition(offset + 1);
@@ -1195,10 +1197,14 @@
             calendar.setTimeZone(new SimpleTimeZone(raw, ""));
             return position.getIndex();
         }
+
+        // If there was "GMT" but no offset.
         if (foundGMT) {
             calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
             return offset;
         }
+
+        // Exhaustively look for the string in this DateFormat's localized time zone strings.
         for (String[] row : formatData.internalZoneStrings()) {
             for (int i = TimeZoneNames.LONG_NAME; i < TimeZoneNames.NAME_COUNT; ++i) {
                 if (row[i] == null) {
diff --git a/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java b/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java
index 1f0ee63..42de50a 100644
--- a/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java
+++ b/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java
@@ -122,7 +122,7 @@
 
     private Map<String, Date> getCrlDates(String name) throws Exception {
         Map<String, Date> dates = new HashMap<String, Date>();
-        final SimpleDateFormat sdf = new SimpleDateFormat("MMM dd HH:mm:ss yyyy zzz");
+        final SimpleDateFormat sdf = new SimpleDateFormat("MMM dd HH:mm:ss yyyy zzz", Locale.US);
 
         final InputStream ris = Support_Resources.getStream(name);
         try {
diff --git a/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java b/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java
index ffddcbe..c35f8e6 100644
--- a/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java
+++ b/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java
@@ -170,7 +170,8 @@
         final InputStream ris = Support_Resources.getStream("x509/cert-rsa-dates.txt");
         try {
             // notBefore=Dec 26 00:19:14 2012 GMT
-            final SimpleDateFormat sdf = new SimpleDateFormat("MMM dd HH:mm:ss yyyy zzz");
+            final SimpleDateFormat sdf =
+                    new SimpleDateFormat("MMM dd HH:mm:ss yyyy zzz", Locale.US);
 
             final BufferedReader buf = new BufferedReader(new InputStreamReader(ris));
             String line = buf.readLine();
diff --git a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
index e13e4df..9ab6f70 100644
--- a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
+++ b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
@@ -46,6 +46,10 @@
     }
 
     public void testSerialization() throws Exception {
+        // Set the default locale. The default locale determines what strings are used by the
+        // DateFormatSymbols after deserialization.
+        Locale.setDefault(Locale.US);
+
         // The Polish language needs stand-alone month and weekday names.
         Locale pl = new Locale("pl");
         DateFormatSymbols originalDfs = new DateFormatSymbols(pl);
diff --git a/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java b/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
index 2813cee..bc21739 100644
--- a/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
+++ b/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
@@ -80,27 +80,12 @@
     // The RI fails this test because it doesn't fully support UTS #35.
     // https://code.google.com/p/android/issues/detail?id=39616
     public void testFiveCount_parsing() throws Exception {
-      // It's pretty silly to try to parse the shortest names, because they're almost always ambiguous.
-      try {
-        parseDate(Locale.ENGLISH, "MMMMM", "J");
-        fail();
-      } catch (junit.framework.AssertionFailedError expected) {
-      }
-      try {
-        parseDate(Locale.ENGLISH, "LLLLL", "J");
-        fail();
-      } catch (junit.framework.AssertionFailedError expected) {
-      }
-      try {
-        parseDate(Locale.ENGLISH, "EEEEE", "T");
-        fail();
-      } catch (junit.framework.AssertionFailedError expected) {
-      }
-      try {
-        parseDate(Locale.ENGLISH, "ccccc", "T");
-        fail();
-      } catch (junit.framework.AssertionFailedError expected) {
-      }
+      // It's pretty silly to try to parse the shortest names, because they're almost always
+      // ambiguous.
+      assertCannotParse(Locale.ENGLISH, "MMMMM", "J");
+      assertCannotParse(Locale.ENGLISH, "LLLLL", "J");
+      assertCannotParse(Locale.ENGLISH, "EEEEE", "T");
+      assertCannotParse(Locale.ENGLISH, "ccccc", "T");
     }
 
     // The RI fails this test because it doesn't fully support UTS #35.
@@ -197,6 +182,13 @@
         return dateFormat.format(new Date(0));
     }
 
+    private static void assertCannotParse(Locale l, String fmt, String value) {
+        SimpleDateFormat sdf = new SimpleDateFormat(fmt, l);
+        ParsePosition pp = new ParsePosition(0);
+        Date d = sdf.parse(value, pp);
+        assertNull("Value " + value + " must not parse in locale " + l + " with format " + fmt, d);
+    }
+
     private static Calendar parseDate(Locale l, String fmt, String value) {
         SimpleDateFormat sdf = new SimpleDateFormat(fmt, l);
         ParsePosition pp = new ParsePosition(0);
@@ -215,21 +207,37 @@
         String date = "2010-12-23 12:44:57.0 CET";
         // ICU considers "CET" (Central European Time) to be common in Britain...
         assertEquals(1293104697000L, parseDate(Locale.UK, fmt, date).getTimeInMillis());
-        // ...but not in the US. Check we can parse such a date anyway.
-        assertEquals(1293104697000L, parseDate(Locale.US, fmt, date).getTimeInMillis());
+        // ...but not in the US.
+        assertCannotParse(Locale.US, fmt, date);
     }
 
+    // In Honeycomb, only one Olson id was associated with CET (or any other "uncommon"
+    // abbreviation). This was changed after KitKat to avoid Java hacks on top of ICU data.
+    // ICU data only provides abbreviations for timezones in the locales where they would
+    // not be ambiguous to most people of that locale.
     public void testFormattingUncommonTimeZoneAbbreviations() {
-        // In Honeycomb, only one Olson id was associated with CET (or any
-        // other "uncommon" abbreviation).
         String fmt = "yyyy-MM-dd HH:mm:ss.SSS z";
-        String date = "1970-01-01 01:00:00.000 CET";
-        SimpleDateFormat sdf = new SimpleDateFormat(fmt, Locale.US);
+        String unambiguousDate = "1970-01-01 01:00:00.000 CET";
+        String ambiguousDate = "1970-01-01 01:00:00.000 GMT+01:00";
+
+        // The locale to use when formatting. Not every Locale renders "Europe/Berlin" as "CET". The
+        // UK is one that does, the US is one that does not.
+        Locale cetUnambiguousLocale = Locale.UK;
+        Locale cetAmbiguousLocale = Locale.US;
+
+        SimpleDateFormat sdf = new SimpleDateFormat(fmt, cetUnambiguousLocale);
         sdf.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
-        assertEquals(date, sdf.format(new Date(0)));
-        sdf = new SimpleDateFormat(fmt, Locale.US);
+        assertEquals(unambiguousDate, sdf.format(new Date(0)));
+        sdf = new SimpleDateFormat(fmt, cetUnambiguousLocale);
         sdf.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));
-        assertEquals(date, sdf.format(new Date(0)));
+        assertEquals(unambiguousDate, sdf.format(new Date(0)));
+
+        sdf = new SimpleDateFormat(fmt, cetAmbiguousLocale);
+        sdf.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
+        assertEquals(ambiguousDate, sdf.format(new Date(0)));
+        sdf = new SimpleDateFormat(fmt, cetAmbiguousLocale);
+        sdf.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));
+        assertEquals(ambiguousDate, sdf.format(new Date(0)));
     }
 
     // http://code.google.com/p/android/issues/detail?id=8258
@@ -270,17 +278,6 @@
         assertEquals("2010-07-08T02:44:48+0000", sdf.format(date));
     }
 
-    /**
-     * Africa/Cairo standard time is EET and daylight time is EEST. They no
-     * longer use their DST zone but we should continue to parse it properly.
-     */
-    public void testObsoleteDstZoneName() throws Exception {
-        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US);
-        Date normal = format.parse("1970-01-01T00:00 EET");
-        Date dst = format.parse("1970-01-01T00:00 EEST");
-        assertEquals(60 * 60 * 1000, normal.getTime() - dst.getTime());
-    }
-
     public void testDstZoneNameWithNonDstTimestamp() throws Exception {
         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US);
         Calendar calendar = new GregorianCalendar(AMERICA_LOS_ANGELES);
diff --git a/luni/src/test/java/libcore/java/util/DateTest.java b/luni/src/test/java/libcore/java/util/DateTest.java
index ddcc3e5..3ed0952 100644
--- a/luni/src/test/java/libcore/java/util/DateTest.java
+++ b/luni/src/test/java/libcore/java/util/DateTest.java
@@ -24,16 +24,26 @@
 
 public class DateTest extends TestCase {
     // http://code.google.com/p/android/issues/detail?id=6013
-    public void test_toString() throws Exception {
-        // Ensure that no matter where this is run, we know what time zone
-        // to expect. (Though we still assume an "en" locale.)
+    public void test_toString_us() throws Exception {
+        // Ensure that no matter where this is run, we know what time zone to expect.
+        Locale.setDefault(Locale.US);
         TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
         assertEquals("Wed Dec 31 18:00:00 CST 1969", new Date(0).toString());
     }
 
-    public void test_toGMTString() throws Exception {
+    public void test_toString_nonUs() {
+        // The string for the timezone depends on what the default locale is. Not every locale
+        // has a short-name for America/Chicago -> PST.
+        Locale.setDefault(Locale.UK);
+        TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
+        assertEquals("Wed Dec 31 18:00:00 GMT-06:00 1969", new Date(0).toString());
+    }
+
+    public void test_toGMTString_us() throws Exception {
         // Based on https://issues.apache.org/jira/browse/HARMONY-501
         TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
+        Locale.setDefault(Locale.US);
+
         Calendar c = Calendar.getInstance();
         c.clear();
         c.set(Calendar.YEAR, 21);
@@ -43,4 +53,18 @@
         assertEquals("Sun Jan 01 00:00:00 PST 321", c.getTime().toString());
         assertEquals("1 Jan 321 08:00:00 GMT", c.getTime().toGMTString());
     }
+
+    public void test_toGMTString_nonUs() throws Exception {
+        TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
+        Locale.setDefault(Locale.UK);
+
+        Calendar c = Calendar.getInstance();
+        c.clear();
+        c.set(Calendar.YEAR, 21);
+        assertEquals("Wed Jan 01 00:00:00 GMT-08:00 21", c.getTime().toString());
+        assertEquals("1 Jan 21 08:00:00 GMT", c.getTime().toGMTString());
+        c.set(Calendar.YEAR, 321);
+        assertEquals("Sun Jan 01 00:00:00 GMT-08:00 321", c.getTime().toString());
+        assertEquals("1 Jan 321 08:00:00 GMT", c.getTime().toGMTString());
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java b/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java
index 93bd028..ecf2e5f 100644
--- a/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java
@@ -25,7 +25,7 @@
 
 public class OldTimeZoneTest extends TestCase {
 
-    class Mock_TimeZone extends TimeZone {
+    static class Mock_TimeZone extends TimeZone {
         @Override
         public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
             return 0;
@@ -91,29 +91,50 @@
 
     public void test_getDisplayNameLjava_util_Locale() {
         TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
-        assertEquals("Pacific Standard Time", tz.getDisplayName(new Locale("US")));
+        assertEquals("Pacific Standard Time", tz.getDisplayName(Locale.US));
         assertEquals("heure normale du Pacifique nord-américain", tz.getDisplayName(Locale.FRANCE));
     }
 
     public void test_getDisplayNameZI() {
         Locale.setDefault(Locale.US);
         TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
-        assertEquals("PST",                   tz.getDisplayName(false, 0));
-        assertEquals("Pacific Daylight Time", tz.getDisplayName(true, 1));
-        assertEquals("Pacific Standard Time", tz.getDisplayName(false, 1));
+        assertEquals("PST",                   tz.getDisplayName(false, TimeZone.SHORT));
+        assertEquals("Pacific Daylight Time", tz.getDisplayName(true, TimeZone.LONG));
+        assertEquals("Pacific Standard Time", tz.getDisplayName(false, TimeZone.LONG));
     }
 
     @AndroidOnly("fail on RI. See comment below")
     public void test_getDisplayNameZILjava_util_Locale() {
         TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
-        assertEquals("PST",                   tz.getDisplayName(false, 0, Locale.US));
-        assertEquals("Pacific Daylight Time", tz.getDisplayName(true,  1, Locale.US));
-        assertEquals("Pacific Standard Time", tz.getDisplayName(false, 1, Locale.UK));
-        // RI always returns short time zone name as "PST"
-        // ICU zone/root.txt patched to allow metazone names.
-        assertEquals("PST",             tz.getDisplayName(false, 0, Locale.FRANCE));
-        assertEquals("heure avanc\u00e9e du Pacifique", tz.getDisplayName(true,  1, Locale.FRANCE));
-        assertEquals("heure normale du Pacifique nord-américain", tz.getDisplayName(false, 1, Locale.FRANCE));
+        assertEquals("Pacific Daylight Time", tz.getDisplayName(true,  TimeZone.LONG, Locale.US));
+        assertEquals("Pacific Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.UK));
+        assertEquals("heure avanc\u00e9e du Pacifique",
+                tz.getDisplayName(true,  TimeZone.LONG, Locale.FRANCE));
+        assertEquals("heure normale du Pacifique nord-américain",
+                tz.getDisplayName(false, TimeZone.LONG, Locale.FRANCE));
+
+        assertEquals("PDT", tz.getDisplayName(true, TimeZone.SHORT, Locale.US));
+        assertEquals("PST", tz.getDisplayName(false, TimeZone.SHORT, Locale.US));
+        // RI fails on following lines. RI always returns short time zone name for
+        // "America/Los_Angeles" as "PST", Android only returns a string if ICU has a translation.
+        // There is no short time zone name for America/Los_Angeles in French or British English in
+        // ICU data so an offset is returned instead.
+        assertEquals("GMT-08:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.FRANCE));
+        assertEquals("GMT-07:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.FRANCE));
+        assertEquals("GMT-08:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.UK));
+        assertEquals("GMT-07:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.UK));
+
+        // The RI behavior mentioned above does not appear to be because "PST" is a legacy
+        // three-character timezone supported by the RI: it happens for "Asia/Tehran"/"IRST" too
+        // (IRST is not a legacy code). The RI may just use a different dataset that has "PST" /
+        // "IRST" as valid translations (even for scripts like Chinese).
+        TimeZone iranTz = TimeZone.getTimeZone("Asia/Tehran");
+        assertEquals("Iran Summer Time", iranTz.getDisplayName(true, TimeZone.LONG, Locale.UK));
+        assertEquals("Iran Daylight Time", iranTz.getDisplayName(true, TimeZone.LONG, Locale.US));
+        assertEquals("Iran Standard Time", iranTz.getDisplayName(false, TimeZone.LONG, Locale.UK));
+        assertEquals("Iran Standard Time", iranTz.getDisplayName(false, TimeZone.LONG, Locale.US));
+        assertEquals("GMT+03:30", iranTz.getDisplayName(false, TimeZone.SHORT, Locale.UK));
+        assertEquals("GMT+04:30", iranTz.getDisplayName(true, TimeZone.SHORT, Locale.UK));
     }
 
     public void test_getID() {
diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
index 08d1e69..86a8b7f 100644
--- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
@@ -57,6 +57,7 @@
 
     // http://code.google.com/p/android/issues/detail?id=14395
     public void testPreHistoricInDaylightTime() throws Exception {
+        Locale.setDefault(Locale.US);
         TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
         TimeZone.setDefault(tz);
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");