1 /*
2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 package test.java.time.format;
25 
26 import static org.testng.Assert.assertEquals;
27 
28 import java.text.DateFormatSymbols;
29 import java.time.ZoneId;
30 import java.time.ZonedDateTime;
31 import java.time.format.DecimalStyle;
32 import java.time.format.DateTimeFormatter;
33 import java.time.format.DateTimeFormatterBuilder;
34 import java.time.format.TextStyle;
35 import java.time.temporal.ChronoField;
36 import java.time.temporal.TemporalQueries;
37 import java.time.zone.ZoneRulesProvider;
38 import java.util.Arrays;
39 import java.util.Date;
40 import java.util.HashSet;
41 import java.util.Locale;
42 import java.util.Random;
43 import java.util.Set;
44 import java.util.TimeZone;
45 import jdk.testlibrary.RandomFactory;
46 
47 import org.testng.annotations.DataProvider;
48 import org.testng.annotations.Test;
49 
50 /*
51  * @test
52  * @bug 8081022
53  * @key randomness
54  */
55 
56 /**
57  * Test ZoneTextPrinterParser
58  */
59 @Test
60 public class TestZoneTextPrinterParser extends AbstractTestPrinterParser {
61 
getFormatter(Locale locale, TextStyle style)62     protected static DateTimeFormatter getFormatter(Locale locale, TextStyle style) {
63         return new DateTimeFormatterBuilder().appendZoneText(style)
64                                              .toFormatter(locale)
65                                              .withDecimalStyle(DecimalStyle.of(locale));
66     }
67 
test_printText()68     public void test_printText() {
69         Random r = RandomFactory.getRandom();
70         // Android-changed: only run one iteration.
71         int N = 1;
72         Locale[] locales = Locale.getAvailableLocales();
73         Set<String> zids = ZoneRulesProvider.getAvailableZoneIds();
74         ZonedDateTime zdt = ZonedDateTime.now();
75 
76         //System.out.printf("locale==%d, timezone=%d%n", locales.length, zids.size());
77         while (N-- > 0) {
78             zdt = zdt.withDayOfYear(r.nextInt(365) + 1)
79                      .with(ChronoField.SECOND_OF_DAY, r.nextInt(86400));
80             // Android-changed: loop over locales first to speed up test. TimeZoneNames are cached
81             // per locale, but the cache only holds the most recently used locales.
82             for (Locale locale : locales) {
83                 // Android-changed: "ji" isn't correctly aliased to "yi", see http//b/8634320.
84                 if (locale.getLanguage().equals("ji")) {
85                     continue;
86                 }
87                 for (String zid : zids) {
88                     if (zid.equals("ROC") || zid.startsWith("Etc/GMT")) {
89                         continue;      // TBD: match jdk behavior?
90                     }
91                     // Android-changed (http://b/33197219): TimeZone.getDisplayName() for
92                     // non-canonical time zones are not correct.
93                     if (!zid.equals(getSystemCanonicalID(zid))) {
94                         continue;
95                     }
96                     zdt = zdt.withZoneSameLocal(ZoneId.of(zid));
97                     TimeZone tz = TimeZone.getTimeZone(zid);
98                     // Android-changed: We don't have long names for GMT.
99                     if (tz.getID().equals("GMT")) {
100                         continue;
101                     }
102                     boolean isDST = tz.inDaylightTime(new Date(zdt.toInstant().toEpochMilli()));
103                     printText(locale, zdt, TextStyle.FULL, tz,
104                             tz.getDisplayName(isDST, TimeZone.LONG, locale));
105                     printText(locale, zdt, TextStyle.SHORT, tz,
106                             tz.getDisplayName(isDST, TimeZone.SHORT, locale));
107                 }
108             }
109         }
110     }
111 
112     // BEGIN Android-added: Get non-custom system canonical time zone Id from ICU.
getSystemCanonicalID(String zid)113     private static String getSystemCanonicalID(String zid) {
114         if (android.icu.util.TimeZone.UNKNOWN_ZONE_ID.equals(zid)) {
115             return zid;
116         }
117         boolean[] isSystemID = { false };
118         String canonicalID = android.icu.util.TimeZone.getCanonicalID(zid, isSystemID);
119         if (canonicalID == null || !isSystemID[0]) {
120             return null;
121         }
122         return canonicalID;
123     }
124     // END Android-added: Get non-custom system canonical time zone Id from ICU.
125 
printText(Locale locale, ZonedDateTime zdt, TextStyle style, TimeZone zone, String expected)126     private void printText(Locale locale, ZonedDateTime zdt, TextStyle style, TimeZone zone, String expected) {
127         String result = getFormatter(locale, style).format(zdt);
128         // Android-changed: TimeZone.getDisplayName() will never return "GMT".
129         if (result.startsWith("GMT") && expected.equals("GMT+00:00")) {
130             return;
131         }
132         if (!result.equals(expected)) {
133             if (result.equals("FooLocation")) { // from rules provider test if same vm
134                 return;
135             }
136             System.out.println("----------------");
137             System.out.printf("tdz[%s]%n", zdt.toString());
138             System.out.printf("[%-5s, %5s] :[%s]%n", locale.toString(), style.toString(),result);
139             System.out.printf(" %5s, %5s  :[%s] %s%n", "", "", expected, zone);
140         }
141         assertEquals(result, expected);
142     }
143 
144     // Android-changed: disable test as it doesn't assert anything and produces a lot of output.
145     @Test(enabled = false)
test_ParseText()146     public void test_ParseText() {
147         Locale[] locales = new Locale[] { Locale.ENGLISH, Locale.JAPANESE, Locale.FRENCH };
148         Set<String> zids = ZoneRulesProvider.getAvailableZoneIds();
149         for (Locale locale : locales) {
150             parseText(zids, locale, TextStyle.FULL, false);
151             parseText(zids, locale, TextStyle.FULL, true);
152             parseText(zids, locale, TextStyle.SHORT, false);
153             parseText(zids, locale, TextStyle.SHORT, true);
154         }
155     }
156 
157     private static Set<ZoneId> preferred = new HashSet<>(Arrays.asList(new ZoneId[] {
158         ZoneId.of("EST", ZoneId.SHORT_IDS),
159         ZoneId.of("Asia/Taipei"),
160         ZoneId.of("CET"),
161     }));
162 
163     private static Set<ZoneId> preferred_s = new HashSet<>(Arrays.asList(new ZoneId[] {
164          ZoneId.of("EST", ZoneId.SHORT_IDS),
165          ZoneId.of("CET"),
166          ZoneId.of("Australia/South"),
167          ZoneId.of("Australia/West"),
168          ZoneId.of("Asia/Shanghai"),
169     }));
170 
171     private static Set<ZoneId> none = new HashSet<>();
172 
173     @DataProvider(name="preferredZones")
data_preferredZones()174     Object[][] data_preferredZones() {
175         // Android-changed: Differences in time zone name handling.
176         // Android and java.time (via the RI) have differences in how they handle Time Zone Names.
177         // - Android doesn't use IANA abbreviates (usually 3-letter abbreviations) except where they
178         //   are widely used in a given locale (so CST will not resolve to "Chinese Standard Time").
179         // - Android doesn't provide long names for zones like "CET". Only the Olson IDs like
180         //   "Europe/London" have names attached to them.
181         // - When no preferred zones are provided then no guarantee is made about the specific zone
182         //   returned.
183         // - Android uses the display name "Taipei Standard Time" as CLDR does.
184         // Basically Android time zone parsing sticks strictly to what can be done with the data
185         // provided by IANA and CLDR and avoids introducing additional values (like specific order
186         // and additional names) to those.
187         return new Object[][] {
188             // {"America/New_York", "Eastern Standard Time", none,      Locale.ENGLISH, TextStyle.FULL},
189 //          {"EST",              "Eastern Standard Time", preferred, Locale.ENGLISH, TextStyle.FULL},
190             // {"Europe/Paris",     "Central European Time", none,      Locale.ENGLISH, TextStyle.FULL},
191             // {"CET",              "Central European Time", preferred, Locale.UK, TextStyle.FULL},
192             // {"Asia/Shanghai",    "China Standard Time",   none,      Locale.ENGLISH, TextStyle.FULL},
193             // {"Asia/Taipei",      "China Standard Time",   preferred, Locale.ENGLISH, TextStyle.FULL},
194             // {"America/Chicago",  "CST",                   none,      Locale.ENGLISH, TextStyle.SHORT},
195             // {"Asia/Taipei",      "CST",                   preferred, Locale.ENGLISH, TextStyle.SHORT},
196             // Australia/South is a valid synonym for Australia/Adelaide, so this test will pass.
197             {"Australia/South",  "ACST",                  preferred_s, new Locale("en", "AU"), TextStyle.SHORT},
198             // {"America/Chicago",  "CDT",                   none,        Locale.ENGLISH, TextStyle.SHORT},
199             // {"Asia/Shanghai",    "CDT",                   preferred_s, Locale.ENGLISH, TextStyle.SHORT},
200        };
201     }
202 
203     @Test(dataProvider="preferredZones")
test_ParseText(String expected, String text, Set<ZoneId> preferred, Locale locale, TextStyle style)204     public void test_ParseText(String expected, String text, Set<ZoneId> preferred, Locale locale, TextStyle style) {
205         DateTimeFormatter fmt = new DateTimeFormatterBuilder().appendZoneText(style, preferred)
206                                                               .toFormatter(locale)
207                                                               .withDecimalStyle(DecimalStyle.of(locale));
208 
209         String ret = fmt.parse(text, TemporalQueries.zone()).getId();
210 
211         System.out.printf("[%-5s %s] %24s -> %s(%s)%n",
212                           locale.toString(),
213                           style == TextStyle.FULL ? " full" :"short",
214                           text, ret, expected);
215 
216         assertEquals(ret, expected);
217 
218     }
219 
220 
parseText(Set<String> zids, Locale locale, TextStyle style, boolean ci)221     private void parseText(Set<String> zids, Locale locale, TextStyle style, boolean ci) {
222         System.out.println("---------------------------------------");
223         DateTimeFormatter fmt = getFormatter(locale, style, ci);
224         for (String[] names : new DateFormatSymbols(locale).getZoneStrings()) {
225             if (!zids.contains(names[0])) {
226                 continue;
227             }
228             String zid = names[0];
229             String expected = ZoneName.toZid(zid, locale);
230 
231             parse(fmt, zid, expected, zid, locale, style, ci);
232             int i = style == TextStyle.FULL ? 1 : 2;
233             for (; i < names.length; i += 2) {
234                 parse(fmt, zid, expected, names[i], locale, style, ci);
235             }
236         }
237     }
238 
parse(DateTimeFormatter fmt, String zid, String expected, String text, Locale locale, TextStyle style, boolean ci)239     private void parse(DateTimeFormatter fmt,
240                        String zid, String expected, String text,
241                        Locale locale, TextStyle style, boolean ci) {
242         if (ci) {
243             text = text.toUpperCase();
244         }
245         String ret = fmt.parse(text, TemporalQueries.zone()).getId();
246         // TBD: need an excluding list
247         // assertEquals(...);
248         if (ret.equals(expected) ||
249             ret.equals(zid) ||
250             ret.equals(ZoneName.toZid(zid)) ||
251             ret.equals(expected.replace("UTC", "UCT"))) {
252             return;
253         }
254         System.out.printf("[%-5s %s %s %16s] %24s -> %s(%s)%n",
255                           locale.toString(),
256                           ci ? "ci" : "  ",
257                           style == TextStyle.FULL ? " full" :"short",
258                           zid, text, ret, expected);
259     }
260 
getFormatter(Locale locale, TextStyle style, boolean ci)261     private DateTimeFormatter getFormatter(Locale locale, TextStyle style, boolean ci) {
262         DateTimeFormatterBuilder db = new DateTimeFormatterBuilder();
263         if (ci) {
264             db = db.parseCaseInsensitive();
265         }
266         return db.appendZoneText(style)
267                  .toFormatter(locale)
268                  .withDecimalStyle(DecimalStyle.of(locale));
269     }
270 
271 }
272