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  
18  package org.apache.commons.lang3.time;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotEquals;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.lang.reflect.Constructor;
29  import java.lang.reflect.Modifier;
30  import java.time.Duration;
31  import java.util.Calendar;
32  import java.util.TimeZone;
33  
34  import org.apache.commons.lang3.AbstractLangTest;
35  import org.junit.jupiter.api.Test;
36  import org.junitpioneer.jupiter.DefaultTimeZone;
37  
38  /**
39   * Tests {@link DurationFormatUtils}.
40   * <p>
41   * NOT THREAD-SAFE.
42   * </p>
43   */
44  public class DurationFormatUtilsTest extends AbstractLangTest {
45  
46      private static final int FOUR_YEARS = 365 * 3 + 366;
47  
48      private void assertEqualDuration(final String expected, final int[] start, final int[] end, final String format) {
49          assertEqualDuration(null, expected, start, end, format);
50      }
51  
52      private void assertEqualDuration(final String message, final String expected, final int[] start, final int[] end, final String format) {
53          final Calendar cal1 = Calendar.getInstance();
54          cal1.set(start[0], start[1], start[2], start[3], start[4], start[5]);
55          cal1.set(Calendar.MILLISECOND, 0);
56          final Calendar cal2 = Calendar.getInstance();
57          cal2.set(end[0], end[1], end[2], end[3], end[4], end[5]);
58          cal2.set(Calendar.MILLISECOND, 0);
59          final long milli1 = cal1.getTime().getTime();
60          final long milli2 = cal2.getTime().getTime();
61          final String result = DurationFormatUtils.formatPeriod(milli1, milli2, format);
62          if (message == null) {
63              assertEquals(expected, result);
64          } else {
65              assertEquals(expected, result, message);
66          }
67      }
68  
69      private void bruteForce(final int year, final int month, final int day, final String format, final int calendarType) {
70          final String msg = year + "-" + month + "-" + day + " to ";
71          final Calendar c = Calendar.getInstance();
72          c.set(year, month, day, 0, 0, 0);
73          final int[] array1 = { year, month, day, 0, 0, 0 };
74          final int[] array2 = { year, month, day, 0, 0, 0 };
75          for (int i=0; i < FOUR_YEARS; i++) {
76              array2[0] = c.get(Calendar.YEAR);
77              array2[1] = c.get(Calendar.MONTH);
78              array2[2] = c.get(Calendar.DAY_OF_MONTH);
79              final String tmpMsg = msg + array2[0] + "-" + array2[1] + "-" + array2[2] + " at ";
80              assertEqualDuration(tmpMsg + i, Integer.toString(i), array1, array2, format );
81              c.add(calendarType, 1);
82          }
83      }
84  
85      private DurationFormatUtils.Token createTokenWithCount(final Object value, final int count) {
86          DurationFormatUtils.Token token = new DurationFormatUtils.Token(value, false, -1);
87          for (int i = 1; i < count; i++) {
88              token.increment();
89          }
90          return token;
91      }
92  
93      @Test
94      public void testAlternatingLiteralOptionals() {
95          String format = "['d'dH'h'][m'm']['s's]['ms'S]";
96  
97          assertEquals("d1",
98              DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), format));
99  
100         assertEquals("1h",
101             DurationFormatUtils.formatDuration(Duration.ofHours(1).toMillis(), format));
102 
103         assertEquals("1m",
104             DurationFormatUtils.formatDuration(Duration.ofMinutes(1).toMillis(), format));
105 
106         assertEquals("s1",
107             DurationFormatUtils.formatDuration(Duration.ofSeconds(1).toMillis(), format));
108 
109         assertEquals("ms001",
110             DurationFormatUtils.formatDuration(Duration.ofMillis(1).toMillis(), format));
111 
112         assertEquals("d1s1",
113             DurationFormatUtils.formatDuration(Duration.ofDays(1).plusSeconds(1).toMillis(), format));
114 
115         assertEquals("d11h",
116             DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).toMillis(), format));
117 
118         assertEquals("d11h1m",
119             DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).plusMinutes(1).toMillis(), format));
120 
121         assertEquals("d11h1ms1",
122             DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).plusMinutes(1).plusSeconds(1).toMillis(), format));
123 
124         assertEquals("d11h1ms1ms001",
125             DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).plusMinutes(1).plusSeconds(1).plusMillis(1).toMillis(), format));
126 
127     }
128 
129     /** https://issues.apache.org/bugzilla/show_bug.cgi?id=38401 */
130     @Test
131     public void testBugzilla38401() {
132         assertEqualDuration("0000/00/30 16:00:00 000", new int[] { 2006, 0, 26, 18, 47, 34 },
133                              new int[] { 2006, 1, 26, 10, 47, 34 }, "yyyy/MM/dd HH:mm:ss SSS");
134     }
135 
136     @Test
137     public void testConstructor() {
138         assertNotNull(new DurationFormatUtils());
139         final Constructor<?>[] cons = DurationFormatUtils.class.getDeclaredConstructors();
140         assertEquals(1, cons.length);
141         assertTrue(Modifier.isPublic(cons[0].getModifiers()));
142         assertTrue(Modifier.isPublic(DurationFormatUtils.class.getModifiers()));
143         assertFalse(Modifier.isFinal(DurationFormatUtils.class.getModifiers()));
144     }
145 
146     @Test
147     public void testDurationsByBruteForce() {
148         bruteForce(2006, 0, 1, "d", Calendar.DAY_OF_MONTH);
149         bruteForce(2006, 0, 2, "d", Calendar.DAY_OF_MONTH);
150         bruteForce(2007, 1, 2, "d", Calendar.DAY_OF_MONTH);
151         bruteForce(2004, 1, 29, "d", Calendar.DAY_OF_MONTH);
152         bruteForce(1996, 1, 29, "d", Calendar.DAY_OF_MONTH);
153 
154         bruteForce(1969, 1, 28, "M", Calendar.MONTH);  // tests for 48 years
155         //bruteForce(1996, 1, 29, "M", Calendar.MONTH);  // this will fail
156     }
157 
158     /** Attempting to test edge cases in DurationFormatUtils.formatPeriod. */
159     @Test
160     @DefaultTimeZone(TimeZones.GMT_ID)
161     public void testEdgeDurations() {
162         // This test case must use a time zone without DST
163         TimeZone.setDefault(FastTimeZone.getGmtTimeZone());
164         assertEqualDuration("01", new int[] { 2006, 0, 15, 0, 0, 0 },
165                              new int[] { 2006, 2, 10, 0, 0, 0 }, "MM");
166         assertEqualDuration("12", new int[] { 2005, 0, 15, 0, 0, 0 },
167                              new int[] { 2006, 0, 15, 0, 0, 0 }, "MM");
168         assertEqualDuration("12", new int[] { 2005, 0, 15, 0, 0, 0 },
169                              new int[] { 2006, 0, 16, 0, 0, 0 }, "MM");
170         assertEqualDuration("11", new int[] { 2005, 0, 15, 0, 0, 0 },
171                              new int[] { 2006, 0, 14, 0, 0, 0 }, "MM");
172 
173         assertEqualDuration("01 26", new int[] { 2006, 0, 15, 0, 0, 0 },
174                              new int[] { 2006, 2, 10, 0, 0, 0 }, "MM dd");
175         assertEqualDuration("54", new int[] { 2006, 0, 15, 0, 0, 0 },
176                              new int[] { 2006, 2, 10, 0, 0, 0 }, "dd");
177 
178         assertEqualDuration("09 12", new int[] { 2006, 1, 20, 0, 0, 0 },
179                              new int[] { 2006, 11, 4, 0, 0, 0 }, "MM dd");
180         assertEqualDuration("287", new int[] { 2006, 1, 20, 0, 0, 0 },
181                              new int[] { 2006, 11, 4, 0, 0, 0 }, "dd");
182 
183         assertEqualDuration("11 30", new int[] { 2006, 0, 2, 0, 0, 0 },
184                              new int[] { 2007, 0, 1, 0, 0, 0 }, "MM dd");
185         assertEqualDuration("364", new int[] { 2006, 0, 2, 0, 0, 0 },
186                              new int[] { 2007, 0, 1, 0, 0, 0 }, "dd");
187 
188         assertEqualDuration("12 00", new int[] { 2006, 0, 1, 0, 0, 0 },
189                              new int[] { 2007, 0, 1, 0, 0, 0 }, "MM dd");
190         assertEqualDuration("365", new int[] { 2006, 0, 1, 0, 0, 0 },
191                              new int[] { 2007, 0, 1, 0, 0, 0 }, "dd");
192 
193         assertEqualDuration("31", new int[] { 2006, 0, 1, 0, 0, 0 },
194                 new int[] { 2006, 1, 1, 0, 0, 0 }, "dd");
195 
196         assertEqualDuration("92", new int[] { 2005, 9, 1, 0, 0, 0 },
197                 new int[] { 2006, 0, 1, 0, 0, 0 }, "dd");
198         assertEqualDuration("77", new int[] { 2005, 9, 16, 0, 0, 0 },
199                 new int[] { 2006, 0, 1, 0, 0, 0 }, "dd");
200 
201         // test month larger in start than end
202         assertEqualDuration("136", new int[] { 2005, 9, 16, 0, 0, 0 },
203                 new int[] { 2006, 2, 1, 0, 0, 0 }, "dd");
204         // test when start in leap year
205         assertEqualDuration("136", new int[] { 2004, 9, 16, 0, 0, 0 },
206                 new int[] { 2005, 2, 1, 0, 0, 0 }, "dd");
207         // test when end in leap year
208         assertEqualDuration("137", new int[] { 2003, 9, 16, 0, 0, 0 },
209                 new int[] { 2004, 2, 1, 0, 0, 0 }, "dd");
210         // test when end in leap year but less than end of feb
211         assertEqualDuration("135", new int[] { 2003, 9, 16, 0, 0, 0 },
212                 new int[] { 2004, 1, 28, 0, 0, 0 }, "dd");
213 
214         assertEqualDuration("364", new int[] { 2007, 0, 2, 0, 0, 0 },
215                 new int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
216         assertEqualDuration("729", new int[] { 2006, 0, 2, 0, 0, 0 },
217                 new int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
218 
219         assertEqualDuration("365", new int[] { 2007, 2, 2, 0, 0, 0 },
220                 new int[] { 2008, 2, 1, 0, 0, 0 }, "dd");
221         assertEqualDuration("333", new int[] { 2007, 1, 2, 0, 0, 0 },
222                 new int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
223 
224         assertEqualDuration("28", new int[] { 2008, 1, 2, 0, 0, 0 },
225                 new int[] { 2008, 2, 1, 0, 0, 0 }, "dd");
226         assertEqualDuration("393", new int[] { 2007, 1, 2, 0, 0, 0 },
227                 new int[] { 2008, 2, 1, 0, 0, 0 }, "dd");
228 
229         assertEqualDuration("369", new int[] { 2004, 0, 29, 0, 0, 0 },
230                 new int[] { 2005, 1, 1, 0, 0, 0 }, "dd");
231 
232         assertEqualDuration("338", new int[] { 2004, 1, 29, 0, 0, 0 },
233                 new int[] { 2005, 1, 1, 0, 0, 0 }, "dd");
234 
235         assertEqualDuration("28", new int[] { 2004, 2, 8, 0, 0, 0 },
236                 new int[] { 2004, 3, 5, 0, 0, 0 }, "dd");
237 
238         assertEqualDuration("48", new int[] { 1992, 1, 29, 0, 0, 0 },
239                 new int[] { 1996, 1, 29, 0, 0, 0 }, "M");
240 
241 
242         // this seems odd - and will fail if I throw it in as a brute force
243         // below as it expects the answer to be 12. It's a tricky edge case
244         assertEqualDuration("11", new int[] { 1996, 1, 29, 0, 0, 0 },
245                 new int[] { 1997, 1, 28, 0, 0, 0 }, "M");
246         // again - this seems odd
247         assertEqualDuration("11 28", new int[] { 1996, 1, 29, 0, 0, 0 },
248                 new int[] { 1997, 1, 28, 0, 0, 0 }, "M d");
249 
250     }
251 
252     @Test
253     public void testEmptyOptionals() {
254       assertEquals(
255           "",
256           DurationFormatUtils.formatDuration(0L, "[d'd'][H'h'][m'm'][s's']"));
257       assertEquals(
258           "",
259           DurationFormatUtils.formatDuration(0L, "['d''h''m''s's]"));
260     }
261 
262     @Test
263     public void testFormatDuration() {
264         long duration = 0;
265         assertEquals("0", DurationFormatUtils.formatDuration(duration, "y"));
266         assertEquals("0", DurationFormatUtils.formatDuration(duration, "M"));
267         assertEquals("0", DurationFormatUtils.formatDuration(duration, "d"));
268         assertEquals("0", DurationFormatUtils.formatDuration(duration, "H"));
269         assertEquals("0", DurationFormatUtils.formatDuration(duration, "m"));
270         assertEquals("0", DurationFormatUtils.formatDuration(duration, "s"));
271         assertEquals("0", DurationFormatUtils.formatDuration(duration, "S"));
272         assertEquals("0000", DurationFormatUtils.formatDuration(duration, "SSSS"));
273         assertEquals("0000", DurationFormatUtils.formatDuration(duration, "yyyy"));
274         assertEquals("0000", DurationFormatUtils.formatDuration(duration, "yyMM"));
275 
276         duration = 60 * 1000;
277         assertEquals("0", DurationFormatUtils.formatDuration(duration, "y"));
278         assertEquals("0", DurationFormatUtils.formatDuration(duration, "M"));
279         assertEquals("0", DurationFormatUtils.formatDuration(duration, "d"));
280         assertEquals("0", DurationFormatUtils.formatDuration(duration, "H"));
281         assertEquals("1", DurationFormatUtils.formatDuration(duration, "m"));
282         assertEquals("60", DurationFormatUtils.formatDuration(duration, "s"));
283         assertEquals("60000", DurationFormatUtils.formatDuration(duration, "S"));
284         assertEquals("01:00", DurationFormatUtils.formatDuration(duration, "mm:ss"));
285 
286         final Calendar base = Calendar.getInstance();
287         base.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
288         base.set(Calendar.MILLISECOND, 0);
289 
290         final Calendar cal = Calendar.getInstance();
291         cal.set(2003, Calendar.FEBRUARY, 1, 0, 0, 0);
292         cal.set(Calendar.MILLISECOND, 0);
293         duration = cal.getTime().getTime() - base.getTime().getTime(); // duration from 2000-01-01 to cal
294         // don't use 1970 in test as time zones were less reliable in 1970 than now
295         // remember that duration formatting ignores time zones, working on strict hour lengths
296         final int days = 366 + 365 + 365 + 31;
297         assertEquals("0 0 " + days, DurationFormatUtils.formatDuration(duration, "y M d"));
298     }
299 
300     @Test
301     public void testFormatDurationHMS() {
302         long time = 0;
303         assertEquals("00:00:00.000", DurationFormatUtils.formatDurationHMS(time));
304 
305         time = 1;
306         assertEquals("00:00:00.001", DurationFormatUtils.formatDurationHMS(time));
307 
308         time = 15;
309         assertEquals("00:00:00.015", DurationFormatUtils.formatDurationHMS(time));
310 
311         time = 165;
312         assertEquals("00:00:00.165", DurationFormatUtils.formatDurationHMS(time));
313 
314         time = 1675;
315         assertEquals("00:00:01.675", DurationFormatUtils.formatDurationHMS(time));
316 
317         time = 13465;
318         assertEquals("00:00:13.465", DurationFormatUtils.formatDurationHMS(time));
319 
320         time = 72789;
321         assertEquals("00:01:12.789", DurationFormatUtils.formatDurationHMS(time));
322 
323         time = 12789 + 32 * 60000;
324         assertEquals("00:32:12.789", DurationFormatUtils.formatDurationHMS(time));
325 
326         time = 12789 + 62 * 60000;
327         assertEquals("01:02:12.789", DurationFormatUtils.formatDurationHMS(time));
328     }
329 
330     @Test
331     public void testFormatDurationISO() {
332         assertEquals("P0Y0M0DT0H0M0.000S", DurationFormatUtils.formatDurationISO(0L));
333         assertEquals("P0Y0M0DT0H0M0.001S", DurationFormatUtils.formatDurationISO(1L));
334         assertEquals("P0Y0M0DT0H0M0.010S", DurationFormatUtils.formatDurationISO(10L));
335         assertEquals("P0Y0M0DT0H0M0.100S", DurationFormatUtils.formatDurationISO(100L));
336         assertEquals("P0Y0M0DT0H1M15.321S", DurationFormatUtils.formatDurationISO(75321L));
337     }
338 
339     /**
340      * Tests that "1 &lt;unit&gt;s" gets converted to "1 &lt;unit&gt;" but that "11 &lt;unit&gt;s" is left alone.
341      */
342     @Test
343     public void testFormatDurationPluralWords() {
344         final long oneSecond = 1000;
345         final long oneMinute = oneSecond * 60;
346         final long oneHour = oneMinute * 60;
347         final long oneDay = oneHour * 24;
348         String text;
349 
350         text = DurationFormatUtils.formatDurationWords(oneSecond, false, false);
351         assertEquals("0 days 0 hours 0 minutes 1 second", text);
352         text = DurationFormatUtils.formatDurationWords(oneSecond * 2, false, false);
353         assertEquals("0 days 0 hours 0 minutes 2 seconds", text);
354         text = DurationFormatUtils.formatDurationWords(oneSecond * 11, false, false);
355         assertEquals("0 days 0 hours 0 minutes 11 seconds", text);
356 
357         text = DurationFormatUtils.formatDurationWords(oneMinute, false, false);
358         assertEquals("0 days 0 hours 1 minute 0 seconds", text);
359         text = DurationFormatUtils.formatDurationWords(oneMinute * 2, false, false);
360         assertEquals("0 days 0 hours 2 minutes 0 seconds", text);
361         text = DurationFormatUtils.formatDurationWords(oneMinute * 11, false, false);
362         assertEquals("0 days 0 hours 11 minutes 0 seconds", text);
363         text = DurationFormatUtils.formatDurationWords(oneMinute + oneSecond, false, false);
364         assertEquals("0 days 0 hours 1 minute 1 second", text);
365 
366         text = DurationFormatUtils.formatDurationWords(oneHour, false, false);
367         assertEquals("0 days 1 hour 0 minutes 0 seconds", text);
368         text = DurationFormatUtils.formatDurationWords(oneHour * 2, false, false);
369         assertEquals("0 days 2 hours 0 minutes 0 seconds", text);
370         text = DurationFormatUtils.formatDurationWords(oneHour * 11, false, false);
371         assertEquals("0 days 11 hours 0 minutes 0 seconds", text);
372         text = DurationFormatUtils.formatDurationWords(oneHour + oneMinute + oneSecond, false, false);
373         assertEquals("0 days 1 hour 1 minute 1 second", text);
374 
375         text = DurationFormatUtils.formatDurationWords(oneDay, false, false);
376         assertEquals("1 day 0 hours 0 minutes 0 seconds", text);
377         text = DurationFormatUtils.formatDurationWords(oneDay * 2, false, false);
378         assertEquals("2 days 0 hours 0 minutes 0 seconds", text);
379         text = DurationFormatUtils.formatDurationWords(oneDay * 11, false, false);
380         assertEquals("11 days 0 hours 0 minutes 0 seconds", text);
381         text = DurationFormatUtils.formatDurationWords(oneDay + oneHour + oneMinute + oneSecond, false, false);
382         assertEquals("1 day 1 hour 1 minute 1 second", text);
383     }
384 
385     @Test
386     public void testFormatDurationWords() {
387         String text;
388 
389         text = DurationFormatUtils.formatDurationWords(50 * 1000, true, false);
390         assertEquals("50 seconds", text);
391         text = DurationFormatUtils.formatDurationWords(65 * 1000, true, false);
392         assertEquals("1 minute 5 seconds", text);
393         text = DurationFormatUtils.formatDurationWords(120 * 1000, true, false);
394         assertEquals("2 minutes 0 seconds", text);
395         text = DurationFormatUtils.formatDurationWords(121 * 1000, true, false);
396         assertEquals("2 minutes 1 second", text);
397         text = DurationFormatUtils.formatDurationWords(72 * 60 * 1000, true, false);
398         assertEquals("1 hour 12 minutes 0 seconds", text);
399         text = DurationFormatUtils.formatDurationWords(24 * 60 * 60 * 1000, true, false);
400         assertEquals("1 day 0 hours 0 minutes 0 seconds", text);
401 
402         text = DurationFormatUtils.formatDurationWords(50 * 1000, true, true);
403         assertEquals("50 seconds", text);
404         text = DurationFormatUtils.formatDurationWords(65 * 1000, true, true);
405         assertEquals("1 minute 5 seconds", text);
406         text = DurationFormatUtils.formatDurationWords(120 * 1000, true, true);
407         assertEquals("2 minutes", text);
408         text = DurationFormatUtils.formatDurationWords(121 * 1000, true, true);
409         assertEquals("2 minutes 1 second", text);
410         text = DurationFormatUtils.formatDurationWords(72 * 60 * 1000, true, true);
411         assertEquals("1 hour 12 minutes", text);
412         text = DurationFormatUtils.formatDurationWords(24 * 60 * 60 * 1000, true, true);
413         assertEquals("1 day", text);
414 
415         text = DurationFormatUtils.formatDurationWords(50 * 1000, false, true);
416         assertEquals("0 days 0 hours 0 minutes 50 seconds", text);
417         text = DurationFormatUtils.formatDurationWords(65 * 1000, false, true);
418         assertEquals("0 days 0 hours 1 minute 5 seconds", text);
419         text = DurationFormatUtils.formatDurationWords(120 * 1000, false, true);
420         assertEquals("0 days 0 hours 2 minutes", text);
421         text = DurationFormatUtils.formatDurationWords(121 * 1000, false, true);
422         assertEquals("0 days 0 hours 2 minutes 1 second", text);
423         text = DurationFormatUtils.formatDurationWords(72 * 60 * 1000, false, true);
424         assertEquals("0 days 1 hour 12 minutes", text);
425         text = DurationFormatUtils.formatDurationWords(24 * 60 * 60 * 1000, false, true);
426         assertEquals("1 day", text);
427 
428         text = DurationFormatUtils.formatDurationWords(50 * 1000, false, false);
429         assertEquals("0 days 0 hours 0 minutes 50 seconds", text);
430         text = DurationFormatUtils.formatDurationWords(65 * 1000, false, false);
431         assertEquals("0 days 0 hours 1 minute 5 seconds", text);
432         text = DurationFormatUtils.formatDurationWords(120 * 1000, false, false);
433         assertEquals("0 days 0 hours 2 minutes 0 seconds", text);
434         text = DurationFormatUtils.formatDurationWords(121 * 1000, false, false);
435         assertEquals("0 days 0 hours 2 minutes 1 second", text);
436         text = DurationFormatUtils.formatDurationWords(72 * 60 * 1000, false, false);
437         assertEquals("0 days 1 hour 12 minutes 0 seconds", text);
438         text = DurationFormatUtils.formatDurationWords(24 * 60 * 60 * 1000 + 72 * 60 * 1000, false, false);
439         assertEquals("1 day 1 hour 12 minutes 0 seconds", text);
440         text = DurationFormatUtils.formatDurationWords(2 * 24 * 60 * 60 * 1000 + 72 * 60 * 1000, false, false);
441         assertEquals("2 days 1 hour 12 minutes 0 seconds", text);
442         for (int i = 2; i < 31; i++) {
443             text = DurationFormatUtils.formatDurationWords(i * 24 * 60 * 60 * 1000L, false, false);
444             assertEquals(i + " days 0 hours 0 minutes 0 seconds", text);
445         }
446     }
447 
448 
449     @Test
450     public void testFormatNegativeDuration() {
451         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(-5000, "S", true));
452     }
453 
454     @Test
455     public void testFormatNegativeDurationHMS() {
456         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationHMS(-5000));
457     }
458 
459     @Test
460     public void testFormatNegativeDurationISO() {
461         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationISO(-5000));
462     }
463 
464     @Test
465     public void testFormatNegativeDurationWords() {
466         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationWords(-5000, true, true));
467     }
468 
469     @Test
470     public void testFormatPeriod() {
471         final Calendar cal1970 = Calendar.getInstance();
472         cal1970.set(1970, Calendar.JANUARY, 1, 0, 0, 0);
473         cal1970.set(Calendar.MILLISECOND, 0);
474         final long time1970 = cal1970.getTime().getTime();
475 
476         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "y"));
477         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "M"));
478         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "d"));
479         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "H"));
480         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "m"));
481         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "s"));
482         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time1970, "S"));
483         assertEquals("0000", DurationFormatUtils.formatPeriod(time1970, time1970, "SSSS"));
484         assertEquals("0000", DurationFormatUtils.formatPeriod(time1970, time1970, "yyyy"));
485         assertEquals("0000", DurationFormatUtils.formatPeriod(time1970, time1970, "yyMM"));
486 
487         long time = time1970 + 60 * 1000;
488         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time, "y"));
489         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time, "M"));
490         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time, "d"));
491         assertEquals("0", DurationFormatUtils.formatPeriod(time1970, time, "H"));
492         assertEquals("1", DurationFormatUtils.formatPeriod(time1970, time, "m"));
493         assertEquals("60", DurationFormatUtils.formatPeriod(time1970, time, "s"));
494         assertEquals("60000", DurationFormatUtils.formatPeriod(time1970, time, "S"));
495         assertEquals("01:00", DurationFormatUtils.formatPeriod(time1970, time, "mm:ss"));
496 
497         final Calendar cal = Calendar.getInstance();
498         cal.set(1973, Calendar.JULY, 1, 0, 0, 0);
499         cal.set(Calendar.MILLISECOND, 0);
500         time = cal.getTime().getTime();
501         assertEquals("36", DurationFormatUtils.formatPeriod(time1970, time, "yM"));
502         assertEquals("3 years 6 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'"));
503         assertEquals("03/06", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM"));
504 
505         cal.set(1973, Calendar.NOVEMBER, 1, 0, 0, 0);
506         cal.set(Calendar.MILLISECOND, 0);
507         time = cal.getTime().getTime();
508         assertEquals("310", DurationFormatUtils.formatPeriod(time1970, time, "yM"));
509         assertEquals("3 years 10 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'"));
510         assertEquals("03/10", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM"));
511 
512         cal.set(1974, Calendar.JANUARY, 1, 0, 0, 0);
513         cal.set(Calendar.MILLISECOND, 0);
514         time = cal.getTime().getTime();
515         assertEquals("40", DurationFormatUtils.formatPeriod(time1970, time, "yM"));
516         assertEquals("4 years 0 months", DurationFormatUtils.formatPeriod(time1970, time, "y' years 'M' months'"));
517         assertEquals("04/00", DurationFormatUtils.formatPeriod(time1970, time, "yy/MM"));
518         assertEquals("48", DurationFormatUtils.formatPeriod(time1970, time, "M"));
519         assertEquals("48", DurationFormatUtils.formatPeriod(time1970, time, "MM"));
520         assertEquals("048", DurationFormatUtils.formatPeriod(time1970, time, "MMM"));
521     }
522 
523     @Test
524     public void testFormatPeriodeStartGreaterEnd() {
525         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatPeriod(5000, 2500, "yy/MM"));
526     }
527 
528     @SuppressWarnings("deprecation")
529     @Test
530     public void testFormatPeriodISO() {
531         final TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
532         final Calendar base = Calendar.getInstance(timeZone);
533         base.set(1970, Calendar.JANUARY, 1, 0, 0, 0);
534         base.set(Calendar.MILLISECOND, 0);
535 
536         final Calendar cal = Calendar.getInstance(timeZone);
537         cal.set(2002, Calendar.FEBRUARY, 23, 9, 11, 12);
538         cal.set(Calendar.MILLISECOND, 1);
539         String text;
540         // repeat a test from testDateTimeISO to compare extended and not extended.
541         text = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), timeZone);
542         assertEquals("2002-02-23T09:11:12-03:00", text);
543         // test fixture is the same as above, but now with extended format.
544         text = DurationFormatUtils.formatPeriod(base.getTime().getTime(), cal.getTime().getTime(),
545                 DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN, false, timeZone);
546         assertEquals("P32Y1M22DT9H11M12.001S", text);
547         // test fixture from example in https://www.w3.org/TR/xmlschema-2/#duration
548         cal.set(1971, Calendar.FEBRUARY, 3, 10, 30, 0);
549         cal.set(Calendar.MILLISECOND, 0);
550         text = DurationFormatUtils.formatPeriod(base.getTime().getTime(), cal.getTime().getTime(),
551                 DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN, false, timeZone);
552         assertEquals("P1Y1M2DT10H30M0.000S", text);
553         // want a way to say 'don't print the seconds in format()' or other fields for that matter:
554         // assertEquals("P1Y2M3DT10H30M", text);
555     }
556 
557     @Test
558     public void testFormatPeriodISOMethod() {
559         assertEquals("P0Y0M0DT0H0M0.000S", DurationFormatUtils.formatPeriodISO(0L, 0L));
560         assertEquals("P0Y0M0DT0H0M1.000S", DurationFormatUtils.formatPeriodISO(0L, 1000L));
561         assertEquals("P0Y0M0DT0H1M1.000S", DurationFormatUtils.formatPeriodISO(0L, 61000L));
562     }
563 
564     @Test
565     public void testFormatPeriodISOStartGreaterEnd() {
566         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatPeriodISO(5000, 2000));
567     }
568 
569     // https://issues.apache.org/jira/browse/LANG-281
570     @Test
571     public void testJiraLang281() {
572         assertEqualDuration("09", new int[] { 2005, 11, 31, 0, 0, 0 },
573                              new int[] { 2006, 9, 6, 0, 0, 0 }, "MM");
574     }
575 
576     // Takes a minute to run, so generally turned off
577 //    public void testBrutally() {
578 //        Calendar c = Calendar.getInstance();
579 //        c.set(2004, 0, 1, 0, 0, 0);
580 //        for (int i=0; i < FOUR_YEARS; i++) {
581 //            bruteForce(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH), "d", Calendar.DAY_OF_MONTH );
582 //            c.add(Calendar.DAY_OF_MONTH, 1);
583 //        }
584 //    }
585 
586     @Test
587     public void testLANG815() {
588         final Calendar calendar = Calendar.getInstance();
589         calendar.set(2012, Calendar.JULY, 30, 0, 0, 0);
590         final long startMillis = calendar.getTimeInMillis();
591 
592         calendar.set(2012, Calendar.SEPTEMBER, 8);
593         final long endMillis = calendar.getTimeInMillis();
594 
595         assertEquals("1 9", DurationFormatUtils.formatPeriod(startMillis, endMillis, "M d"));
596     }
597 
598     @Test
599     public void testLANG981() { // unmatched quote char in lexx
600         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.lexx("'yMdHms''S"));
601     }
602     @Test
603     public void testLANG982() { // More than 3 millisecond digits following a second
604         assertEquals("61.999", DurationFormatUtils.formatDuration(61999, "s.S"));
605         assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m S"));
606         assertEquals("61.999", DurationFormatUtils.formatDuration(61999, "s.SSS"));
607         assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m SSS"));
608         assertEquals("61.0999", DurationFormatUtils.formatDuration(61999, "s.SSSS"));
609         assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m SSSS"));
610         assertEquals("61.00999", DurationFormatUtils.formatDuration(61999, "s.SSSSS"));
611         assertEquals("1 01999", DurationFormatUtils.formatDuration(61999, "m SSSSS"));
612     }
613 
614 
615     @Test
616     public void testLANG984() { // Long durations
617         assertEquals("0", DurationFormatUtils.formatDuration(0, "S"));
618         assertEquals(Integer.toString(Integer.MAX_VALUE), DurationFormatUtils.formatDuration(Integer.MAX_VALUE, "S"));
619         long maxIntPlus=Integer.MAX_VALUE;
620         maxIntPlus++;
621         assertEquals(Long.toString(maxIntPlus), DurationFormatUtils.formatDuration(maxIntPlus, "S"));
622         assertEquals(Long.toString(Long.MAX_VALUE), DurationFormatUtils.formatDuration(Long.MAX_VALUE, "S"));
623     }
624 
625     @Test
626     public void testLexx() {
627         // tests each constant
628         assertArrayEquals(new DurationFormatUtils.Token[]{
629             createTokenWithCount(DurationFormatUtils.y, 1),
630             createTokenWithCount(DurationFormatUtils.M, 1),
631             createTokenWithCount(DurationFormatUtils.d, 1),
632             createTokenWithCount(DurationFormatUtils.H, 1),
633             createTokenWithCount(DurationFormatUtils.m, 1),
634             createTokenWithCount(DurationFormatUtils.s, 1),
635             createTokenWithCount(DurationFormatUtils.S, 1)}, DurationFormatUtils.lexx("yMdHmsS"));
636 
637         // tests the ISO 8601-like
638         assertArrayEquals(new DurationFormatUtils.Token[]{
639             createTokenWithCount(DurationFormatUtils.H, 2),
640             createTokenWithCount(new StringBuilder(":"), 1),
641             createTokenWithCount(DurationFormatUtils.m, 2),
642             createTokenWithCount(new StringBuilder(":"), 1),
643             createTokenWithCount(DurationFormatUtils.s, 2),
644             createTokenWithCount(new StringBuilder("."), 1),
645             createTokenWithCount(DurationFormatUtils.S, 3)}, DurationFormatUtils.lexx("HH:mm:ss.SSS"));
646 
647         // test the iso extended format
648         assertArrayEquals(new DurationFormatUtils.Token[]{
649             createTokenWithCount(new StringBuilder("P"), 1),
650             createTokenWithCount(DurationFormatUtils.y, 4),
651             createTokenWithCount(new StringBuilder("Y"), 1),
652             createTokenWithCount(DurationFormatUtils.M, 1),
653             createTokenWithCount(new StringBuilder("M"), 1),
654             createTokenWithCount(DurationFormatUtils.d, 1),
655             createTokenWithCount(new StringBuilder("DT"), 1),
656             createTokenWithCount(DurationFormatUtils.H, 1),
657             createTokenWithCount(new StringBuilder("H"), 1),
658             createTokenWithCount(DurationFormatUtils.m, 1),
659             createTokenWithCount(new StringBuilder("M"), 1),
660             createTokenWithCount(DurationFormatUtils.s, 1),
661             createTokenWithCount(new StringBuilder("."), 1),
662             createTokenWithCount(DurationFormatUtils.S, 3),
663             createTokenWithCount(new StringBuilder("S"), 1)}, DurationFormatUtils
664                 .lexx(DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN));
665 
666         // test failures in equals
667         final DurationFormatUtils.Token token = createTokenWithCount(DurationFormatUtils.y, 4);
668         assertNotEquals(token, new Object(), "Token equal to non-Token class. ");
669         assertNotEquals(token, createTokenWithCount(new Object(), 1), "Token equal to Token with wrong value class. ");
670         assertNotEquals(token, createTokenWithCount(DurationFormatUtils.y, 1), "Token equal to Token with different count. ");
671         final DurationFormatUtils.Token numToken = createTokenWithCount(Integer.valueOf(1), 4);
672         assertEquals(numToken, numToken, "Token with Number value not equal to itself. ");
673     }
674 
675     @Test
676     public void testLiteralPrefixOptionalToken() {
677       assertEquals(
678           DurationFormatUtils.formatDuration(10000L, "s's'"),
679           DurationFormatUtils.formatDuration(10000L, "['['d']']['<'H'>']['{'m'}']s's'"));
680       assertEquals(
681           DurationFormatUtils.formatDuration(10000L, "s's'"),
682           DurationFormatUtils.formatDuration(10000L, "['{'m'}']s's'"));
683     }
684 
685     // Testing the under a day range in DurationFormatUtils.formatPeriod
686     @Test
687     public void testLowDurations() {
688         for (int hr=0; hr < 24; hr++) {
689             for (int min=0; min < 60; min++) {
690                 for (int sec=0; sec < 60; sec++) {
691                     assertEqualDuration(hr + ":" + min + ":" + sec,
692                                          new int[] { 2000, 0, 1, 0, 0, 0, 0 },
693                                          new int[] { 2000, 0, 1, hr, min, sec },
694                                          "H:m:s"
695                                        );
696                 }
697             }
698         }
699     }
700 
701     @Test
702     public void testMultipleOptionalBlocks() {
703 
704       assertEquals(
705           DurationFormatUtils.formatDuration(Duration.ofHours(1).toMillis(), "'[['H']]'"),
706           DurationFormatUtils.formatDuration(Duration.ofHours(1).toMillis(), "['{'d'}']['[['H']]']"));
707 
708       assertEquals(
709           DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), "['{'d'}']"),
710           DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), "['{'d'}']['['H']']"));
711     }
712 
713     @Test
714     public void testOptionalLiteralSpecialCharacters() {
715       assertEquals(
716           DurationFormatUtils.formatDuration(10000L, "s's'"),
717           DurationFormatUtils.formatDuration(10000L, "['['m']']s's'"));
718     }
719 
720     @Test
721     public void testOptionalToken() {
722 
723         //make sure optional formats match corresponding adjusted non-optional formats
724         assertEquals(
725                 DurationFormatUtils.formatDuration(915361000L, "d'd'H'h'm'm's's'"),
726                 DurationFormatUtils.formatDuration(915361000L, "[d'd'H'h'm'm']s's'"));
727 
728         assertEquals(
729                 DurationFormatUtils.formatDuration(9153610L, "H'h'm'm's's'"),
730                 DurationFormatUtils.formatDuration(9153610L, "[d'd'H'h'm'm']s's'"));
731 
732         assertEquals(
733                 DurationFormatUtils.formatDuration(915361L, "m'm's's'"),
734                 DurationFormatUtils.formatDuration(915361L, "[d'd'H'h'm'm']s's'"));
735 
736         assertEquals(
737                 DurationFormatUtils.formatDuration(9153L, "s's'"),
738                 DurationFormatUtils.formatDuration(9153L, "[d'd'H'h'm'm']s's'"));
739 
740         assertEquals(
741                 DurationFormatUtils.formatDuration(9153L, "s's'"),
742                 DurationFormatUtils.formatDuration(9153L, "[d'd'H'h'm'm']s's'"));
743 
744         assertEquals(
745                 DurationFormatUtils.formatPeriod(9153610L, 915361000L, "d'd'H'h'm'm's's'"),
746                 DurationFormatUtils.formatPeriod(9153610L, 915361000L, "[d'd'H'h'm'm']s's'"));
747 
748         assertEquals(
749                 DurationFormatUtils.formatPeriod(915361L, 9153610L, "H'h'm'm's's'"),
750                 DurationFormatUtils.formatPeriod(915361L, 9153610L, "[d'd'H'h'm'm']s's'"));
751 
752         assertEquals(
753                 DurationFormatUtils.formatPeriod(9153L, 915361L, "m'm's's'"),
754                 DurationFormatUtils.formatPeriod(9153L, 915361L, "[d'd'H'h'm'm']s's'"));
755 
756         assertEquals(
757                 DurationFormatUtils.formatPeriod(0L, 9153L, "s's'"),
758                 DurationFormatUtils.formatPeriod(0L, 9153L, "[d'd'H'h'm'm']s's'"));
759 
760         //make sure optional parts are actually omitted when zero
761 
762         assertEquals("2h32m33s610ms", DurationFormatUtils.formatDuration(9153610L, "[d'd'H'h'm'm's's']S'ms'"));
763 
764         assertEquals("15m15s361ms", DurationFormatUtils.formatDuration(915361L, "[d'd'H'h'm'm's's']S'ms'"));
765 
766         assertEquals("9s153ms", DurationFormatUtils.formatDuration(9153L, "[d'd'H'h'm'm's's']S'ms'"));
767 
768         assertEquals("915ms", DurationFormatUtils.formatDuration(915L, "[d'd'H'h'm'm's's']S'ms'"));
769 
770         //make sure we can handle omitting multiple literals after a token
771 
772         assertEquals(
773                 DurationFormatUtils.formatPeriod(915361L, 9153610L, "H'h''h2'm'm's's'"),
774                 DurationFormatUtils.formatPeriod(915361L, 9153610L, "[d'd''d2'H'h''h2'm'm']s's'"));
775     }
776 
777     @Test
778     public void testUnmatchedOptionalTokens() {
779         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[s"));
780         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[[s"));
781         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[s]]"));
782     }
783 }