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