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