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