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.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  public 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      public 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     public 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     public 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     public 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     public 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     public 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     public 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     public 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     public 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     public 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     public 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     public void testFormatNegativeDuration() {
399         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(-5000, "S", true));
400     }
401 
402     @Test
403     public void testFormatNegativeDurationHMS() {
404         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationHMS(-5000));
405     }
406 
407     @Test
408     public void testFormatNegativeDurationISO() {
409         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationISO(-5000));
410     }
411 
412     @Test
413     public void testFormatNegativeDurationWords() {
414         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDurationWords(-5000, true, true));
415     }
416 
417     @Test
418     public 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     public void testFormatPeriodeStartGreaterEnd() {
478         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatPeriod(5000, 2500, "yy/MM"));
479     }
480 
481     @SuppressWarnings("deprecation")
482     @Test
483     public void testFormatPeriodISO() {
484         final TimeZone timeZone = TimeZone.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         // TODO Jacoco shows missing coverage for internal negative days
510     }
511 
512     @Test
513     public void testFormatPeriodISOMethod() {
514         assertEquals("P0Y0M0DT0H0M0.000S", DurationFormatUtils.formatPeriodISO(0L, 0L));
515         assertEquals("P0Y0M0DT0H0M1.000S", DurationFormatUtils.formatPeriodISO(0L, 1000L));
516         assertEquals("P0Y0M0DT0H1M1.000S", DurationFormatUtils.formatPeriodISO(0L, 61000L));
517     }
518 
519     @Test
520     public void testFormatPeriodISOStartGreaterEnd() {
521         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatPeriodISO(5000, 2000));
522     }
523 
524     /**
525      * Takes 8 seconds to run.
526      */
527     @Test
528     public void testFourYears() {
529         Calendar c = Calendar.getInstance();
530         c.set(2004, 0, 1, 0, 0, 0);
531         for (int i = 0; i < FOUR_YEARS; i++) {
532             bruteForce(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH), "d", Calendar.DAY_OF_MONTH);
533             c.add(Calendar.DAY_OF_MONTH, 1);
534         }
535     }
536 
537     // https://issues.apache.org/jira/browse/LANG-281
538     @Test
539     public void testJiraLang281() {
540         assertEqualDuration("09", new int[] { 2005, 11, 31, 0, 0, 0 }, new int[] { 2006, 9, 6, 0, 0, 0 }, "MM");
541     }
542 
543     @Test
544     public void testLANG815() {
545         final Calendar calendar = Calendar.getInstance();
546         calendar.set(2012, Calendar.JULY, 30, 0, 0, 0);
547         final long startMillis = calendar.getTimeInMillis();
548 
549         calendar.set(2012, Calendar.SEPTEMBER, 8);
550         final long endMillis = calendar.getTimeInMillis();
551 
552         assertEquals("1 9", DurationFormatUtils.formatPeriod(startMillis, endMillis, "M d"));
553     }
554 
555     @Test
556     public void testLANG981() { // unmatched quote char in lexx
557         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.lexx("'yMdHms''S"));
558     }
559     @Test
560     public void testLANG982() { // More than 3 millisecond digits following a second
561         assertEquals("61.999", DurationFormatUtils.formatDuration(61999, "s.S"));
562         assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m S"));
563         assertEquals("61.999", DurationFormatUtils.formatDuration(61999, "s.SSS"));
564         assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m SSS"));
565         assertEquals("61.0999", DurationFormatUtils.formatDuration(61999, "s.SSSS"));
566         assertEquals("1 1999", DurationFormatUtils.formatDuration(61999, "m SSSS"));
567         assertEquals("61.00999", DurationFormatUtils.formatDuration(61999, "s.SSSSS"));
568         assertEquals("1 01999", DurationFormatUtils.formatDuration(61999, "m SSSSS"));
569     }
570 
571     @Test
572     public void testLANG984() { // Long durations
573         assertEquals("0", DurationFormatUtils.formatDuration(0, "S"));
574         assertEquals(Integer.toString(Integer.MAX_VALUE), DurationFormatUtils.formatDuration(Integer.MAX_VALUE, "S"));
575         long maxIntPlus = Integer.MAX_VALUE;
576         maxIntPlus++;
577         assertEquals(Long.toString(maxIntPlus), DurationFormatUtils.formatDuration(maxIntPlus, "S"));
578         assertEquals(Long.toString(Long.MAX_VALUE), DurationFormatUtils.formatDuration(Long.MAX_VALUE, "S"));
579     }
580 
581     @Test
582     public void testLexx() {
583         // tests each constant
584         assertArrayEquals(new DurationFormatUtils.Token[] {
585             createTokenWithCount(DurationFormatUtils.y, 1),
586             createTokenWithCount(DurationFormatUtils.M, 1),
587             createTokenWithCount(DurationFormatUtils.d, 1),
588             createTokenWithCount(DurationFormatUtils.H, 1),
589             createTokenWithCount(DurationFormatUtils.m, 1),
590             createTokenWithCount(DurationFormatUtils.s, 1),
591             createTokenWithCount(DurationFormatUtils.S, 1) }, DurationFormatUtils.lexx("yMdHmsS"));
592 
593         // tests the ISO 8601-like
594         assertArrayEquals(new DurationFormatUtils.Token[] {
595             createTokenWithCount(DurationFormatUtils.H, 2),
596             createTokenWithCount(new StringBuilder(":"), 1),
597             createTokenWithCount(DurationFormatUtils.m, 2),
598             createTokenWithCount(new StringBuilder(":"), 1),
599             createTokenWithCount(DurationFormatUtils.s, 2),
600             createTokenWithCount(new StringBuilder("."), 1),
601             createTokenWithCount(DurationFormatUtils.S, 3) }, DurationFormatUtils.lexx("HH:mm:ss.SSS"));
602 
603         // test the iso extended format
604         assertArrayEquals(new DurationFormatUtils.Token[] {
605             createTokenWithCount(new StringBuilder("P"), 1),
606             createTokenWithCount(DurationFormatUtils.y, 4),
607             createTokenWithCount(new StringBuilder("Y"), 1),
608             createTokenWithCount(DurationFormatUtils.M, 1),
609             createTokenWithCount(new StringBuilder("M"), 1),
610             createTokenWithCount(DurationFormatUtils.d, 1),
611             createTokenWithCount(new StringBuilder("DT"), 1),
612             createTokenWithCount(DurationFormatUtils.H, 1),
613             createTokenWithCount(new StringBuilder("H"), 1),
614             createTokenWithCount(DurationFormatUtils.m, 1),
615             createTokenWithCount(new StringBuilder("M"), 1),
616             createTokenWithCount(DurationFormatUtils.s, 1),
617             createTokenWithCount(new StringBuilder("."), 1),
618             createTokenWithCount(DurationFormatUtils.S, 3),
619             createTokenWithCount(new StringBuilder("S"), 1) }, DurationFormatUtils.lexx(DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN));
620 
621         // test failures in equals
622         final DurationFormatUtils.Token token = createTokenWithCount(DurationFormatUtils.y, 4);
623         assertEquals(token, token);
624         assertEquals(token.hashCode(), token.hashCode());
625         assertNotEquals(token, new Object(), "Token equal to non-Token class. ");
626         final Token token2 = createTokenWithCount("", 1);
627         assertNotEquals(token, token2, "Token equal to Token with wrong value class. ");
628         assertNotEquals(token.hashCode(), token2.hashCode());
629         assertNotEquals(token, createTokenWithCount(DurationFormatUtils.y, 1), "Token equal to Token with different count. ");
630         final DurationFormatUtils.Token numToken = createTokenWithCount("1", 4);
631         assertEquals(numToken, numToken, "Token with Number value not equal to itself. ");
632     }
633 
634     @Test
635     public void testLiteralPrefixOptionalToken() {
636         assertEquals(DurationFormatUtils.formatDuration(10000L, "s's'"), DurationFormatUtils.formatDuration(10000L, "['['d']']['<'H'>']['{'m'}']s's'"));
637         assertEquals(DurationFormatUtils.formatDuration(10000L, "s's'"), DurationFormatUtils.formatDuration(10000L, "['{'m'}']s's'"));
638     }
639 
640     // Testing the under a day range in DurationFormatUtils.formatPeriod
641     @Test
642     public void testLowDurations() {
643         for (int hr = 0; hr < 24; hr++) {
644             for (int min = 0; min < 60; min++) {
645                 for (int sec = 0; sec < 60; sec++) {
646                     assertEqualDuration(hr + ":" + min + ":" + sec, new int[] { 2000, 0, 1, 0, 0, 0, 0 }, new int[] { 2000, 0, 1, hr, min, sec }, "H:m:s");
647                 }
648             }
649         }
650     }
651 
652     @Test
653     public void testMultipleOptionalBlocks() {
654         assertEquals(DurationFormatUtils.formatDuration(Duration.ofHours(1).toMillis(), "'[['H']]'"),
655                 DurationFormatUtils.formatDuration(Duration.ofHours(1).toMillis(), "['{'d'}']['[['H']]']"));
656         assertEquals(DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), "['{'d'}']"),
657                 DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), "['{'d'}']['['H']']"));
658     }
659 
660     @Test
661     public void testOptionalLiteralSpecialCharacters() {
662       assertEquals(
663           DurationFormatUtils.formatDuration(10000L, "s's'"),
664           DurationFormatUtils.formatDuration(10000L, "['['m']']s's'"));
665     }
666 
667     @Test
668     public void testOptionalToken() {
669 
670         //make sure optional formats match corresponding adjusted non-optional formats
671         assertEquals(
672                 DurationFormatUtils.formatDuration(915361000L, "d'd'H'h'm'm's's'"),
673                 DurationFormatUtils.formatDuration(915361000L, "[d'd'H'h'm'm']s's'"));
674 
675         assertEquals(
676                 DurationFormatUtils.formatDuration(9153610L, "H'h'm'm's's'"),
677                 DurationFormatUtils.formatDuration(9153610L, "[d'd'H'h'm'm']s's'"));
678 
679         assertEquals(
680                 DurationFormatUtils.formatDuration(915361L, "m'm's's'"),
681                 DurationFormatUtils.formatDuration(915361L, "[d'd'H'h'm'm']s's'"));
682 
683         assertEquals(
684                 DurationFormatUtils.formatDuration(9153L, "s's'"),
685                 DurationFormatUtils.formatDuration(9153L, "[d'd'H'h'm'm']s's'"));
686 
687         assertEquals(
688                 DurationFormatUtils.formatDuration(9153L, "s's'"),
689                 DurationFormatUtils.formatDuration(9153L, "[d'd'H'h'm'm']s's'"));
690 
691         assertEquals(
692                 DurationFormatUtils.formatPeriod(9153610L, 915361000L, "d'd'H'h'm'm's's'"),
693                 DurationFormatUtils.formatPeriod(9153610L, 915361000L, "[d'd'H'h'm'm']s's'"));
694 
695         assertEquals(
696                 DurationFormatUtils.formatPeriod(915361L, 9153610L, "H'h'm'm's's'"),
697                 DurationFormatUtils.formatPeriod(915361L, 9153610L, "[d'd'H'h'm'm']s's'"));
698 
699         assertEquals(
700                 DurationFormatUtils.formatPeriod(9153L, 915361L, "m'm's's'"),
701                 DurationFormatUtils.formatPeriod(9153L, 915361L, "[d'd'H'h'm'm']s's'"));
702 
703         assertEquals(
704                 DurationFormatUtils.formatPeriod(0L, 9153L, "s's'"),
705                 DurationFormatUtils.formatPeriod(0L, 9153L, "[d'd'H'h'm'm']s's'"));
706 
707         //make sure optional parts are actually omitted when zero
708 
709         assertEquals("2h32m33s610ms", DurationFormatUtils.formatDuration(9153610L, "[d'd'H'h'm'm's's']S'ms'"));
710 
711         assertEquals("15m15s361ms", DurationFormatUtils.formatDuration(915361L, "[d'd'H'h'm'm's's']S'ms'"));
712 
713         assertEquals("9s153ms", DurationFormatUtils.formatDuration(9153L, "[d'd'H'h'm'm's's']S'ms'"));
714 
715         assertEquals("915ms", DurationFormatUtils.formatDuration(915L, "[d'd'H'h'm'm's's']S'ms'"));
716 
717         //make sure we can handle omitting multiple literals after a token
718 
719         assertEquals(
720                 DurationFormatUtils.formatPeriod(915361L, 9153610L, "H'h''h2'm'm's's'"),
721                 DurationFormatUtils.formatPeriod(915361L, 9153610L, "[d'd''d2'H'h''h2'm'm']s's'"));
722     }
723 
724     @Test
725     public void testUnmatchedOptionalTokens() {
726         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[s"));
727         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[[s"));
728         assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[s]]"));
729     }
730 }