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