• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2012, 2018, 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.test.lib.RandomFactory;
46 
47 import org.testng.annotations.DataProvider;
48 import org.testng.annotations.Test;
49 
50 /*
51  * @test
52  * @bug 8081022 8151876 8166875 8189784 8206980
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             /*
83             for (String zid : zids) {
84                 if (zid.equals("ROC") || zid.startsWith("Etc/GMT")) {
85                     continue;      // TBD: match jdk behavior?
86                 }
87                 zdt = zdt.withZoneSameLocal(ZoneId.of(zid));
88                 TimeZone tz = TimeZone.getTimeZone(zid);
89                 boolean isDST = tz.inDaylightTime(new Date(zdt.toInstant().toEpochMilli()));
90                 for (Locale locale : locales) {
91                     boolean isDST = tz.inDaylightTime(new Date(zdt.toInstant().toEpochMilli()));
92                     String longDisplayName = tz.getDisplayName(isDST, TimeZone.LONG, locale);
93                     String shortDisplayName = tz.getDisplayName(isDST, TimeZone.SHORT, locale);
94                     if ((longDisplayName.startsWith("GMT+") && shortDisplayName.startsWith("GMT+"))
95                             || (longDisplayName.startsWith("GMT-") && shortDisplayName.startsWith("GMT-"))) {
96                         printText(locale, zdt, TextStyle.FULL, tz, tz.getID());
97                         printText(locale, zdt, TextStyle.SHORT, tz, tz.getID());
98                         continue;
99                     }
100              */
101             for (Locale locale : locales) {
102                 // Android-changed: "ji" isn't correctly aliased to "yi", see http//b/8634320.
103                 if (locale.getLanguage().equals("ji")) {
104                     continue;
105                 }
106                 for (String zid : zids) {
107                     if (zid.equals("ROC") || zid.startsWith("Etc/GMT")) {
108                         continue;      // TBD: match jdk behavior?
109                     }
110                     // Android-changed (http://b/33197219): TimeZone.getDisplayName() for
111                     // non-canonical time zones are not correct.
112                     if (!zid.equals(getSystemCanonicalID(zid))) {
113                         continue;
114                     }
115                     zdt = zdt.withZoneSameLocal(ZoneId.of(zid));
116                     TimeZone tz = TimeZone.getTimeZone(zid);
117                     // Android-changed: We don't have long names for GMT.
118                     if (tz.getID().equals("GMT")) {
119                         continue;
120                     }
121                     boolean isDST = tz.inDaylightTime(new Date(zdt.toInstant().toEpochMilli()));
122                     printText(locale, zdt, TextStyle.FULL, tz,
123                         tz.getDisplayName(isDST, TimeZone.LONG, locale));
124                     printText(locale, zdt, TextStyle.SHORT, tz,
125                         tz.getDisplayName(isDST, TimeZone.SHORT, locale));
126                 }
127             }
128         }
129     }
130 
131     // BEGIN Android-added: Get non-custom system canonical time zone Id from ICU.
getSystemCanonicalID(String zid)132     private static String getSystemCanonicalID(String zid) {
133         if (android.icu.util.TimeZone.UNKNOWN_ZONE_ID.equals(zid)) {
134             return zid;
135         }
136         boolean[] isSystemID = { false };
137         String canonicalID = android.icu.util.TimeZone.getCanonicalID(zid, isSystemID);
138         if (canonicalID == null || !isSystemID[0]) {
139             return null;
140         }
141         return canonicalID;
142     }
143     // END Android-added: Get non-custom system canonical time zone Id from ICU.
144 
printText(Locale locale, ZonedDateTime zdt, TextStyle style, TimeZone zone, String expected)145     private void printText(Locale locale, ZonedDateTime zdt, TextStyle style, TimeZone zone, String expected) {
146         String result = getFormatter(locale, style).format(zdt);
147         // Android-changed: TimeZone.getDisplayName() will never return "GMT".
148         if (result.startsWith("GMT") && expected.equals("GMT+00:00")) {
149             return;
150         }
151         if (!result.equals(expected)) {
152             if (result.equals("FooLocation")) { // from rules provider test if same vm
153                 return;
154             }
155             System.out.println("----------------");
156             System.out.printf("tdz[%s]%n", zdt.toString());
157             System.out.printf("[%-5s, %5s] :[%s]%n", locale.toString(), style.toString(),result);
158             System.out.printf(" %5s, %5s  :[%s] %s%n", "", "", expected, zone);
159         }
160         assertEquals(result, expected);
161     }
162 
163     // Android-changed: disable test as it doesn't assert anything and produces a lot of output.
164     @Test(enabled = false)
test_ParseText()165     public void test_ParseText() {
166         Locale[] locales = new Locale[] { Locale.ENGLISH, Locale.JAPANESE, Locale.FRENCH };
167         Set<String> zids = ZoneRulesProvider.getAvailableZoneIds();
168         for (Locale locale : locales) {
169             parseText(zids, locale, TextStyle.FULL, false);
170             parseText(zids, locale, TextStyle.FULL, true);
171             parseText(zids, locale, TextStyle.SHORT, false);
172             parseText(zids, locale, TextStyle.SHORT, true);
173         }
174     }
175 
176     private static Set<ZoneId> preferred = new HashSet<>(Arrays.asList(new ZoneId[] {
177         ZoneId.of("EST", ZoneId.SHORT_IDS),
178         ZoneId.of("Asia/Taipei"),
179         ZoneId.of("Asia/Macau"),
180         ZoneId.of("CET"),
181     }));
182 
183     private static Set<ZoneId> preferred_s = new HashSet<>(Arrays.asList(new ZoneId[] {
184          ZoneId.of("EST", ZoneId.SHORT_IDS),
185          ZoneId.of("CET"),
186          ZoneId.of("Australia/South"),
187          ZoneId.of("Australia/West"),
188          ZoneId.of("Asia/Shanghai"),
189     }));
190 
191     private static Set<ZoneId> none = new HashSet<>();
192 
193     @DataProvider(name="preferredZones")
data_preferredZones()194     Object[][] data_preferredZones() {
195         // Android-changed: Differences in time zone name handling.
196         // Android and java.time (via the RI) have differences in how they handle Time Zone Names.
197         // - Android doesn't use IANA abbreviates (usually 3-letter abbreviations) except where they
198         //   are widely used in a given locale (so CST will not resolve to "Chinese Standard Time").
199         // - Android doesn't provide long names for zones like "CET". Only the Olson IDs like
200         //   "Europe/London" have names attached to them.
201         // - When no preferred zones are provided then no guarantee is made about the specific zone
202         //   returned.
203         // - Android uses the display name "Taipei Standard Time" as CLDR does.
204         // Basically Android time zone parsing sticks strictly to what can be done with the data
205         // provided by IANA and CLDR and avoids introducing additional values (like specific order
206         // and additional names) to those.
207         return new Object[][] {
208             // {"America/New_York", "Eastern Standard Time", none,      Locale.ENGLISH, TextStyle.FULL},
209 //          {"EST",              "Eastern Standard Time", preferred, Locale.ENGLISH, TextStyle.FULL},
210             // {"Europe/Paris",     "Central European Time", none,      Locale.ENGLISH, TextStyle.FULL},
211 //          {"CET",              "Central European Time", preferred, Locale.ENGLISH, TextStyle.FULL}, no three-letter ID in CLDR
212             // {"Asia/Shanghai",    "China Standard Time",   none,      Locale.ENGLISH, TextStyle.FULL},
213             {"Asia/Macau",       "China Standard Time",   preferred, Locale.ENGLISH, TextStyle.FULL},
214             // {"Asia/Taipei",      "China Standard Time",   preferred, Locale.ENGLISH, TextStyle.FULL},
215             // {"America/Chicago",  "CST",                   none,      Locale.ENGLISH, TextStyle.SHORT},
216             // {"Asia/Taipei",      "CST",                   preferred, Locale.ENGLISH, TextStyle.SHORT},
217             // Australia/South is a valid synonym for Australia/Adelaide, so this test will pass.
218             {"Australia/South",  "ACST",                  preferred_s, new Locale("en", "AU"), TextStyle.SHORT},
219             // {"America/Chicago",  "CDT",                   none,        Locale.ENGLISH, TextStyle.SHORT},
220             // {"Asia/Shanghai",    "CDT",                   preferred_s, Locale.ENGLISH, TextStyle.SHORT},
221             // {"America/Juneau",   "AKST",                  none,      Locale.ENGLISH, TextStyle.SHORT},
222             // {"America/Juneau",   "AKDT",                  none,      Locale.ENGLISH, TextStyle.SHORT},
223             {"Pacific/Honolulu", "HST",                   none,      Locale.ENGLISH, TextStyle.SHORT},
224             // {"America/Halifax",  "AST",                   none,      Locale.ENGLISH, TextStyle.SHORT},
225             {"Z",                "Z",                     none,      Locale.ENGLISH, TextStyle.SHORT},
226             {"Z",                "Z",                     none,      Locale.US,      TextStyle.SHORT},
227             {"Z",                "Z",                     none,      Locale.CANADA,  TextStyle.SHORT},
228        };
229     }
230 
231     @Test(dataProvider="preferredZones")
test_ParseText(String expected, String text, Set<ZoneId> preferred, Locale locale, TextStyle style)232     public void test_ParseText(String expected, String text, Set<ZoneId> preferred, Locale locale, TextStyle style) {
233         DateTimeFormatter fmt = new DateTimeFormatterBuilder().appendZoneText(style, preferred)
234                                                               .toFormatter(locale)
235                                                               .withDecimalStyle(DecimalStyle.of(locale));
236 
237         String ret = fmt.parse(text, TemporalQueries.zone()).getId();
238 
239         System.out.printf("[%-5s %s] %24s -> %s(%s)%n",
240                           locale.toString(),
241                           style == TextStyle.FULL ? " full" :"short",
242                           text, ret, expected);
243 
244         assertEquals(ret, expected);
245 
246     }
247 
248 
parseText(Set<String> zids, Locale locale, TextStyle style, boolean ci)249     private void parseText(Set<String> zids, Locale locale, TextStyle style, boolean ci) {
250         System.out.println("---------------------------------------");
251         DateTimeFormatter fmt = getFormatter(locale, style, ci);
252         for (String[] names : new DateFormatSymbols(locale).getZoneStrings()) {
253             if (!zids.contains(names[0])) {
254                 continue;
255             }
256             String zid = names[0];
257             String expected = ZoneName.toZid(zid, locale);
258 
259             parse(fmt, zid, expected, zid, locale, style, ci);
260             int i = style == TextStyle.FULL ? 1 : 2;
261             for (; i < names.length; i += 2) {
262                 parse(fmt, zid, expected, names[i], locale, style, ci);
263             }
264         }
265     }
266 
parse(DateTimeFormatter fmt, String zid, String expected, String text, Locale locale, TextStyle style, boolean ci)267     private void parse(DateTimeFormatter fmt,
268                        String zid, String expected, String text,
269                        Locale locale, TextStyle style, boolean ci) {
270         if (ci) {
271             text = text.toUpperCase();
272         }
273         String ret = fmt.parse(text, TemporalQueries.zone()).getId();
274         // TBD: need an excluding list
275         // assertEquals(...);
276         if (ret.equals(expected) ||
277             ret.equals(zid) ||
278             ret.equals(ZoneName.toZid(zid)) ||
279             ret.equals(expected.replace("UTC", "UCT"))) {
280             return;
281         }
282         System.out.printf("[%-5s %s %s %16s] %24s -> %s(%s)%n",
283                           locale.toString(),
284                           ci ? "ci" : "  ",
285                           style == TextStyle.FULL ? " full" :"short",
286                           zid, text, ret, expected);
287     }
288 
getFormatter(Locale locale, TextStyle style, boolean ci)289     private DateTimeFormatter getFormatter(Locale locale, TextStyle style, boolean ci) {
290         DateTimeFormatterBuilder db = new DateTimeFormatterBuilder();
291         if (ci) {
292             db = db.parseCaseInsensitive();
293         }
294         return db.appendZoneText(style)
295                  .toFormatter(locale)
296                  .withDecimalStyle(DecimalStyle.of(locale));
297     }
298 
299 }
300