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