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.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotEquals;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.text.ParseException;
25  import java.text.ParsePosition;
26  import java.text.SimpleDateFormat;
27  import java.util.Date;
28  import java.util.Locale;
29  import java.util.TimeZone;
30  import java.util.stream.Stream;
31  
32  import org.apache.commons.lang3.AbstractLangTest;
33  import org.junit.jupiter.params.ParameterizedTest;
34  import org.junit.jupiter.params.provider.Arguments;
35  import org.junit.jupiter.params.provider.MethodSource;
36  
37  /**
38   * Compare FastDateParser with SimpleDateFormat
39   */
40  class FastDateParserSDFTest extends AbstractLangTest {
41  
42      private static final TimeZone timeZone = TimeZone.getDefault();
43  
44      public static Stream<Arguments> data() {
45          // @formatter:off
46          return Stream.of(
47                  // General Time zone tests
48                  Arguments.of("z yyyy", "GMT 2010",       Locale.UK, true), // no offset specified, but this is allowed as a TimeZone name
49                  Arguments.of("z yyyy", "GMT-123 2010",   Locale.UK, false),
50                  Arguments.of("z yyyy", "GMT-1234 2010",  Locale.UK, false),
51                  Arguments.of("z yyyy", "GMT-12:34 2010", Locale.UK, true),
52                  Arguments.of("z yyyy", "GMT-1:23 2010",  Locale.UK, true),
53                  // RFC 822 tests
54                  Arguments.of("z yyyy", "-1234 2010",     Locale.UK, true),
55                  Arguments.of("z yyyy", "-12:34 2010",    Locale.UK, false),
56                  Arguments.of("z yyyy", "-123 2010",      Locale.UK, false),
57                  // year tests
58                  Arguments.of("MM/dd/yyyy", "01/11/12",  Locale.UK, true),
59                  Arguments.of("MM/dd/yy", "01/11/12",    Locale.UK, true),
60  
61                  // LANG-1089
62                  Arguments.of("HH", "00",    Locale.UK, true), // Hour in day (0-23)
63                  Arguments.of("KK", "00",    Locale.UK, true), // Hour in am/pm (0-11)
64                  Arguments.of("hh", "00",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
65                  Arguments.of("kk", "00",    Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
66  
67                  Arguments.of("HH", "01",    Locale.UK, true), // Hour in day (0-23)
68                  Arguments.of("KK", "01",    Locale.UK, true), // Hour in am/pm (0-11)
69                  Arguments.of("hh", "01",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
70                  Arguments.of("kk", "01",    Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
71  
72                  Arguments.of("HH", "11",    Locale.UK, true), // Hour in day (0-23)
73                  Arguments.of("KK", "11",    Locale.UK, true), // Hour in am/pm (0-11)
74                  Arguments.of("hh", "11",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
75                  Arguments.of("kk", "11",    Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
76  
77                  Arguments.of("HH", "12",    Locale.UK, true), // Hour in day (0-23)
78                  Arguments.of("KK", "12",    Locale.UK, true), // Hour in am/pm (0-11)
79                  Arguments.of("hh", "12",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
80                  Arguments.of("kk", "12",    Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
81  
82                  Arguments.of("HH", "13",    Locale.UK, true), // Hour in day (0-23)
83                  Arguments.of("KK", "13",    Locale.UK, true), // Hour in am/pm (0-11)
84                  Arguments.of("hh", "13",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
85                  Arguments.of("kk", "13",    Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
86  
87                  Arguments.of("HH", "23",    Locale.UK, true), // Hour in day (0-23)
88                  Arguments.of("KK", "23",    Locale.UK, true), // Hour in am/pm (0-11)
89                  Arguments.of("hh", "23",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
90                  Arguments.of("kk", "23",    Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
91  
92                  Arguments.of("HH", "24",    Locale.UK, true), // Hour in day (0-23)
93                  Arguments.of("KK", "24",    Locale.UK, true), // Hour in am/pm (0-11)
94                  Arguments.of("hh", "24",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
95                  Arguments.of("kk", "24",    Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
96  
97                  Arguments.of("HH", "25",    Locale.UK, true), // Hour in day (0-23)
98                  Arguments.of("KK", "25",    Locale.UK, true), // Hour in am/pm (0-11)
99                  Arguments.of("hh", "25",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
100                 Arguments.of("kk", "25",    Locale.UK, true), // Hour in day (1-24), i.e. midnight is 24, not 0
101 
102                 Arguments.of("HH", "48",    Locale.UK, true), // Hour in day (0-23)
103                 Arguments.of("KK", "48",    Locale.UK, true), // Hour in am/pm (0-11)
104                 Arguments.of("hh", "48",    Locale.UK, true), // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
105                 Arguments.of("kk", "48",    Locale.UK, true)  // Hour in day (1-24), i.e. midnight is 24, not 0
106         );
107         // @formatter:on
108     }
109 
110     private void checkParse(final String formattedDate, final String format, final Locale locale, final boolean valid) {
111         final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
112         sdf.setTimeZone(timeZone);
113         final DateParser fdf = new FastDateParser(format, timeZone, locale);
114         Date expectedTime = null;
115         Class<?> sdfE = null;
116         try {
117             expectedTime = sdf.parse(formattedDate);
118             if (!valid) {
119                 // Error in test data
120                 throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime);
121             }
122         } catch (final ParseException e) {
123             if (valid) {
124                 // Error in test data
125                 throw new RuntimeException("Test data error: expected SDF parse to succeed, but got " + e);
126             }
127             sdfE = e.getClass();
128         }
129         Date actualTime = null;
130         Class<?> fdfE = null;
131         try {
132             actualTime = fdf.parse(formattedDate);
133             // failure in test
134             assertTrue(valid, "Expected FDP parse to fail, but got " + actualTime);
135         } catch (final ParseException e) {
136             // failure in test
137             assertFalse(valid, "Expected FDP parse to succeed, but got " + e);
138             fdfE = e.getClass();
139         }
140         if (valid) {
141             assertEquals(expectedTime, actualTime, locale + " " + formattedDate + "\n");
142         } else {
143             assertEquals(sdfE, fdfE, locale + " " + formattedDate + " expected same Exception ");
144         }
145     }
146 
147     private void checkParsePosition(final String formattedDate, final String format, final Locale locale, final boolean valid) {
148         final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
149         sdf.setTimeZone(timeZone);
150         final DateParser fdf = new FastDateParser(format, timeZone, locale);
151 
152         final ParsePosition sdfP = new ParsePosition(0);
153         final Date expectedTime = sdf.parse(formattedDate, sdfP);
154         final int sdferrorIndex = sdfP.getErrorIndex();
155         if (valid) {
156             assertEquals(-1, sdferrorIndex, "Expected SDF error index -1 ");
157             final int endIndex = sdfP.getIndex();
158             final int length = formattedDate.length();
159             if (endIndex != length) {
160                 // Error in test data
161                 throw new RuntimeException("Test data error: expected SDF parse to consume entire string; endindex " + endIndex + " != " + length);
162             }
163         } else {
164             final int errorIndex = sdfP.getErrorIndex();
165             if (errorIndex == -1) {
166                 throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime);
167             }
168         }
169 
170         final ParsePosition fdfP = new ParsePosition(0);
171         final Date actualTime = fdf.parse(formattedDate, fdfP);
172         final int fdferrorIndex = fdfP.getErrorIndex();
173         if (valid) {
174             assertEquals(-1, fdferrorIndex, "Expected FDF error index -1 ");
175             final int endIndex = fdfP.getIndex();
176             final int length = formattedDate.length();
177             assertEquals(length, endIndex, "Expected FDF to parse full string " + fdfP);
178             assertEquals(expectedTime, actualTime, locale + " " + formattedDate + "\n");
179         } else {
180             assertNotEquals(-1, fdferrorIndex, "Test data error: expected FDF parse to fail, but got " + actualTime);
181             assertTrue(sdferrorIndex - fdferrorIndex <= 4, "FDF error index (" + fdferrorIndex + ") should approximate SDF index (" + sdferrorIndex + ")");
182         }
183     }
184 
185     @ParameterizedTest
186     @MethodSource("data")
187     void testLowerCase(final String format, final String input, final Locale locale, final boolean valid) {
188         checkParse(input.toLowerCase(locale), format, locale, valid);
189     }
190 
191     @ParameterizedTest
192     @MethodSource("data")
193     void testLowerCasePP(final String format, final String input, final Locale locale, final boolean valid) {
194         checkParsePosition(input.toLowerCase(locale), format, locale, valid);
195     }
196 
197     @ParameterizedTest
198     @MethodSource("data")
199     void testOriginal(final String format, final String input, final Locale locale, final boolean valid) {
200         checkParse(input, format, locale, valid);
201     }
202 
203     @ParameterizedTest
204     @MethodSource("data")
205     void testOriginalPP(final String format, final String input, final Locale locale, final boolean valid) {
206         checkParsePosition(input, format, locale, valid);
207     }
208 
209     @ParameterizedTest
210     @MethodSource("data")
211     void testUpperCase(final String format, final String input, final Locale locale, final boolean valid) {
212         checkParse(input.toUpperCase(locale), format, locale, valid);
213     }
214     @ParameterizedTest
215     @MethodSource("data")
216     void testUpperCasePP(final String format, final String input, final Locale locale, final boolean valid) {
217         checkParsePosition(input.toUpperCase(locale), format, locale, valid);
218     }
219 }