View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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.assertNotSame;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.fail;
25  
26  import java.text.FieldPosition;
27  import java.text.Format;
28  import java.text.ParseException;
29  import java.text.ParsePosition;
30  import java.text.SimpleDateFormat;
31  import java.time.Instant;
32  import java.time.LocalDate;
33  import java.time.ZoneId;
34  import java.time.format.DateTimeFormatter;
35  import java.util.Calendar;
36  import java.util.Date;
37  import java.util.Locale;
38  import java.util.TimeZone;
39  import java.util.concurrent.ExecutorService;
40  import java.util.concurrent.Executors;
41  import java.util.concurrent.TimeUnit;
42  import java.util.concurrent.atomic.AtomicInteger;
43  import java.util.concurrent.atomic.AtomicLongArray;
44  
45  import org.apache.commons.lang3.AbstractLangTest;
46  import org.junit.jupiter.api.Test;
47  import org.junitpioneer.jupiter.DefaultLocale;
48  import org.junitpioneer.jupiter.DefaultTimeZone;
49  
50  /**
51   * Tests {@link org.apache.commons.lang3.time.FastDateFormat}.
52   */
53  class FastDateFormatTest extends AbstractLangTest {
54  
55      private static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZZ";
56  
57      private static final int NTHREADS = 10;
58  
59      private static final int NROUNDS = 10000;
60  
61      final Locale FINNISH = Locale.forLanguageTag("fi");
62      final Locale HUNGARIAN = Locale.forLanguageTag("hu");
63  
64      private void assertCalendar(final TimeZone expectedTimeZone, final Date expectedDate, final Calendar actual) {
65          assertSame(expectedTimeZone, actual.getTimeZone());
66          // Calendar.getTime() returns a new Date in Java 8.
67          assertEquals(expectedDate, actual.getTime());
68      }
69  
70      private AtomicLongArray measureTime(final Format printer, final Format parser) throws InterruptedException {
71          final ExecutorService pool = Executors.newFixedThreadPool(NTHREADS);
72          final AtomicInteger failures = new AtomicInteger();
73          final AtomicLongArray totalElapsed = new AtomicLongArray(2);
74          try {
75              for (int i = 0; i < NTHREADS; ++i) {
76                  pool.submit(() -> {
77                      for (int j = 0; j < NROUNDS; ++j) {
78                          try {
79                              final Date date = new Date();
80  
81                              final long t0Millis = System.currentTimeMillis();
82                              final String formattedDate = printer.format(date);
83                              totalElapsed.addAndGet(0, System.currentTimeMillis() - t0Millis);
84  
85                              final long t1Millis = System.currentTimeMillis();
86                              final Object pd = parser.parseObject(formattedDate);
87                              totalElapsed.addAndGet(1, System.currentTimeMillis() - t1Millis);
88  
89                              if (!date.equals(pd)) {
90                                  failures.incrementAndGet();
91                              }
92                          } catch (final Exception e) {
93                              failures.incrementAndGet();
94                              e.printStackTrace();
95                          }
96                      }
97                  });
98              }
99          } finally {
100             pool.shutdown();
101             // depending on the performance of the machine used to run the parsing,
102             // the tests can run for a while. It should however complete within
103             // 30 seconds. Might need increase on very slow machines.
104             if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
105                 pool.shutdownNow();
106                 fail("did not complete tasks");
107             }
108         }
109         assertEquals(0, failures.get());
110         return totalElapsed;
111     }
112 
113     @DefaultLocale(language = "en", country = "US")
114     @Test
115     void test_changeDefault_Locale_DateInstance() {
116         final FastDateFormat format1 = FastDateFormat.getDateInstance(FastDateFormat.FULL, Locale.GERMANY);
117         final FastDateFormat format2 = FastDateFormat.getDateInstance(FastDateFormat.FULL);
118         Locale.setDefault(Locale.GERMANY);
119         final FastDateFormat format3 = FastDateFormat.getDateInstance(FastDateFormat.FULL);
120 
121         assertSame(Locale.GERMANY, format1.getLocale());
122         assertEquals(Locale.US, format2.getLocale());
123         assertSame(Locale.GERMANY, format3.getLocale());
124         assertNotSame(format1, format2);
125         assertNotSame(format2, format3);
126     }
127 
128     @DefaultLocale(language = "en", country = "US")
129     @Test
130     void test_changeDefault_Locale_DateTimeInstance() {
131         final FastDateFormat format1 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL, Locale.GERMANY);
132         final FastDateFormat format2 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL);
133         Locale.setDefault(Locale.GERMANY);
134         final FastDateFormat format3 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL);
135 
136         assertSame(Locale.GERMANY, format1.getLocale());
137         assertEquals(Locale.US, format2.getLocale());
138         assertSame(Locale.GERMANY, format3.getLocale());
139         assertNotSame(format1, format2);
140         assertNotSame(format2, format3);
141     }
142 
143     /*
144      * Only the cache methods need to be tested here.
145      * The print methods are tested by {@link FastDateFormat_PrinterTest}
146      * and the parse methods are tested by {@link FastDateFormat_ParserTest}
147      */
148     @Test
149     void test_getInstance() {
150         final FastDateFormat format1 = FastDateFormat.getInstance();
151         final FastDateFormat format2 = FastDateFormat.getInstance();
152         assertSame(format1, format2);
153     }
154 
155     @Test
156     void test_getInstance_String() {
157         final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy");
158         final FastDateFormat format2 = FastDateFormat.getInstance("MM-DD-yyyy");
159         final FastDateFormat format3 = FastDateFormat.getInstance("MM-DD-yyyy");
160         assertNotSame(format1, format2);
161         assertSame(format2, format3);
162         assertEquals("MM/DD/yyyy", format1.getPattern());
163         assertEquals(TimeZone.getDefault(), format1.getTimeZone());
164         assertEquals(TimeZone.getDefault(), format2.getTimeZone());
165         assertNotNull(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZ"));
166     }
167 
168     @DefaultLocale(language = "en", country = "US")
169     @Test
170     void test_getInstance_String_Locale() {
171         final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
172         final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy");
173         final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
174 
175         assertNotSame(format1, format2);
176         assertSame(format1, format3);
177         assertEquals(Locale.GERMANY, format1.getLocale());
178     }
179 
180     @DefaultLocale(language = "en", country = "US")
181     @DefaultTimeZone("America/New_York")
182     @Test
183     void test_getInstance_String_TimeZone() {
184 
185         final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy",
186                 TimeZones.getTimeZone("Atlantic/Reykjavik"));
187         final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy");
188         final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault());
189         final FastDateFormat format4 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault());
190         final FastDateFormat format5 = FastDateFormat.getInstance("MM-DD-yyyy", TimeZone.getDefault());
191         final FastDateFormat format6 = FastDateFormat.getInstance("MM-DD-yyyy");
192 
193         assertNotSame(format1, format2);
194         assertEquals(TimeZones.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
195         assertEquals(TimeZone.getDefault(), format2.getTimeZone());
196         assertSame(format3, format4);
197         assertNotSame(format3, format5);
198         assertNotSame(format4, format6);
199     }
200 
201     @DefaultLocale(language = "en", country = "US")
202     @DefaultTimeZone("America/New_York")
203     @Test
204     void test_getInstance_String_TimeZone_Locale() {
205         final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy",
206                 TimeZones.getTimeZone("Atlantic/Reykjavik"), Locale.GERMANY);
207         final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
208         final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy",
209                 TimeZone.getDefault(), Locale.GERMANY);
210 
211         assertNotSame(format1, format2);
212         assertEquals(TimeZones.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
213         assertEquals(TimeZone.getDefault(), format2.getTimeZone());
214         assertEquals(TimeZone.getDefault(), format3.getTimeZone());
215         assertEquals(Locale.GERMANY, format1.getLocale());
216         assertEquals(Locale.GERMANY, format2.getLocale());
217         assertEquals(Locale.GERMANY, format3.getLocale());
218     }
219 
220     @Test
221     void testCheckDefaults() {
222         final FastDateFormat format = FastDateFormat.getInstance();
223         final FastDateFormat medium = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT);
224         assertEquals(medium, format);
225 
226         final SimpleDateFormat sdf = new SimpleDateFormat();
227         assertEquals(sdf.toPattern(), format.getPattern());
228 
229         assertEquals(Locale.getDefault(), format.getLocale());
230         assertEquals(TimeZone.getDefault(), format.getTimeZone());
231     }
232 
233     @Test
234     void testCheckDifferingStyles() {
235         final FastDateFormat shortShort = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT, Locale.US);
236         final FastDateFormat shortLong = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.LONG, Locale.US);
237         final FastDateFormat longShort = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT, Locale.US);
238         final FastDateFormat longLong = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.LONG, Locale.US);
239 
240         assertNotEquals(shortShort, shortLong);
241         assertNotEquals(shortShort, longShort);
242         assertNotEquals(shortShort, longLong);
243         assertNotEquals(shortLong, longShort);
244         assertNotEquals(shortLong, longLong);
245         assertNotEquals(longShort, longLong);
246     }
247 
248     @Test
249     void testDateDefaults() {
250         assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, Locale.CANADA),
251                 FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
252 
253         assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZones.getTimeZone("America/New_York")),
254                 FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZones.getTimeZone("America/New_York"), Locale.getDefault()));
255 
256         assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG),
257                 FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
258     }
259 
260     @Test
261     void testLang1152() {
262         final TimeZone utc = FastTimeZone.getGmtTimeZone();
263         final Date date = new Date(Long.MAX_VALUE);
264 
265         String dateAsString = FastDateFormat.getInstance("yyyy-MM-dd", utc, Locale.US).format(date);
266         assertEquals("292278994-08-17", dateAsString);
267 
268         dateAsString = FastDateFormat.getInstance("dd/MM/yyyy", utc, Locale.US).format(date);
269         assertEquals("17/08/292278994", dateAsString);
270     }
271     @Test
272     void testLang1267() {
273         FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
274     }
275 
276     @Test
277     void testLang1641() {
278         assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT), FastDateFormat.getInstance(ISO_8601_DATE_FORMAT));
279         // commons-lang's GMT TimeZone
280         assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, FastTimeZone.getGmtTimeZone()),
281                 FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, FastTimeZone.getGmtTimeZone()));
282         // default TimeZone
283         assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getDefault()),
284                 FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getDefault()));
285         // TimeZones that are identical in every way except ID
286         assertNotSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZones.getTimeZone("Australia/Broken_Hill")),
287                 FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZones.getTimeZone("Australia/Yancowinna")));
288     }
289 
290     /**
291      * See LANG-1791 https://issues.apache.org/jira/browse/LANG-1791.
292      */
293     @Test
294     @DefaultTimeZone("America/Toronto")
295     void testLang1791() {
296         final Instant now = Instant.now();
297         final String pattern = "yyyyMMddHH";
298         final FastDateFormat gmtFormatter = FastDateFormat.getInstance(pattern, TimeZones.GMT);
299         final Calendar gmtCal = Calendar.getInstance(TimeZones.GMT);
300         final TimeZone timeZone = gmtCal.getTimeZone();
301         final Date date = gmtCal.getTime();
302         final String gmtString = gmtFormatter.format(gmtCal);
303         // Asserts formatting doesn't edit the given Calendar
304         assertCalendar(timeZone, date, gmtCal);
305         assertEquals(DateTimeFormatter.ofPattern(pattern).withZone(ZoneId.of("GMT")).format(now), gmtString);
306         final FastDateFormat defaultFormatter = FastDateFormat.getInstance(pattern);
307         final String defaultString = defaultFormatter.format(gmtCal);
308         // Asserts formatting doesn't edit the given Calendar
309         assertCalendar(timeZone, date, gmtCal);
310         assertEquals(DateTimeFormatter.ofPattern(pattern).withZone(ZoneId.systemDefault()).format(now), defaultString);
311     }
312 
313     /**
314      * According to LANG-954 (https://issues.apache.org/jira/browse/LANG-954) this is broken in Android 2.1.
315      */
316     @Test
317     void testLang954() {
318         final String pattern = "yyyy-MM-dd'T'";
319         FastDateFormat.getInstance(pattern);
320     }
321 
322     /**
323      * Tests [LANG-1767] FastDateFormat.parse can not recognize "CEST" Timezone.
324      *
325      * @throws ParseException Throws on test failure.
326      */
327     @Test
328     void testParseCentralEuropeanSummerTime() throws ParseException {
329         assertNotNull(FastDateFormat.getInstance("dd.MM.yyyy HH:mm:ss", Locale.GERMANY).parse("26.10.2014 02:00:00"));
330         assertNotNull(FastDateFormat.getInstance("dd.MM.yyyy HH:mm:ss z", Locale.US).parse("26.10.2014 02:00:00 CEST"));
331         assertNotNull(FastDateFormat.getInstance("dd.MM.yyyy HH:mm:ss z", Locale.GERMANY).parse("26.10.2014 02:00:00 MESZ"));
332     }
333 
334     @Test
335     void testParseSync() throws InterruptedException {
336         final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS";
337         final SimpleDateFormat inner = new SimpleDateFormat(pattern);
338         final Format sdf = new Format() {
339             private static final long serialVersionUID = 1L;
340 
341             @Override
342             public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition fieldPosition) {
343                 synchronized (this) {
344                     return inner.format(obj, toAppendTo, fieldPosition);
345                 }
346             }
347 
348             @Override
349             public Object parseObject(final String source, final ParsePosition pos) {
350                 synchronized (this) {
351                     return inner.parseObject(source, pos);
352                 }
353             }
354         };
355         final AtomicLongArray sdfTime = measureTime(sdf, sdf);
356 
357         final Format fdf = FastDateFormat.getInstance(pattern);
358         final AtomicLongArray fdfTime = measureTime(fdf, fdf);
359 
360         // System.out.println(">>FastDateFormatTest: FastDatePrinter:"+fdfTime.get(0)+" SimpleDateFormat:"+sdfTime.get(0));
361         // System.out.println(">>FastDateFormatTest: FastDateParser:"+fdfTime.get(1)+" SimpleDateFormat:"+sdfTime.get(1));
362     }
363 
364     @Test
365     void testStandaloneLongMonthForm() {
366         final TimeZone utc = FastTimeZone.getGmtTimeZone();
367         final Instant testInstant = LocalDate.of(1970, 9, 15).atStartOfDay(ZoneId.of("UTC")).toInstant();
368         final Date date = Date.from(testInstant);
369 
370         String dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, Locale.GERMAN).format(date);
371         assertEquals("1970-September-15", dateAsString);
372 
373         dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, FINNISH).format(date);
374         assertEquals("1970-syyskuu-15", dateAsString);
375 
376         dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, HUNGARIAN).format(date);
377         assertEquals("1970-szeptember-15", dateAsString);
378     }
379 
380     @Test
381     void testStandaloneShortMonthForm() {
382         final TimeZone utc = FastTimeZone.getGmtTimeZone();
383         final Instant testInstant = LocalDate.of(1970, 9, 15).atStartOfDay(ZoneId.of("UTC")).toInstant();
384         final Date date = Date.from(testInstant);
385 
386         String dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, Locale.GERMAN).format(date);
387         assertEquals("1970-Sep-15", dateAsString);
388 
389         dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, FINNISH).format(date);
390         assertEquals("1970-syys-15", dateAsString);
391 
392         dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, HUNGARIAN).format(date);
393         assertEquals("1970-szept.-15", dateAsString);
394     }
395 
396     @Test
397     void testTimeDateDefaults() {
398         assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, Locale.CANADA),
399                 FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.CANADA));
400 
401         assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZones.getTimeZone("America/New_York")),
402                 FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZones.getTimeZone("America/New_York"), Locale.getDefault()));
403 
404         assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM),
405                 FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.getDefault()));
406     }
407 
408     @Test
409     void testTimeDefaults() {
410         assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, Locale.CANADA),
411                 FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
412 
413         assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZones.getTimeZone("America/New_York")),
414                 FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZones.getTimeZone("America/New_York"), Locale.getDefault()));
415 
416         assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG),
417                 FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
418     }
419 }