1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.lang3.time;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertNotEquals;
21 import static org.junit.jupiter.api.Assertions.assertNotNull;
22 import static org.junit.jupiter.api.Assertions.fail;
23 import static org.junit.jupiter.api.Assumptions.assumeFalse;
24 import static org.junit.jupiter.api.Assumptions.assumeTrue;
25
26 import java.text.DateFormatSymbols;
27 import java.text.ParseException;
28 import java.time.ZoneId;
29 import java.util.ArrayList;
30 import java.util.Comparator;
31 import java.util.Date;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Objects;
37 import java.util.Set;
38 import java.util.TimeZone;
39 import java.util.concurrent.atomic.AtomicInteger;
40
41 import org.apache.commons.lang3.AbstractLangTest;
42 import org.apache.commons.lang3.ArraySorter;
43 import org.apache.commons.lang3.JavaVersion;
44 import org.apache.commons.lang3.LocaleUtils;
45 import org.apache.commons.lang3.SystemUtils;
46 import org.junit.jupiter.api.AfterAll;
47 import org.junit.jupiter.api.Disabled;
48 import org.junit.jupiter.api.Test;
49 import org.junit.jupiter.params.ParameterizedTest;
50 import org.junit.jupiter.params.provider.MethodSource;
51 import org.junit.jupiter.params.provider.ValueSource;
52 import org.junitpioneer.jupiter.DefaultLocale;
53 import org.junitpioneer.jupiter.DefaultTimeZone;
54 import org.junitpioneer.jupiter.ReadsDefaultLocale;
55 import org.junitpioneer.jupiter.ReadsDefaultTimeZone;
56
57
58
59
60 @DefaultLocale(language = "en")
61 @DefaultTimeZone(TimeZones.GMT_ID)
62 @ReadsDefaultLocale
63 @ReadsDefaultTimeZone
64 class FastDateParser_TimeZoneStrategyTest extends AbstractLangTest {
65
66 private static final String UTC_FULL_NAME = "Coordinated Universal Time";
67 private static final List<Locale> JAVA_11_FAILURES = new ArrayList<>();
68 private static final List<Locale> JAVA_17_FAILURES = new ArrayList<>();
69 private static final AtomicInteger FAILS = new AtomicInteger();
70
71 @AfterAll
72 public static void afterAll() {
73 if (!JAVA_17_FAILURES.isEmpty()) {
74 System.err.printf("Actual failures on Java 17+: %,d%n%s%n", JAVA_17_FAILURES.size(), JAVA_17_FAILURES);
75 }
76 if (!JAVA_11_FAILURES.isEmpty()) {
77 System.err.printf("Actual failures on Java 11: %,d%n%s%n", JAVA_11_FAILURES.size(), JAVA_11_FAILURES);
78 }
79 }
80
81 static Set<Entry<String, String>> getZoneIdStream() {
82 return ZoneId.SHORT_IDS.entrySet();
83 }
84
85 private String[][] getZoneStringsSorted(final Locale locale) {
86 return ArraySorter.sort(DateFormatSymbols.getInstance(locale).getZoneStrings(), Comparator.comparing(array -> array[0]));
87 }
88
89
90
91
92
93
94
95
96 @ParameterizedTest
97 @ValueSource(strings = { "ACT", "CST" })
98 @ReadsDefaultLocale
99 void testJava25DeprecatedZoneId(final String shortId) throws ParseException {
100 final FastDateParser parser = new FastDateParser("dd.MM.yyyy HH:mm:ss z", TimeZone.getTimeZone(shortId), Locale.getDefault());
101 final Date date1 = parser.parse("26.10.2014 02:00:00 " + shortId);
102
103 assertNotNull(date1);
104
105 assertEquals(2014, date1.getYear() + 1900);
106 }
107
108
109
110
111
112
113
114
115 @Disabled
116 @ParameterizedTest
117 @MethodSource("getZoneIdStream")
118 void testJava25DeprecatedZoneIds(final Map.Entry<String, String> entry) throws ParseException {
119 final FastDateParser parser = new FastDateParser("dd.MM.yyyy HH:mm:ss z", TimeZone.getDefault(), Locale.GERMAN);
120 final Date date1 = parser.parse("26.10.2014 02:00:00 " + entry.getKey());
121 final Date date2 = parser.parse("26.10.2014 02:00:00 " + entry.getValue());
122 assertNotEquals(date1.getTime(), date2.getTime());
123 }
124
125 @Test
126 void testLang1219() throws ParseException {
127 final FastDateParser parser = new FastDateParser("dd.MM.yyyy HH:mm:ss z", TimeZone.getDefault(), Locale.GERMAN);
128 final Date summer = parser.parse("26.10.2014 02:00:00 MESZ");
129 final Date standard = parser.parse("26.10.2014 02:00:00 MEZ");
130 assertNotEquals(summer.getTime(), standard.getTime());
131 }
132
133 @ParameterizedTest
134 @MethodSource("org.apache.commons.lang3.LocaleUtils#availableLocaleList()")
135 void testTimeZoneStrategy_DateFormatSymbols(final Locale locale) {
136 testTimeZoneStrategyPattern_DateFormatSymbols_getZoneStrings(locale);
137 }
138
139 @ParameterizedTest
140 @MethodSource("org.apache.commons.lang3.LocaleUtils#availableLocaleList()")
141 void testTimeZoneStrategy_TimeZone(final Locale locale) {
142 testTimeZoneStrategyPattern_TimeZone_getAvailableIDs(locale);
143 }
144
145 private void testTimeZoneStrategyPattern(final Locale locale, final String timeZoneId) {
146 final TimeZone timeZone = TimeZones.getTimeZone(timeZoneId);
147 final String displayName = timeZone.getDisplayName(locale);
148 final FastDateParser parser = new FastDateParser("z", timeZone, locale);
149 try {
150 parser.parse(displayName);
151 } catch (final ParseException e) {
152 final StringBuilder sb = new StringBuilder("chars: [");
153 displayName.chars().forEach(c -> sb.append(c).append(", "));
154 sb.append("], code points: [");
155 displayName.codePoints().forEach(c -> sb.append(c).append(", "));
156 sb.append("]");
157 if (displayName.contains("\uFFFD")) {
158 System.err.printf("TimeZone ID %s displayName contains Unicode character 'REPLACEMENT CHARACTER' (U+FFFD) in '%s'%n", timeZoneId, displayName);
159 } else {
160
161 fail(String.format("displayName: '%s' for id: '%s', %s, exception: %s, %s, parser = %s", displayName, timeZoneId, sb.toString(), e,
162 toFailureMessage(locale, null, timeZone), parser.toStringAll()));
163 }
164 }
165 }
166
167 private void testTimeZoneStrategyPattern(final String languageTag, final String source) throws ParseException {
168 final Locale locale = Locale.forLanguageTag(languageTag);
169 final TimeZone timeZone = TimeZones.getTimeZone("Etc/UTC");
170 assumeFalse(LocaleUtils.isLanguageUndetermined(locale), () -> toFailureMessage(locale, languageTag, timeZone));
171 assumeTrue(LocaleUtils.isAvailableLocale(locale), () -> toFailureMessage(locale, languageTag, timeZone));
172 final FastDateParser parser = new FastDateParser("z", timeZone, locale);
173 parser.parse(source);
174 testTimeZoneStrategyPattern_TimeZone_getAvailableIDs(locale);
175 }
176
177 private void testTimeZoneStrategyPattern_DateFormatSymbols_getZoneStrings(final Locale locale) {
178 Objects.requireNonNull(locale, "locale");
179 assumeFalse(LocaleUtils.isLanguageUndetermined(locale), () -> toFailureMessage(locale, null, null));
180 assumeTrue(LocaleUtils.isAvailableLocale(locale), () -> toFailureMessage(locale, null, null));
181 final String[][] zones = getZoneStringsSorted(locale);
182 for (final String[] zone : zones) {
183 for (int zIndex = 1; zIndex < zone.length; ++zIndex) {
184 final String tzDisplay = zone[zIndex];
185 if (tzDisplay == null) {
186 break;
187 }
188 final TimeZone timeZone = TimeZone.getDefault();
189 final FastDateParser parser = new FastDateParser("z", timeZone, locale);
190
191 try {
192 parser.parse(tzDisplay);
193 } catch (final ParseException e) {
194
195
196 final String localeStr = locale.toString();
197 if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_17)
198 && (localeStr.contains("_") || UTC_FULL_NAME.equals(tzDisplay) || "sommartid â Atyrau".equals(tzDisplay))) {
199 JAVA_17_FAILURES.add(locale);
200
201
202 System.err.printf(
203 "[%,d][%s] Java %s %s - Mark as an assumption failure instead of a hard fail: locale = '%s', parse = '%s'%n",
204 FAILS.incrementAndGet(),
205 Thread.currentThread().getName(),
206 SystemUtils.JAVA_VENDOR,
207 SystemUtils.JAVA_VM_VERSION,
208 localeStr, tzDisplay);
209
210 assumeTrue(false, localeStr);
211 continue;
212 }
213 if (SystemUtils.IS_JAVA_11 && (localeStr.contains("_") || UTC_FULL_NAME.equals(tzDisplay))) {
214 JAVA_11_FAILURES.add(locale);
215
216
217 System.err.printf(
218 "[%,d][%s] Java %s %s - Mark as an assumption failure instead of a hard fail: locale = '%s', parse = '%s'%n",
219 FAILS.incrementAndGet(),
220 Thread.currentThread().getName(),
221 SystemUtils.JAVA_VENDOR,
222 SystemUtils.JAVA_VM_VERSION,
223 localeStr, tzDisplay);
224
225 assumeTrue(false, localeStr);
226 continue;
227 }
228
229 fail(String.format("%s: with locale = %s, zIndex = %,d, tzDisplay = '%s', parser = '%s'", e, localeStr, zIndex, tzDisplay, parser), e);
230 }
231 }
232 }
233 }
234
235
236
237
238
239
240 private void testTimeZoneStrategyPattern_TimeZone_getAvailableIDs(final Locale locale) {
241 Objects.requireNonNull(locale, "locale");
242 assumeFalse(LocaleUtils.isLanguageUndetermined(locale), () -> toFailureMessage(locale, null, null));
243 assumeTrue(LocaleUtils.isAvailableLocale(locale), () -> toFailureMessage(locale, null, null));
244 for (final String timeZoneId : TimeZones.SORTED_AVAILABLE_IDS) {
245 testTimeZoneStrategyPattern(locale, timeZoneId);
246 }
247 }
248
249 @Test
250 void testTimeZoneStrategyPattern_zh_HK_Hans() throws ParseException {
251 testTimeZoneStrategyPattern("zh_HK_#Hans", "?????????");
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265 @Test
266 void testTimeZoneStrategyPatternPortugal_PT() throws ParseException {
267 testTimeZoneStrategyPattern("pt_PT", "Horário do Meridiano de Greenwich");
268 }
269
270
271
272
273
274
275 @Test
276 void testTimeZoneStrategyPatternPortugal_ST() throws ParseException {
277 testTimeZoneStrategyPattern("pt_ST", "Hora padrão de Damasco");
278 }
279
280
281
282
283
284
285 @Test
286 void testTimeZoneStrategyPatternPortugal_TL() throws ParseException {
287 testTimeZoneStrategyPattern("pt_TL", "Hora padrão de Damasco");
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301 @Test
302 void testTimeZoneStrategyPatternSuriname() throws ParseException {
303 testTimeZoneStrategyPattern("sr_ME_#Cyrl", "Srednje vreme po GriniÄu");
304 }
305
306 private String toFailureMessage(final Locale locale, final String languageTag, final TimeZone timeZone) {
307 return String.format("locale = %s, languageTag = '%s', isAvailableLocale = %s, isLanguageUndetermined = %s, timeZone = %s", languageTag, locale,
308 LocaleUtils.isAvailableLocale(locale), LocaleUtils.isLanguageUndetermined(locale), TimeZones.toTimeZone(timeZone));
309 }
310 }