1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.calendarcommon2;
17 
18 import android.util.TimeFormatException;
19 
20 import androidx.test.filters.MediumTest;
21 import androidx.test.filters.SmallTest;
22 
23 import junit.framework.TestCase;
24 
25 /**
26  * Tests for com.android.calendarcommon2.Time.
27  *
28  * Some of these tests are borrowed from android.text.format.TimeTest.
29  */
30 public class TimeTest extends TestCase {
31 
32     @SmallTest
testNullTimezone()33     public void testNullTimezone() {
34         try {
35             Time t = new Time(null);
36             fail("expected a null timezone to throw an exception.");
37         } catch (NullPointerException npe) {
38             // expected.
39         }
40     }
41 
42     @SmallTest
testTimezone()43     public void testTimezone() {
44         Time t = new Time(Time.TIMEZONE_UTC);
45         assertEquals(Time.TIMEZONE_UTC, t.getTimezone());
46     }
47 
48     @SmallTest
testSwitchTimezone()49     public void testSwitchTimezone() {
50         Time t = new Time(Time.TIMEZONE_UTC);
51         String newTimezone = "America/Los_Angeles";
52         t.switchTimezone(newTimezone);
53         assertEquals(newTimezone, t.getTimezone());
54     }
55 
56     @SmallTest
testGetActualMaximum()57     public void testGetActualMaximum() {
58         Time t = new Time(Time.TIMEZONE_UTC);
59         t.set(1, 0, 2020);
60         assertEquals(59, t.getActualMaximum(Time.SECOND));
61         assertEquals(59, t.getActualMaximum(Time.MINUTE));
62         assertEquals(23, t.getActualMaximum(Time.HOUR));
63         assertEquals(31, t.getActualMaximum(Time.MONTH_DAY));
64         assertEquals(11, t.getActualMaximum(Time.MONTH));
65         assertEquals(7, t.getActualMaximum(Time.WEEK_DAY));
66         assertEquals(366, t.getActualMaximum(Time.YEAR_DAY)); // 2020 is a leap year
67         t.set(1, 0, 2019);
68         assertEquals(365, t.getActualMaximum(Time.YEAR_DAY));
69     }
70 
71     @SmallTest
testAdd()72     public void testAdd() {
73         Time t = new Time(Time.TIMEZONE_UTC);
74         t.set(0, 0, 0, 1, 0, 2020);
75         t.add(Time.SECOND, 1);
76         assertEquals(1, t.getSecond());
77         t.add(Time.MINUTE, 1);
78         assertEquals(1, t.getMinute());
79         t.add(Time.HOUR, 1);
80         assertEquals(1, t.getHour());
81         t.add(Time.MONTH_DAY, 1);
82         assertEquals(2, t.getDay());
83         t.add(Time.MONTH, 1);
84         assertEquals(1, t.getMonth());
85         t.add(Time.YEAR, 1);
86         assertEquals(2021, t.getYear());
87     }
88 
89     @SmallTest
testClear()90     public void testClear() {
91         Time t = new Time(Time.TIMEZONE_UTC);
92         t.clear(Time.TIMEZONE_UTC);
93 
94         assertEquals(Time.TIMEZONE_UTC, t.getTimezone());
95         assertFalse(t.isAllDay());
96         assertEquals(0, t.getSecond());
97         assertEquals(0, t.getMinute());
98         assertEquals(0, t.getHour());
99         assertEquals(1, t.getDay()); // default for Calendar is 1
100         assertEquals(0, t.getMonth());
101         assertEquals(1970, t.getYear());
102         assertEquals(4, t.getWeekDay()); // 1970 Jan 1 --> Thursday
103         assertEquals(0, t.getYearDay());
104         assertEquals(0, t.getGmtOffset());
105     }
106 
107     @SmallTest
testCompare()108     public void testCompare() {
109         Time a = new Time(Time.TIMEZONE_UTC);
110         Time b = new Time("America/Los_Angeles");
111         assertTrue(a.compareTo(b) < 0);
112 
113         Time c = new Time("Asia/Calcutta");
114         assertTrue(a.compareTo(c) > 0);
115 
116         Time d = new Time(Time.TIMEZONE_UTC);
117         assertEquals(0, a.compareTo(d));
118     }
119 
120     @SmallTest
testFormat2445()121     public void testFormat2445() {
122         Time t = new Time();
123         assertEquals("19700101T000000", t.format2445());
124         t.setTimezone(Time.TIMEZONE_UTC);
125         assertEquals("19700101T000000Z", t.format2445());
126         t.setAllDay(true);
127         assertEquals("19700101", t.format2445());
128     }
129 
130     @SmallTest
testFormat3339()131     public void testFormat3339() {
132         Time t = new Time(Time.TIMEZONE_UTC);
133         assertEquals("1970-01-01", t.format3339(true));
134         t.set(29, 1, 2020);
135         assertEquals("2020-02-29", t.format3339(true));
136     }
137 
138     @SmallTest
testToMillis()139     public void testToMillis() {
140         Time t = new Time(Time.TIMEZONE_UTC);
141         t.set(1, 0, 0, 1, 0, 1970);
142         assertEquals(1000L, t.toMillis());
143 
144         t.set(0, 0, 0, 1, 1, 2020);
145         assertEquals(1580515200000L, t.toMillis());
146         t.set(1, 0, 0, 1, 1, 2020);
147         assertEquals(1580515201000L, t.toMillis());
148 
149         t.set(1, 0, 2020);
150         assertEquals(1577836800000L, t.toMillis());
151         t.set(1, 1, 2020);
152         assertEquals(1580515200000L, t.toMillis());
153     }
154 
155     @SmallTest
testToMillis_overflow()156     public void testToMillis_overflow() {
157         Time t = new Time(Time.TIMEZONE_UTC);
158         t.set(32, 0, 2020);
159         assertEquals(1580515200000L, t.toMillis());
160         assertEquals(1, t.getDay());
161         assertEquals(1, t.getMonth());
162     }
163 
164     @SmallTest
testParse()165     public void testParse() {
166         Time t = new Time(Time.TIMEZONE_UTC);
167         t.parse("20201010T160000Z");
168         assertEquals(2020, t.getYear());
169         assertEquals(9, t.getMonth());
170         assertEquals(10, t.getDay());
171         assertEquals(16, t.getHour());
172         assertEquals(0, t.getMinute());
173         assertEquals(0, t.getSecond());
174 
175         t.parse("20200220");
176         assertEquals(2020, t.getYear());
177         assertEquals(1, t.getMonth());
178         assertEquals(20, t.getDay());
179         assertEquals(0, t.getHour());
180         assertEquals(0, t.getMinute());
181         assertEquals(0, t.getSecond());
182 
183         try {
184             t.parse("invalid");
185             fail();
186         } catch (IllegalArgumentException e) {
187             // expected
188         }
189 
190         try {
191             t.parse("20201010Z160000");
192             fail();
193         } catch (IllegalArgumentException e) {
194             // expected
195         }
196     }
197 
198     @SmallTest
testParse3339()199     public void testParse3339() {
200         Time t = new Time(Time.TIMEZONE_UTC);
201 
202         t.parse3339("1980-05-23");
203         if (!t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23) {
204             fail("Did not parse all-day date correctly");
205         }
206 
207         t.parse3339("1980-05-23T09:50:50");
208         if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
209                 || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
210                 || t.getGmtOffset() != 0) {
211             fail("Did not parse timezone-offset-less date correctly");
212         }
213 
214         t.parse3339("1980-05-23T09:50:50Z");
215         if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
216                 || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
217                 || t.getGmtOffset() != 0) {
218             fail("Did not parse UTC date correctly");
219         }
220 
221         t.parse3339("1980-05-23T09:50:50.0Z");
222         if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
223                 || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
224                 || t.getGmtOffset() != 0) {
225             fail("Did not parse UTC date correctly");
226         }
227 
228         t.parse3339("1980-05-23T09:50:50.12Z");
229         if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
230                 || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
231                 || t.getGmtOffset() != 0) {
232             fail("Did not parse UTC date correctly");
233         }
234 
235         t.parse3339("1980-05-23T09:50:50.123Z");
236         if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
237                 || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
238                 || t.getGmtOffset() != 0) {
239             fail("Did not parse UTC date correctly");
240         }
241 
242         // the time should be normalized to UTC
243         t.parse3339("1980-05-23T09:50:50-01:05");
244         if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
245                 || t.getHour() != 10 || t.getMinute() != 55 || t.getSecond() != 50
246                 || t.getGmtOffset() != 0) {
247             fail("Did not parse timezone-offset date correctly");
248         }
249 
250         // the time should be normalized to UTC
251         t.parse3339("1980-05-23T09:50:50.123-01:05");
252         if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
253                 || t.getHour() != 10 || t.getMinute() != 55 || t.getSecond() != 50
254                 || t.getGmtOffset() != 0) {
255             fail("Did not parse timezone-offset date correctly");
256         }
257 
258         try {
259             t.parse3339("1980");
260             fail("Did not throw error on truncated input length");
261         } catch (TimeFormatException e) {
262             // successful
263         }
264 
265         try {
266             t.parse3339("1980-05-23T09:50:50.123+");
267             fail("Did not throw error on truncated timezone offset");
268         } catch (TimeFormatException e1) {
269             // successful
270         }
271 
272         try {
273             t.parse3339("1980-05-23T09:50:50.123+05:0");
274             fail("Did not throw error on truncated timezone offset");
275         } catch (TimeFormatException e1) {
276             // successful
277         }
278     }
279 
280     @SmallTest
testSet_millis()281     public void testSet_millis() {
282         Time t = new Time(Time.TIMEZONE_UTC);
283 
284         t.set(1000L);
285         assertEquals(1970, t.getYear());
286         assertEquals(1, t.getSecond());
287 
288         t.set(2000L);
289         assertEquals(2, t.getSecond());
290         assertEquals(0, t.getMinute());
291 
292         t.set(1000L * 60);
293         assertEquals(1, t.getMinute());
294         assertEquals(0, t.getHour());
295 
296         t.set(1000L * 60 * 60);
297         assertEquals(1, t.getHour());
298         assertEquals(1, t.getDay());
299 
300         t.set((1000L * 60 * 60 * 24) + 1000L);
301         assertEquals(2, t.getDay());
302         assertEquals(1970, t.getYear());
303     }
304 
305     @SmallTest
testSet_dayMonthYear()306     public void testSet_dayMonthYear() {
307         Time t = new Time(Time.TIMEZONE_UTC);
308         t.set(1, 2, 2021);
309         assertEquals(1, t.getDay());
310         assertEquals(2, t.getMonth());
311         assertEquals(2021, t.getYear());
312     }
313 
314     @SmallTest
testSet_secondMinuteHour()315     public void testSet_secondMinuteHour() {
316         Time t = new Time(Time.TIMEZONE_UTC);
317         t.set(1, 2, 3, 4, 5, 2021);
318         assertEquals(1, t.getSecond());
319         assertEquals(2, t.getMinute());
320         assertEquals(3, t.getHour());
321         assertEquals(4, t.getDay());
322         assertEquals(5, t.getMonth());
323         assertEquals(2021, t.getYear());
324     }
325 
326     @SmallTest
testSet_overflow()327     public void testSet_overflow() {
328         // Jan 32nd --> Feb 1st
329         Time t = new Time(Time.TIMEZONE_UTC);
330         t.set(32, 0, 2020);
331         assertEquals(1, t.getDay());
332         assertEquals(1, t.getMonth());
333         assertEquals(2020, t.getYear());
334 
335         t = new Time(Time.TIMEZONE_UTC);
336         t.set(5, 10, 15, 32, 0, 2020);
337         assertEquals(5, t.getSecond());
338         assertEquals(10, t.getMinute());
339         assertEquals(15, t.getHour());
340         assertEquals(1, t.getDay());
341         assertEquals(1, t.getMonth());
342         assertEquals(2020, t.getYear());
343     }
344 
345     @SmallTest
testSet_other()346     public void testSet_other() {
347         Time t = new Time(Time.TIMEZONE_UTC);
348         t.set(1, 2, 3, 4, 5, 2021);
349         Time t2 = new Time();
350         t2.set(t);
351         assertEquals(Time.TIMEZONE_UTC, t2.getTimezone());
352         assertEquals(1, t2.getSecond());
353         assertEquals(2, t2.getMinute());
354         assertEquals(3, t2.getHour());
355         assertEquals(4, t2.getDay());
356         assertEquals(5, t2.getMonth());
357         assertEquals(2021, t2.getYear());
358     }
359 
360     @SmallTest
testSetToNow()361     public void testSetToNow() {
362         long now = System.currentTimeMillis();
363         Time t = new Time(Time.TIMEZONE_UTC);
364         t.set(now);
365         long ms = t.toMillis();
366         // ensure time is within 1 second because of rounding errors
367         assertTrue("now: " + now + "; actual: " + ms, Math.abs(ms - now) < 1000);
368     }
369 
370     @SmallTest
testGetWeekNumber()371     public void testGetWeekNumber() {
372         Time t = new Time(Time.TIMEZONE_UTC);
373         t.set(1000L);
374         assertEquals(1, t.getWeekNumber());
375         t.set(1, 1, 2020);
376         assertEquals(5, t.getWeekNumber());
377 
378         // ensure ISO 8601 standards are met: weeks start on Monday and the first week has at least
379         // 4 days in it (the year's first Thursday or Jan 4th)
380         for (int i = 1; i <= 8; i++) {
381             t.set(i, 0, 2020);
382             // Jan 6th is the first Monday in 2020 so that would be week 2
383             assertEquals(i < 6 ? 1 : 2, t.getWeekNumber());
384         }
385     }
386 
387     private static class DateTest {
388         public int year1;
389         public int month1;
390         public int day1;
391         public int hour1;
392         public int minute1;
393 
394         public int offset;
395 
396         public int year2;
397         public int month2;
398         public int day2;
399         public int hour2;
400         public int minute2;
401 
402         public DateTest(int year1, int month1, int day1, int hour1, int minute1,
403                 int offset, int year2, int month2, int day2, int hour2, int minute2) {
404             this.year1 = year1;
405             this.month1 = month1;
406             this.day1 = day1;
407             this.hour1 = hour1;
408             this.minute1 = minute1;
409             this.offset = offset;
410             this.year2 = year2;
411             this.month2 = month2;
412             this.day2 = day2;
413             this.hour2 = hour2;
414             this.minute2 = minute2;
415         }
416 
417         public boolean equals(Time time) {
418             return time.getYear() == year2 && time.getMonth() == month2 && time.getDay() == day2
419                     && time.getHour() == hour2 && time.getMinute() == minute2;
420         }
421     }
422 
423     @SmallTest
424     public void testNormalize() {
425         Time t = new Time(Time.TIMEZONE_UTC);
426         t.parse("20060432T010203");
427         assertEquals(1146531723000L, t.normalize());
428     }
429 
430     /* These tests assume that DST changes on Nov 4, 2007 at 2am (to 1am). */
431 
432     // The "offset" field in "dayTests" represents days.
433     // Note: the month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11
434     private DateTest[] dayTests = {
435             // Nov 4, 12am + 0 day = Nov 4, 12am
436             new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0),
437             // Nov 5, 12am + 0 day = Nov 5, 12am
438             new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0),
439             // Nov 3, 12am + 1 day = Nov 4, 12am
440             new DateTest(2007, 10, 3, 0, 0, 1, 2007, 10, 4, 0, 0),
441             // Nov 4, 12am + 1 day = Nov 5, 12am
442             new DateTest(2007, 10, 4, 0, 0, 1, 2007, 10, 5, 0, 0),
443             // Nov 5, 12am + 1 day = Nov 6, 12am
444             new DateTest(2007, 10, 5, 0, 0, 1, 2007, 10, 6, 0, 0),
445             // Nov 3, 1am + 1 day = Nov 4, 1am
446             new DateTest(2007, 10, 3, 1, 0, 1, 2007, 10, 4, 1, 0),
447             // Nov 4, 1am + 1 day = Nov 5, 1am
448             new DateTest(2007, 10, 4, 1, 0, 1, 2007, 10, 5, 1, 0),
449             // Nov 5, 1am + 1 day = Nov 6, 1am
450             new DateTest(2007, 10, 5, 1, 0, 1, 2007, 10, 6, 1, 0),
451             // Nov 3, 2am + 1 day = Nov 4, 2am
452             new DateTest(2007, 10, 3, 2, 0, 1, 2007, 10, 4, 2, 0),
453             // Nov 4, 2am + 1 day = Nov 5, 2am
454             new DateTest(2007, 10, 4, 2, 0, 1, 2007, 10, 5, 2, 0),
455             // Nov 5, 2am + 1 day = Nov 6, 2am
456             new DateTest(2007, 10, 5, 2, 0, 1, 2007, 10, 6, 2, 0),
457     };
458 
459     // The "offset" field in "minuteTests" represents minutes.
460     // Note: the month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11
461     private DateTest[] minuteTests = {
462             // Nov 4, 12am + 0 minutes = Nov 4, 12am
463             new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0),
464             // Nov 4, 12am + 60 minutes = Nov 4, 1am
465             new DateTest(2007, 10, 4, 0, 0, 60, 2007, 10, 4, 1, 0),
466             // Nov 5, 12am + 0 minutes = Nov 5, 12am
467             new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0),
468             // Nov 3, 12am + 60 minutes = Nov 3, 1am
469             new DateTest(2007, 10, 3, 0, 0, 60, 2007, 10, 3, 1, 0),
470             // Nov 4, 12am + 60 minutes = Nov 4, 1am
471             new DateTest(2007, 10, 4, 0, 0, 60, 2007, 10, 4, 1, 0),
472             // Nov 5, 12am + 60 minutes = Nov 5, 1am
473             new DateTest(2007, 10, 5, 0, 0, 60, 2007, 10, 5, 1, 0),
474             // Nov 3, 1am + 60 minutes = Nov 3, 2am
475             new DateTest(2007, 10, 3, 1, 0, 60, 2007, 10, 3, 2, 0),
476             // Nov 4, 12:59am (PDT) + 2 minutes = Nov 4, 1:01am (PDT)
477             new DateTest(2007, 10, 4, 0, 59, 2, 2007, 10, 4, 1, 1),
478             // Nov 4, 12:59am (PDT) + 62 minutes = Nov 4, 1:01am (PST)
479             new DateTest(2007, 10, 4, 0, 59, 62, 2007, 10, 4, 1, 1),
480             // Nov 4, 12:30am (PDT) + 120 minutes = Nov 4, 1:30am (PST)
481             new DateTest(2007, 10, 4, 0, 30, 120, 2007, 10, 4, 1, 30),
482             // Nov 4, 12:30am (PDT) + 90 minutes = Nov 4, 1:00am (PST)
483             new DateTest(2007, 10, 4, 0, 30, 90, 2007, 10, 4, 1, 0),
484             // Nov 4, 1am (PDT) + 30 minutes = Nov 4, 1:30am (PDT)
485             new DateTest(2007, 10, 4, 1, 0, 30, 2007, 10, 4, 1, 30),
486             // Nov 4, 1:30am (PDT) + 15 minutes = Nov 4, 1:45am (PDT)
487             new DateTest(2007, 10, 4, 1, 30, 15, 2007, 10, 4, 1, 45),
488             // Mar 11, 1:30am (PST) + 30 minutes = Mar 11, 3am (PDT)
489             new DateTest(2007, 2, 11, 1, 30, 30, 2007, 2, 11, 3, 0),
490             // Nov 4, 1:30am (PST) + 15 minutes = Nov 4, 1:45am (PST)
491             new DateTest(2007, 10, 4, 1, 30, 15, 2007, 10, 4, 1, 45),
492             // Nov 4, 1:30am (PST) + 30 minutes = Nov 4, 2:00am (PST)
493             new DateTest(2007, 10, 4, 1, 30, 30, 2007, 10, 4, 2, 0),
494             // Nov 5, 1am + 60 minutes = Nov 5, 2am
495             new DateTest(2007, 10, 5, 1, 0, 60, 2007, 10, 5, 2, 0),
496             // Nov 3, 2am + 60 minutes = Nov 3, 3am
497             new DateTest(2007, 10, 3, 2, 0, 60, 2007, 10, 3, 3, 0),
498             // Nov 4, 2am + 30 minutes = Nov 4, 2:30am
499             new DateTest(2007, 10, 4, 2, 0, 30, 2007, 10, 4, 2, 30),
500             // Nov 4, 2am + 60 minutes = Nov 4, 3am
501             new DateTest(2007, 10, 4, 2, 0, 60, 2007, 10, 4, 3, 0),
502             // Nov 5, 2am + 60 minutes = Nov 5, 3am
503             new DateTest(2007, 10, 5, 2, 0, 60, 2007, 10, 5, 3, 0),
504             // NOTE: Calendar assumes 1am PDT == 1am PST, the two are not distinct, hence why the transition boundary itself has no tests
505     };
506 
507     @MediumTest
508     public void testNormalize_dst() {
509         Time local = new Time("America/Los_Angeles");
510 
511         int len = dayTests.length;
512         for (int index = 0; index < len; index++) {
513             DateTest test = dayTests[index];
514             local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
515             local.add(Time.MONTH_DAY, test.offset);
516             if (!test.equals(local)) {
517                 String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
518                         test.year2, test.month2, test.day2, test.hour2, test.minute2);
519                 String actualTime = String.format("%d-%02d-%02d %02d:%02d",
520                         local.getYear(), local.getMonth(), local.getDay(), local.getHour(),
521                         local.getMinute());
522                 fail("Expected: " + expectedTime + "; Actual: " + actualTime);
523             }
524 
525             local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
526             local.add(Time.MONTH_DAY, test.offset);
527             if (!test.equals(local)) {
528                 String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
529                         test.year2, test.month2, test.day2, test.hour2, test.minute2);
530                 String actualTime = String.format("%d-%02d-%02d %02d:%02d",
531                         local.getYear(), local.getMonth(), local.getDay(), local.getHour(),
532                         local.getMinute());
533                 fail("Expected: " + expectedTime + "; Actual: " + actualTime);
534             }
535         }
536 
537         len = minuteTests.length;
538         for (int index = 0; index < len; index++) {
539             DateTest test = minuteTests[index];
540             local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
541             local.add(Time.MINUTE, test.offset);
542             if (!test.equals(local)) {
543                 String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
544                         test.year2, test.month2, test.day2, test.hour2, test.minute2);
545                 String actualTime = String.format("%d-%02d-%02d %02d:%02d",
546                         local.getYear(), local.getMonth(), local.getDay(), local.getHour(),
547                         local.getMinute());
548                 fail("Expected: " + expectedTime + "; Actual: " + actualTime);
549             }
550 
551             local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
552             local.add(Time.MINUTE, test.offset);
553             if (!test.equals(local)) {
554                 String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
555                         test.year2, test.month2, test.day2, test.hour2, test.minute2);
556                 String actualTime = String.format("%d-%02d-%02d %02d:%02d",
557                         local.getYear(), local.getMonth(), local.getDay(), local.getHour(),
558                         local.getMinute());
559                 fail("Expected: " + expectedTime + "; Actual: " + actualTime);
560             }
561         }
562     }
563 
564     @SmallTest
565     public void testNormalize_overflow() {
566         Time t = new Time(Time.TIMEZONE_UTC);
567         t.set(32, 0, 2020);
568         t.normalize();
569         assertEquals(1, t.getDay());
570         assertEquals(1, t.getMonth());
571     }
572 
573     @SmallTest
574     public void testDstBehavior_addDays_ignoreDst() {
575         Time time = new Time("America/Los_Angeles");
576         time.set(4, 10, 2007);  // set to Nov 4, 2007, 12am
577         assertEquals(1194159600000L, time.normalize());
578         time.add(Time.MONTH_DAY, 1); // changes to Nov 5, 2007, 12am
579         assertEquals(1194249600000L, time.toMillis());
580 
581         time = new Time("America/Los_Angeles");
582         time.set(11, 2, 2007);  // set to Mar 11, 2007, 12am
583         assertEquals(1173600000000L, time.normalize());
584         time.add(Time.MONTH_DAY, 1); // changes to Mar 12, 2007, 12am
585         assertEquals(1173682800000L, time.toMillis());
586     }
587 
588     @SmallTest
589     public void testDstBehavior_addDays_applyDst() {
590         if (!Time.APPLY_DST_CHANGE_LOGIC) {
591             return;
592         }
593         Time time = new Time("America/Los_Angeles");
594         time.set(4, 10, 2007);  // set to Nov 4, 2007, 12am
595         assertEquals(1194159600000L, time.normalizeApplyDst());
596         time.add(Time.MONTH_DAY, 1); // changes to Nov 4, 2007, 11pm (fall back)
597         assertEquals(1194246000000L, time.toMillisApplyDst());
598 
599         time = new Time("America/Los_Angeles");
600         time.set(11, 2, 2007);  // set to Mar 11, 2007, 12am
601         assertEquals(1173600000000L, time.normalizeApplyDst());
602         time.add(Time.MONTH_DAY, 1); // changes to Mar 12, 2007, 1am (roll forward)
603         assertEquals(1173686400000L, time.toMillisApplyDst());
604     }
605 
606     @SmallTest
607     public void testDstBehavior_addHours_ignoreDst() {
608         // Note: by default, Calendar applies DST logic if adding hours or minutes but not if adding
609         // days, hence in this test, only if the APPLY_DST_CHANGE_LOGIC flag is false, then the time
610         // is adjusted with DST
611         Time time = new Time("America/Los_Angeles");
612         time.set(4, 10, 2007);  // set to Nov 4, 2007, 12am
613         assertEquals(1194159600000L, time.normalize());
614         time.add(Time.HOUR, 24); // changes to Nov 5, 2007, 12am
615         assertEquals(Time.APPLY_DST_CHANGE_LOGIC ? 1194249600000L : 1194246000000L,
616                         time.toMillis());
617 
618         time = new Time("America/Los_Angeles");
619         time.set(11, 2, 2007);  // set to Mar 11, 2007, 12am
620         assertEquals(1173600000000L, time.normalize());
621         time.add(Time.HOUR, 24); // changes to Mar 12, 2007, 12am
622         assertEquals(Time.APPLY_DST_CHANGE_LOGIC ? 1173682800000L : 1173686400000L,
623                         time.toMillis());
624     }
625 
626     @SmallTest
627     public void testDstBehavior_addHours_applyDst() {
628         if (!Time.APPLY_DST_CHANGE_LOGIC) {
629             return;
630         }
631         Time time = new Time("America/Los_Angeles");
632         time.set(4, 10, 2007);  // set to Nov 4, 2007, 12am
633         assertEquals(1194159600000L, time.normalizeApplyDst());
634         time.add(Time.HOUR, 24); // changes to Nov 4, 2007, 11pm (fall back)
635         assertEquals(1194246000000L, time.toMillisApplyDst());
636 
637         time = new Time("America/Los_Angeles");
638         time.set(11, 2, 2007);  // set to Mar 11, 2007, 12am
639         assertEquals(1173600000000L, time.normalizeApplyDst());
640         time.add(Time.HOUR, 24); // changes to Mar 12, 2007, 1am (roll forward)
641         assertEquals(1173686400000L, time.toMillisApplyDst());
642     }
643 
644     // Timezones that cover the world.
645     // Some GMT offsets occur more than once in case some cities decide to change their GMT offset.
646     private static final String[] mTimeZones = {
647             "Pacific/Kiritimati",
648             "Pacific/Enderbury",
649             "Pacific/Fiji",
650             "Antarctica/South_Pole",
651             "Pacific/Norfolk",
652             "Pacific/Ponape",
653             "Asia/Magadan",
654             "Australia/Lord_Howe",
655             "Australia/Sydney",
656             "Australia/Adelaide",
657             "Asia/Tokyo",
658             "Asia/Seoul",
659             "Asia/Taipei",
660             "Asia/Singapore",
661             "Asia/Hong_Kong",
662             "Asia/Saigon",
663             "Asia/Bangkok",
664             "Indian/Cocos",
665             "Asia/Rangoon",
666             "Asia/Omsk",
667             "Antarctica/Mawson",
668             "Asia/Colombo",
669             "Asia/Calcutta",
670             "Asia/Oral",
671             "Asia/Kabul",
672             "Asia/Dubai",
673             "Asia/Tehran",
674             "Europe/Moscow",
675             "Asia/Baghdad",
676             "Africa/Mogadishu",
677             "Europe/Athens",
678             "Africa/Cairo",
679             "Europe/Rome",
680             "Europe/Berlin",
681             "Europe/Amsterdam",
682             "Africa/Tunis",
683             "Europe/London",
684             "Europe/Dublin",
685             "Atlantic/St_Helena",
686             "Africa/Monrovia",
687             "Africa/Accra",
688             "Atlantic/Azores",
689             "Atlantic/South_Georgia",
690             "America/Noronha",
691             "America/Sao_Paulo",
692             "America/Cayenne",
693             "America/St_Johns",
694             "America/Puerto_Rico",
695             "America/Aruba",
696             "America/New_York",
697             "America/Chicago",
698             "America/Denver",
699             "America/Los_Angeles",
700             "America/Anchorage",
701             "Pacific/Marquesas",
702             "America/Adak",
703             "Pacific/Honolulu",
704             "Pacific/Midway",
705     };
706 
707     @MediumTest
708     public void testGetJulianDay() {
709         Time time = new Time(Time.TIMEZONE_UTC);
710 
711         // for 30 random days in the year 2020 and for a random timezone, get the Julian day for
712         // 12am and then check that if we change the time we get the same Julian day.
713         for (int i = 0; i < 30; i++) {
714             int monthDay = (int) (Math.random() * 365) + 1;
715             int zoneIndex = (int) (Math.random() * mTimeZones.length);
716             time.setTimezone(mTimeZones[zoneIndex]);
717             time.set(0, 0, 0, monthDay, 0, 2020);
718 
719             int julianDay = Time.getJulianDay(time.normalize(), time.getGmtOffset());
720 
721             // change the time during the day and check that we get the same Julian day.
722             for (int hour = 0; hour < 24; hour++) {
723                 for (int minute = 0; minute < 60; minute += 15) {
724                     time.set(0, minute, hour, monthDay, 0, 2020);
725                     int day = Time.getJulianDay(time.normalize(), time.getGmtOffset());
726                     assertEquals(day, julianDay);
727                     time.clear(Time.TIMEZONE_UTC);
728                 }
729             }
730         }
731     }
732 
733     @MediumTest
734     public void testSetJulianDay() {
735         Time time = new Time(Time.TIMEZONE_UTC);
736 
737         // for each day in the year 2020, pick a random timezone, and verify that we can
738         // set the Julian day correctly.
739         for (int monthDay = 1; monthDay <= 366; monthDay++) {
740             int zoneIndex = (int) (Math.random() * mTimeZones.length);
741             // leave the "month" as zero because we are changing the "monthDay" from 1 to 366.
742             // the call to normalize() will then change the "month" (but we don't really care).
743             time.set(0, 0, 0, monthDay, 0, 2020);
744             time.setTimezone(mTimeZones[zoneIndex]);
745             long millis = time.normalize();
746             int julianDay = Time.getJulianDay(millis, time.getGmtOffset());
747 
748             time.setJulianDay(julianDay);
749 
750             // some places change daylight saving time at 12am and so there is no 12am on some days
751             // in some timezones - in those cases, the time is set to 1am.
752             // some examples: Africa/Cairo, America/Sao_Paulo, Atlantic/Azores
753             assertTrue(time.getHour() == 0 || time.getHour() == 1);
754             assertEquals(0, time.getMinute());
755             assertEquals(0, time.getSecond());
756 
757             millis = time.toMillis();
758             int day = Time.getJulianDay(millis, time.getGmtOffset());
759             assertEquals(day, julianDay);
760             time.clear(Time.TIMEZONE_UTC);
761         }
762     }
763 }
764