View Javadoc
1   package emissary.util;
2   
3   import emissary.test.core.junit5.UnitTest;
4   
5   import jakarta.annotation.Nullable;
6   import org.junit.jupiter.api.BeforeAll;
7   import org.junit.jupiter.api.Test;
8   
9   import java.time.LocalDateTime;
10  import java.time.ZoneOffset;
11  
12  import static org.junit.jupiter.api.Assertions.assertEquals;
13  import static org.junit.jupiter.api.Assertions.assertNull;
14  
15  class DateTimeFormatParserTest extends UnitTest {
16  
17      private static final long EXPECTED_FULL = 1451931630; // 2016-01-04 18:20:30
18      private static final long EXPECTED_NO_TIME = 1451865600; // 2016-01-04 00:00:00
19      private static final long EXPECTED_NO_SECS = 1451931600; // 2016-01-04 18:20:00
20      private static final long EXPECTED_NO_HR_SEC = 1451866800; // 2016-01-04 00:20:00
21      private static final long EXPECTED_ALT_TIME = 1451894523; // 2016-01-04 08:02:03
22      private static final long EXPECTED_ALT_TIME_NO_SECS = 1451894520; // 2016-01-04 08:02:00
23  
24      @BeforeAll
25      public static void setupClass() {
26          // "warm-up" the class, but this runs before UnitTest has
27          // a chance to setup, so do that first
28          UnitTest.setupSystemProperties();
29      }
30  
31      /**
32       * Test the date string against the expected output
33       *
34       * @param date the string representation of a date
35       * @param expected the expected parsed and formatted date
36       * @param msg the error message to display if the test fails
37       */
38      private static void test(@Nullable String date, long expected, String msg) {
39          LocalDateTime unknownParse = DateTimeFormatParser.parseDate(date, false);
40          assertEquals(expected, unknownParse == null ? 0L : unknownParse.toEpochSecond(ZoneOffset.UTC), "Error on: " + msg);
41      }
42  
43      /**
44       * Test the date string against the expected output
45       *
46       * @param date the string representation of a date
47       * @param expected the expected parsed and formatted date
48       */
49      private static void test(String date, long expected) {
50          long unknownParse = DateTimeFormatParser.parseDate(date, false).toEpochSecond(ZoneOffset.UTC);
51          assertEquals(expected, unknownParse, "Flexible parse failed");
52  
53          long knownParse = DateTimeFormatParser.parseDate(date, false).toEpochSecond(ZoneOffset.UTC);
54          assertEquals(expected, knownParse, "Manual parse failed");
55  
56          // this is as close as I can get to testing the expected date format
57          assertEquals(unknownParse, knownParse, "Parsed date/times are not the same");
58      }
59  
60      /**
61       * Three-letter time zone IDs often point to multiple timezones. Java 8 uses the timezone over the offset causing
62       * problems with the datetime in verifies. Java 9 fixes this issue. Since 9 isn't released and, even if it was, it would
63       * take some time to transition, a regex strips out the short timezone if there is an offset present.
64       * <p>
65       * See {@link java.util.TimeZone} and {@link java.time.ZoneId#SHORT_IDS}
66       */
67      @Test
68      void stripThreeLetterTimeZonesWhenThereIsAnOffset() {
69          // without offset we expect the default ZoneId
70          test("Mon, 4 Jan 2016 13:20:30 EST", EXPECTED_FULL);
71  
72          // see if we can ignore short timezone
73          test("Mon, 4 Jan 2016 18:20:30 +0000(EST)", EXPECTED_FULL);
74          test("Mon, 4 Jan 2016 16:20:30 -0200(EST)", EXPECTED_FULL);
75          test("Mon, 4 Jan 2016 18:20:30 +0000 (EST)", EXPECTED_FULL);
76          test("Mon, 4 Jan 2016 18:20:30 +0000EST", EXPECTED_FULL);
77          test("Mon, 4 Jan 2016 18:20:30 +0000 EST", EXPECTED_FULL);
78          test("Mon, 4 Jan 2016 18:20:30 EST+0000", EXPECTED_FULL);
79          test("Mon, 4 Jan 2016 18:20:30 EST +0000", EXPECTED_FULL);
80          test("Mon, 4 Jan 2016 18:20:30EST+0000", EXPECTED_FULL);
81          test("Mon, 4 Jan 2016 18:20:30EST +0000", EXPECTED_FULL);
82      }
83  
84      @Test
85      void parse_yyyyMMddTHHmmssSSSX() {
86          test("2016-01-04T18:20:30.000Z", EXPECTED_FULL);
87          test("2016-01-04T18:20:30Z", EXPECTED_FULL);
88          test("2016-01-04T18:20:30+00:00", EXPECTED_FULL);
89          test("2016-01-04T13:20:30-05:00", EXPECTED_FULL);
90          test("2016-01-04T18:20:30+0000", EXPECTED_FULL);
91          test("2016-01-04T13:20:30-0500", EXPECTED_FULL);
92          test("2016-01-04 18:20:30", EXPECTED_FULL);
93          test("2016-01-04 18:20:30+0000", EXPECTED_FULL);
94          test("2016-01-04 18:20:30 +0000", EXPECTED_FULL);
95          test("2016-01-04 18:20:30 GMT+0000", EXPECTED_FULL);
96          test("2016-01-04 18:20:30 GMT", EXPECTED_FULL);
97          test("2016-01-04 18:20", EXPECTED_NO_SECS);
98          test("2016-01-04/18/20", EXPECTED_NO_SECS);
99          test("2016-01-04", EXPECTED_NO_TIME);
100         test("2016-1-4T8:2:3.000Z", EXPECTED_ALT_TIME);
101         test("2016-1-4 8:2:3.000", EXPECTED_ALT_TIME);
102         test("2016-1-4 8:2:3", EXPECTED_ALT_TIME);
103         test("2016-1-4T8:2:3Z", EXPECTED_ALT_TIME);
104         test("2016-01-04T18:20:30", EXPECTED_FULL);
105         test("2016-1-4T8:2:3+00:00", EXPECTED_ALT_TIME);
106         test("2016-1-4T3:2:3-05:00", EXPECTED_ALT_TIME);
107         test("2016-1-4T8:2:3+0000", EXPECTED_ALT_TIME);
108         test("2016-1-4T3:2:3-0500", EXPECTED_ALT_TIME);
109         test("2016-01-04 18:20", EXPECTED_NO_SECS);
110         test("2016-01-04 18:20 GMT", EXPECTED_NO_SECS);
111         test("2016-01-04 18:20 +00:00", EXPECTED_NO_SECS);
112         test("2016-01-04 18:20 +0000", EXPECTED_NO_SECS);
113         test("2016-1-4 8:2", EXPECTED_ALT_TIME_NO_SECS);
114         test("2016-01-04", EXPECTED_NO_TIME);
115         test("2016-1-4", EXPECTED_NO_TIME);
116     }
117 
118     @Test
119     void parse_EdMMMyyHmmssZ() {
120         test("Mon, 4 Jan 16 18:20:30 +0000", EXPECTED_FULL);
121         test("Mon, 4 Jan 16 18:20:30+0000", EXPECTED_FULL);
122         test("Mon, 4 Jan 16 18:20:30 GMT+0000", EXPECTED_FULL);
123         test("Mon, 4 Jan 16 13:20:30 -0500", EXPECTED_FULL);
124         test("Mon, 4 Jan 16 18:20:30", EXPECTED_FULL);
125         test("Mon, 4 Jan 16 8:20:30 -1000", EXPECTED_FULL);
126         test("Mon, 4 Jan 16", EXPECTED_NO_TIME);
127         test("Mon,4 Jan 16", EXPECTED_NO_TIME);
128         test("Mon, 4Jan16", EXPECTED_NO_TIME);
129         test("4 Jan 16 18:20 UTC", EXPECTED_NO_SECS);
130         test("4 Jan 16 13:20 -0500", EXPECTED_NO_SECS);
131         test("4 Jan 16 18:20", EXPECTED_NO_SECS);
132         test("4 Jan 16 18:20 +0000", EXPECTED_NO_SECS);
133         test("4 Jan 16 18:20+0000", EXPECTED_NO_SECS);
134         test("4 Jan 16 18:20 +00:00", EXPECTED_NO_SECS);
135         test("4 Jan 16", EXPECTED_NO_TIME);
136         test("4Jan16", EXPECTED_NO_TIME);
137     }
138 
139 
140     @Test
141     void parse_EdMMMyyyyKmmssaZ() {
142         test("Mon, 4 Jan 2016 06:20:30 PM +0000", EXPECTED_FULL);
143         test("Mon, 4 Jan 2016 06:20:30 PM GMT+0000", EXPECTED_FULL);
144         test("Mon, 4 Jan 2016 06:20:30 PM GMT", EXPECTED_FULL);
145         test("Mon, 4 Jan 2016 01:20:30 PM -0500", EXPECTED_FULL);
146         test("Mon, 4 Jan 2016 1:20:30 PM -0500", EXPECTED_FULL);
147         test("Sun, 3 Jan 2016 7:20:00 PM -0500", EXPECTED_NO_HR_SEC);
148         test("Mon, 4 Jan 2016 0:20:00 AM", EXPECTED_NO_HR_SEC);
149         test("Mon,4 Jan 2016 06:20:30 PM", EXPECTED_FULL);
150         test("4 Jan 2016 06:20:30 PM", EXPECTED_FULL);
151         test("4 Jan 2016 06:20:30 PM +0000", EXPECTED_FULL);
152     }
153 
154     @Test
155     void parse_EdMMMyyyyHmmssZz() {
156         test("Mon, 4 Jan 2016 18:20:30 +0000 (Europe/Dublin)", EXPECTED_FULL);
157         test("Mon, 4 Jan 2016 18:20:30 +0000 (Zulu)", EXPECTED_FULL);
158         test("Mon, 4 Jan 2016 18:20:30 +0000 (UTC)", EXPECTED_FULL);
159         test("Mon, 4 Jan 2016 18:20:30 +0000 (GMT)", EXPECTED_FULL);
160         test("Mon, 4 Jan 2016 13:20:30 -0500 (EST)", EXPECTED_FULL);
161         test("Mon, 4 Jan 2016 13:20:30 -0500 (US/Eastern)", EXPECTED_FULL);
162         test("Mon, 4 Jan 2016 18:20:30 +0000", EXPECTED_FULL);
163         test("Mon, 4 Jan 2016 8:20:30 -1000", EXPECTED_FULL);
164         test("Mon, 4 Jan 2016 13:20:30 -0500", EXPECTED_FULL);
165         test("Mon, 4 Jan 2016 18:20:30 UTC", EXPECTED_FULL);
166         test("Mon, 4 Jan 2016 18:20:30 GMT", EXPECTED_FULL);
167         test("Mon, 4 Jan 2016 18:20:30 GMT+0000", EXPECTED_FULL);
168         test("Mon, 4 Jan 2016 13:20:30 -0500", EXPECTED_FULL);
169         test("Mon, 4 Jan 2016 0:20:00 +0000", EXPECTED_NO_HR_SEC);
170         test("Mon, 4 Jan 2016 0:20:00 AM", EXPECTED_NO_HR_SEC);
171         test("Sun, 3 Jan 2016 19:20:00 PM -0500", EXPECTED_NO_HR_SEC);
172         test("Mon, 4 Jan 2016 18:20:30", EXPECTED_FULL);
173         test("Mon, 4 Jan 2016 18:20 +0000", EXPECTED_NO_SECS);
174         test("Mon, 4 Jan 2016 13:20 -0500", EXPECTED_NO_SECS);
175         test("Mon, 4 Jan. 2016 18:20:30 +0000", EXPECTED_FULL);
176         test("Mon, 4 Jan. 2016 13:20:30 -0500", EXPECTED_FULL);
177         test("Mon 4 Jan. 2016 18:20:30 +0000", EXPECTED_FULL);
178         test("Mon 4 Jan. 2016 18:20:30+0000", EXPECTED_FULL);
179         test("Mon 4 Jan. 2016 13:20:30 -0500", EXPECTED_FULL);
180         test("Mon, 4 Jan, 2016 18:20:30 PM", EXPECTED_FULL);
181         test("Mon, 4 Jan, 2016", EXPECTED_NO_TIME);
182         test("Mon 4Jan2016", EXPECTED_NO_TIME);
183         test("Mon, 4Jan2016", EXPECTED_NO_TIME);
184         test("4 Jan 2016 18:20:30 +0000", EXPECTED_FULL);
185         test("4 Jan 2016 18:20:30+0000", EXPECTED_FULL);
186         test("4 Jan 2016 13:20:30 -0500", EXPECTED_FULL);
187         test("4 Jan 2016 18:20 UTC", EXPECTED_NO_SECS);
188         test("4 Jan 2016 13:20 -0500", EXPECTED_NO_SECS);
189         test("4 Jan 2016 18:20", EXPECTED_NO_SECS);
190         test("4 Jan 2016", EXPECTED_NO_TIME);
191         test("4Jan2016", EXPECTED_NO_TIME);
192     }
193 
194     @Test
195     void parse_EMMMdyyyyKmma() {
196         test("Mon, Jan 4, 2016 06:20 PM", EXPECTED_NO_SECS);
197         test("Mon, Jan 4, 2016 6:20 PM", EXPECTED_NO_SECS);
198         test("Mon, Jan 04, 2016 06:20 PM", EXPECTED_NO_SECS);
199         test("Mon Jan 04, 2016 06:20 PM", EXPECTED_NO_SECS);
200         test("Mon Jan 04 2016 06:20 PM", EXPECTED_NO_SECS);
201         test("Mon,Jan 04 2016 06:20 PM", EXPECTED_NO_SECS);
202     }
203 
204     @Test
205     void parse_EMMMdyyyyHmmssz() {
206         test("Mon, Jan 4, 2016 18:20:30 UTC", EXPECTED_FULL);
207         test("Mon, Jan 04, 2016 18:20:30 UTC", EXPECTED_FULL);
208         test("Mon, Jan 4, 2016 13:20:30 -0500", EXPECTED_FULL);
209         test("Mon, Jan 04, 2016 13:20:30 -0500", EXPECTED_FULL);
210         test("Mon, Jan 4, 2016 18:20:30", EXPECTED_FULL);
211         test("Mon, Jan 04, 2016 18:20:30", EXPECTED_FULL);
212         test("Mon Jan 04 2016 18:20:30", EXPECTED_FULL);
213         test("Mon Jan 04 2016 18:20:30 UTC", EXPECTED_FULL);
214         test("Mon Jan 04 2016 13:20:30 -0500", EXPECTED_FULL);
215         test("Mon Jan 04 2016 8:20:30 -1000", EXPECTED_FULL);
216         test("Mon, Jan 4, 2016, 18:20:30 PM +0000", EXPECTED_FULL);
217         test("Mon, Jan 4, 2016, 18:20:30 PM GMT+0000", EXPECTED_FULL);
218         test("Mon, Jan 4, 2016, 13:20:30 PM -0500", EXPECTED_FULL);
219         test("Mon, Jan 4, 2016, 18:20:30 PM UTC", EXPECTED_FULL);
220         test("Mon, Jan 4, 2016, 18:20:30 PM", EXPECTED_FULL);
221         test("Mon, Jan 4, 2016, 18:20:30", EXPECTED_FULL);
222         test("Mon, Jan 4, 2016", EXPECTED_NO_TIME);
223         test("Jan 04, 2016 18:20:30", EXPECTED_FULL);
224         test("Jan 04, 2016, 18:20:30", EXPECTED_FULL);
225         test("Jan 04 2016 18:20:30", EXPECTED_FULL);
226         test("Jan 04 2016 18:20:30 PM", EXPECTED_FULL);
227         test("Jan 04 2016 18:20:30 +0000", EXPECTED_FULL);
228         test("Jan 04 2016 18:20:30 GMT", EXPECTED_FULL);
229         test("Jan 04 2016 18:20:30", EXPECTED_FULL);
230         test("Jan 4 2016 18:20:30", EXPECTED_FULL);
231         test("Jan 4, 2016", EXPECTED_NO_TIME);
232     }
233 
234     @Test
235     void parse_EddMMMyyyyHmmssZ() {
236         test("Mon 04-Jan-2016 18:20:30 +0000", EXPECTED_FULL);
237         test("Mon 04-Jan-2016 18:20:30 GMT+0000", EXPECTED_FULL);
238         test("Mon 04-Jan-2016 18:20:30 GMT", EXPECTED_FULL);
239         test("Mon 04-Jan-2016 13:20:30 -0500", EXPECTED_FULL);
240         test("Mon 04-Jan-2016 18:20:30", EXPECTED_FULL);
241         test("Mon 04-Jan-2016 8:20:30 -1000", EXPECTED_FULL);
242         test("Mon 04-Jan-2016", EXPECTED_NO_TIME);
243         test("Mon, 04-Jan-2016", EXPECTED_NO_TIME);
244         test("Mon,04-Jan-2016", EXPECTED_NO_TIME);
245         test("04-Jan-2016 18:20:30 +0000", EXPECTED_FULL);
246         test("04-Jan-2016 18:20:30 GMT+0000", EXPECTED_FULL);
247         test("04-Jan-2016 18:20:30 GMT", EXPECTED_FULL);
248         test("04-Jan-2016 13:20:30 -0500", EXPECTED_FULL);
249         test("04-Jan-2016 18:20:30", EXPECTED_FULL);
250         test("04-Jan-2016", EXPECTED_NO_TIME);
251     }
252 
253     @Test
254     void parse_EMMMdHHmmsszzzyyyy() {
255         test("Mon Jan 04 18:20:30 GMT 2016", EXPECTED_FULL);
256         test("Mon Jan 04 13:20:30 EST 2016", EXPECTED_FULL);
257         test("Mon Jan 04 13:20 EST 2016", EXPECTED_NO_SECS);
258         test("Mon Jan 04 18:20:30 2016", EXPECTED_FULL);
259         test("Mon Jan 4 18:20:30 2016", EXPECTED_FULL);
260         test("Mon Jan 4 18:20 2016", EXPECTED_NO_SECS);
261         test("Mon, Jan 4 18:20 2016", EXPECTED_NO_SECS);
262         test("Mon,Jan 4 18:20 2016", EXPECTED_NO_SECS);
263         test("Jan 4 18:20 2016", EXPECTED_NO_SECS);
264         test("Jan 04 18:20:30 GMT 2016", EXPECTED_FULL);
265         test("Jan 04 13:20:30 EST 2016", EXPECTED_FULL);
266         test("Jan 04 13:20 EST 2016", EXPECTED_NO_SECS);
267         test("Jan 04 18:20:30 2016", EXPECTED_FULL);
268     }
269 
270     @Test
271     void parse_MdyyKmma() {
272         test("01/04/16 06:20 PM", EXPECTED_NO_SECS);
273         test("1/4/16 6:20 PM", EXPECTED_NO_SECS);
274         test("01/04/16 06:20:30 PM", EXPECTED_FULL);
275         test("1/4/16 06:20:30 PM", EXPECTED_FULL);
276         test("01/04/16 00:20 AM", EXPECTED_NO_HR_SEC);
277         test("01/04/1606:20 PM", EXPECTED_NO_SECS);
278         test("01/04/1606:20:30 PM", EXPECTED_FULL);
279         test("01/04/1600:20 AM", EXPECTED_NO_HR_SEC);
280     }
281 
282     @Test
283     void parse_MdyyHmmssaz() {
284         test("01/04/16 18:20:30 GMT", EXPECTED_FULL);
285         test("1/4/16 18:20:30 GMT", EXPECTED_FULL);
286         test("01/04/16 18:20:30 PM +0000", EXPECTED_FULL);
287         test("1/4/16 18:20:30 PM +0000", EXPECTED_FULL);
288         test("1/4/16 8:20:30 AM -1000", EXPECTED_FULL);
289         test("1/4/16 8:20:30 -1000", EXPECTED_FULL);
290         test("01/04/16 18:20:30 PM GMT", EXPECTED_FULL);
291         test("1/4/16 18:20:30 PM GMT", EXPECTED_FULL);
292         test("01/04/16 18:20:30 PM GMT+0000", EXPECTED_FULL);
293         test("1/4/16 18:20:30 PM GMT+0000", EXPECTED_FULL);
294         test("01/04/16 18:20:30 PM", EXPECTED_FULL);
295         test("01/04/16 18:20:30", EXPECTED_FULL);
296         test("01/04/16 18:20", EXPECTED_NO_SECS);
297         test("1/4/16 18:20", EXPECTED_NO_SECS);
298         test("01/04/1618:20:30GMT", EXPECTED_FULL);
299         test("01/04/1618:20:30PM+0000", EXPECTED_FULL);
300         test("01/04/1618:20:30PMGMT", EXPECTED_FULL);
301         test("01/04/1618:20:30PM", EXPECTED_FULL);
302         test("01/04/1618:20:30", EXPECTED_FULL);
303         test("01/04/1618:20", EXPECTED_NO_SECS);
304     }
305 
306     @Test
307     void parse_HHmmddMMyyyy() {
308         test("04-01-2016", EXPECTED_NO_TIME);
309         test("04.01.2016", EXPECTED_NO_TIME);
310         test("04/01/2016", EXPECTED_NO_TIME);
311         test("182004012016", EXPECTED_NO_SECS);
312     }
313 
314     @Test
315     void parse_yyyyMMddHHmmssS() {
316         test("2016/01/04 18:20:30.0", EXPECTED_FULL);
317         test("2016/01/0418:20:30.0", EXPECTED_FULL);
318         test("2016/01/041820300", EXPECTED_FULL);
319         test("2016/01/04182030", EXPECTED_FULL);
320         test("2016/01/04 18:20:30", EXPECTED_FULL);
321         test("2016/01/04 18:20:30 +0000", EXPECTED_FULL);
322         test("2016/01/04 18:20:30 GMT+0000", EXPECTED_FULL);
323         test("2016/01/04 18:20:30 GMT", EXPECTED_FULL);
324         test("2016/01/04 18:20:30+0000", EXPECTED_FULL);
325         test("2016/01/0418:20:30+0000", EXPECTED_FULL);
326         test("2016/01/04 182030", EXPECTED_FULL);
327         test("2016/01/041820", EXPECTED_NO_SECS);
328         test("2016/01/04", EXPECTED_NO_TIME);
329     }
330 
331     @Test
332     void parse_yyyy_MM_ddHHmmssS() {
333         test("2016:01:04 18:20:30.0", EXPECTED_FULL);
334         test("2016:01:04 18:20:30", EXPECTED_FULL);
335         test("2016:01:04 18:20:30 +0000", EXPECTED_FULL);
336         test("2016:01:04 18:20:30 GMT+0000", EXPECTED_FULL);
337         test("2016:01:04 18:20:30 GMT", EXPECTED_FULL);
338         test("2016:01:04 18:20:30+0000", EXPECTED_FULL);
339         test("2016:01:04 18:20", EXPECTED_NO_SECS);
340         test("2016:01:04", EXPECTED_NO_TIME);
341     }
342 
343     @Test
344     void parse_yyyyMMddHHmmss() {
345         test("20160104182030", EXPECTED_FULL);
346     }
347 
348     @Test
349     void parse_yyyyMMdd() {
350         test("20160104", EXPECTED_NO_TIME);
351     }
352 
353     @Test
354     void parse_yyyyDDDHHmmss() {
355         test("2016004182030", EXPECTED_FULL);
356     }
357 
358     @Test
359     void parse_yyyyDDDHHmm() {
360         test("20160041820", EXPECTED_NO_SECS);
361     }
362 
363     @Test
364     void parse_yyyyDDD() {
365         test("2016004", EXPECTED_NO_TIME);
366     }
367 
368     @Test
369     void parse_yyyy_DDD() {
370         test("2016-004", EXPECTED_NO_TIME);
371     }
372 
373 
374     @Test
375     void testCleanDateString() {
376         // test("2016-01-04 18:20<br>", EXPECTED_NO_SECS, "HTML");
377         test("2016-01-04\t\t18:20", EXPECTED_NO_SECS, "TABS");
378         test("2016-01-04        18:20", EXPECTED_NO_SECS, "SPACES");
379         test("2016-01-04 18:20=0D", EXPECTED_NO_SECS, "qp'ified ending");
380     }
381 
382 
383     @Test
384     void testBad() {
385         test("", 0L, "EMPTY");
386         test(null, 0L, "NULL");
387         assertNull(DateTimeFormatParser.parseDate("1234", false));
388         assertNull(DateTimeFormatParser.parseDate("1234", false));
389         test("17.Mar.2016", 0L, "UNKNOWN");
390         test("Mon, 2 Feb 2017 06:20:30 PM +0000", 0L, "UNKNOWN");
391     }
392 }