• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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 
17 package android.text.format.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23 
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.text.format.Time;
27 import android.util.Log;
28 import android.util.TimeFormatException;
29 
30 import androidx.test.ext.junit.runners.AndroidJUnit4;
31 import androidx.test.filters.SmallTest;
32 
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 
38 import java.time.Duration;
39 import java.time.Instant;
40 import java.time.LocalDate;
41 import java.time.LocalDateTime;
42 import java.time.LocalTime;
43 import java.time.Month;
44 import java.time.ZoneId;
45 import java.time.ZoneOffset;
46 import java.time.temporal.ChronoUnit;
47 import java.time.temporal.JulianFields;
48 import java.time.zone.ZoneOffsetTransition;
49 import java.time.zone.ZoneRules;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Calendar;
53 import java.util.Date;
54 import java.util.GregorianCalendar;
55 import java.util.List;
56 import java.util.Locale;
57 import java.util.Objects;
58 import java.util.TimeZone;
59 
60 @SmallTest
61 @RunWith(AndroidJUnit4.class)
62 public class TimeTest {
63     private static final String TAG = "TimeTest";
64     private static List<Locale> sSystemLocales;
65 
66     private Locale originalLocale;
67 
68     @Before
setup()69     public void setup() {
70         originalLocale = Locale.getDefault();
71 
72         maybeInitializeSystemLocales();
73     }
74 
75     @After
teardown()76     public void teardown() {
77         // The Locale may be changed by tests. Revert to the original.
78         changeJavaAndAndroidLocale(originalLocale, true /* force */);
79     }
80 
81     @Test
testConstructor()82     public void testConstructor() {
83         Time time = new Time();
84         new Time(Time.getCurrentTimezone());
85         time.set(System.currentTimeMillis());
86         Time anotherTime = new Time(time);
87         verifyTime(time, anotherTime);
88     }
89 
90     @Test
testNormalize()91     public void testNormalize() {
92         final int expectedMonth = 3;
93         final int expectedDate = 1;
94         Time time = new Time();
95         // set date to March 32.
96         time.year = 2008;
97         time.month = 2;
98         time.monthDay = 32;
99         long timeMilliseconds = time.normalize(false);
100         Calendar cal = Calendar.getInstance();
101         cal.setTimeInMillis(timeMilliseconds);
102         assertEquals(2008, cal.get(Calendar.YEAR));
103         assertEquals(3, cal.get(Calendar.MONTH));
104         assertEquals(1, cal.get(Calendar.DAY_OF_MONTH));
105         assertEquals(expectedMonth, time.month);
106         assertEquals(expectedDate, time.monthDay);
107 
108         // reset date to March 32.
109         time.month = 2;
110         time.monthDay = 32;
111         assertEquals(timeMilliseconds, time.normalize(true));
112         assertEquals(expectedMonth, time.month);
113         assertEquals(expectedDate, time.monthDay);
114     }
115 
116     @Test
testSwitchTimezone()117     public void testSwitchTimezone() {
118         String timeZone = "US/Pacific";
119         String anotherTimeZone = "Asia/Chongqing";
120         Time time = new Time(timeZone);
121         assertEquals(timeZone, time.timezone);
122         time.switchTimezone(anotherTimeZone);
123         assertEquals(anotherTimeZone, time.timezone);
124     }
125 
126     @Test
testSet()127     public void testSet() {
128         final int year = 2008;
129         final int month = 5;
130         final int date = 10;
131         Time time = new Time();
132         time.set(date, month, year);
133         assertEquals(year, time.year);
134         assertEquals(month, time.month);
135         assertEquals(date, time.monthDay);
136 
137         Time anotherTime = new Time();
138         anotherTime.set(time);
139         verifyTime(time, anotherTime);
140     }
141 
verifyTime(Time time, Time anotherTime)142     private void verifyTime(Time time, Time anotherTime) {
143         assertEquals(time.timezone, anotherTime.timezone);
144         assertEquals(time.allDay, anotherTime.allDay);
145         assertEquals(time.second, anotherTime.second);
146         assertEquals(time.minute, anotherTime.minute);
147         assertEquals(time.hour, anotherTime.hour);
148         assertEquals(time.monthDay, anotherTime.monthDay);
149         assertEquals(time.month, anotherTime.month);
150         assertEquals(time.year, anotherTime.year);
151         assertEquals(time.weekDay, anotherTime.weekDay);
152         assertEquals(time.yearDay, anotherTime.yearDay);
153         assertEquals(time.isDst, anotherTime.isDst);
154         assertEquals(time.gmtoff, anotherTime.gmtoff);
155     }
156 
157     @Test
testGetWeekNumber()158     public void testGetWeekNumber() {
159         Time time = new Time();
160         time.normalize(false);
161         time.set(12, 10, 2008);
162         assertEquals(45, time.getWeekNumber());
163 
164         assertEquals("20081112", time.format2445());
165 
166         assertEquals("2008-11-12", time.format3339(true));
167         assertEquals("2008-11-12T00:00:00.000+00:00", time.format3339(false));
168 
169         Time anotherTime = new Time();
170         assertTrue(anotherTime.parse3339("2008-11-12T00:00:00.000+00:00"));
171         assertEquals(time.year, anotherTime.year);
172         assertEquals(time.month, anotherTime.month);
173         assertEquals(time.monthDay, anotherTime.monthDay);
174         assertEquals(Time.TIMEZONE_UTC, anotherTime.timezone);
175 
176         try {
177             anotherTime.parse3339("2008-111-12T00:00:00.000+00:00");
178             fail("should throw exception");
179         } catch (TimeFormatException e) {
180             // expected
181         }
182     }
183 
184     @Test(expected=NullPointerException.class)
testParseNull()185     public void testParseNull() {
186         Time t = new Time();
187         t.parse(null);
188     }
189 
190     @Test(expected=NullPointerException.class)
testParse3339Null()191     public void testParse3339Null() {
192         Time t = new Time();
193         t.parse3339(null);
194     }
195 
196     // http://code.google.com/p/android/issues/detail?id=16002
197     // We'd leak one JNI global reference each time parsing failed.
198     // This would cause a crash when we filled the global reference table.
199     @Test
testBug16002()200     public void testBug16002() {
201         Time t = new Time();
202         for (int i = 0; i < 8192; ++i) {
203             try {
204                 t.parse3339("xxx");
205                 fail();
206             } catch (TimeFormatException expected) {
207             }
208         }
209     }
210 
211     // http://code.google.com/p/android/issues/detail?id=22225
212     // We'd leak one JNI global reference each time parsing failed.
213     // This would cause a crash when we filled the global reference table.
214     @Test
testBug22225()215     public void testBug22225() {
216         Time t = new Time();
217         for (int i = 0; i < 8192; ++i) {
218             try {
219                 t.parse("xxx");
220                 fail();
221             } catch (TimeFormatException expected) {
222             }
223         }
224     }
225 
226     @Test
testIsEpoch()227     public void testIsEpoch() {
228         // Create a Time that uses UTC to provide a behavior baseline.
229         Time time = new Time(Time.TIMEZONE_UTC);
230 
231         // Time is initialized to 1970-01-01 00:00:00
232         assertTrue(Time.isEpoch(time));
233 
234         // 1970-01-01 23:59:59
235         checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 23, 59, 59, true);
236 
237         // 1970-01-02 00:00:00
238         checkIsEpochResult(time, 1970, 0 /* Jan */, 2, 0, 0, 0, false);
239 
240         // 1969-12-31 23:59:59
241         checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 23, 59, 59, false);
242 
243         // Now demonstrate that the isEpoch() method just checks against the Julian day
244         // calculated for UTC. America/Los_Angeles is UTC-8 so all times have to be adjusted
245         // by 8 hours.
246         time.timezone = "America/Los_Angeles";
247 
248         // 1969-12-31 15:59:59 == 1969-12-31 23:59:59 in UTC
249         checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 15, 59, 59, false);
250 
251         // 1969-12-31 16:00:00 == 1970-01-01 00:00:00 in UTC
252         checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 16, 0, 0, true);
253 
254         // 1970-01-01 15:59:59 == 1970-01-01 23:59:59 in UTC
255         checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 15, 59, 59, true);
256 
257         // 1970-01-01 16:00:00 == 1970-01-02 00:00:00 in UTC
258         checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 16, 0, 0, false);
259     }
260 
checkIsEpochResult(Time time, int year, int month, int monthDay, int hour, int minute, int second, boolean expectedIsEpoch)261     private void checkIsEpochResult(Time time, int year, int month, int monthDay, int hour,
262             int minute, int second, boolean expectedIsEpoch) {
263         time.set(second, minute, hour, monthDay, month, year);
264         time.normalize(false);
265         assertEquals(expectedIsEpoch, Time.isEpoch(time));
266     }
267 
268     @Test
testAfterBefore()269     public void testAfterBefore() {
270         Time a = new Time(Time.TIMEZONE_UTC);
271         Time b = new Time("America/Los_Angeles");
272         assertFalse(a.after(b));
273         assertTrue(b.after(a));
274         assertFalse(b.before(a));
275         assertTrue(a.before(b));
276     }
277 
278     // Below are tests merged from Google
279     private static class DateTest {
280         public int year1;
281         public int month1;
282         public int day1;
283         public int hour1;
284         public int minute1;
285         public int dst1;
286 
287         public int delta;
288 
289         public int year2;
290         public int month2;
291         public int day2;
292         public int hour2;
293         public int minute2;
294         public int dst2;
295 
DateTest(int year1, int month1, int day1, int hour1, int minute1, int dst1, int delta, int year2, int month2, int day2, int hour2, int minute2, int dst2)296         public DateTest(int year1, int month1, int day1, int hour1, int minute1, int dst1,
297                 int delta, int year2, int month2, int day2, int hour2, int minute2,
298                 int dst2) {
299             this.year1 = year1;
300             this.month1 = month1;
301             this.day1 = day1;
302             this.hour1 = hour1;
303             this.minute1 = minute1;
304             this.dst1 = dst1;
305             this.delta = delta;
306             this.year2 = year2;
307             this.month2 = month2;
308             this.day2 = day2;
309             this.hour2 = hour2;
310             this.minute2 = minute2;
311             this.dst2 = dst2;
312         }
313 
DateTest(int year1, int month1, int day1, int hour1, int minute1, int delta, int year2, int month2, int day2, int hour2, int minute2)314         public DateTest(int year1, int month1, int day1, int hour1, int minute1,
315                 int delta, int year2, int month2, int day2, int hour2, int minute2) {
316             this.year1 = year1;
317             this.month1 = month1;
318             this.day1 = day1;
319             this.hour1 = hour1;
320             this.minute1 = minute1;
321             this.dst1 = -1;
322             this.delta = delta;
323             this.year2 = year2;
324             this.month2 = month2;
325             this.day2 = day2;
326             this.hour2 = hour2;
327             this.minute2 = minute2;
328             this.dst2 = -1;
329         }
330     }
331 
332     // These tests assume that DST changes on Nov 4, 2007 at 2am (to 1am).
333 
334     // The "offset" field in "dayTests" represents days.
335     // Use normalize(true) with these tests to change the date by 1 day.
336     private DateTest[] dayTests = {
337             // The month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11
338 
339             // Nov 4, 12am + 0 day = Nov 4, 12am
340             // Nov 5, 12am + 0 day = Nov 5, 12am
341             new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0),
342             new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0),
343 
344             // Nov 3, 12am + 1 day = Nov 4, 12am
345             // Nov 4, 12am + 1 day = Nov 5, 12am
346             // Nov 5, 12am + 1 day = Nov 6, 12am
347             new DateTest(2007, 10, 3, 0, 0, 1, 2007, 10, 4, 0, 0),
348             new DateTest(2007, 10, 4, 0, 0, 1, 2007, 10, 5, 0, 0),
349             new DateTest(2007, 10, 5, 0, 0, 1, 2007, 10, 6, 0, 0),
350 
351             // Nov 3, 1am + 1 day = Nov 4, 1am
352             // Nov 4, 1am + 1 day = Nov 5, 1am
353             // Nov 5, 1am + 1 day = Nov 6, 1am
354             new DateTest(2007, 10, 3, 1, 0, 1, 2007, 10, 4, 1, 0),
355             new DateTest(2007, 10, 4, 1, 0, 1, 2007, 10, 5, 1, 0),
356             new DateTest(2007, 10, 5, 1, 0, 1, 2007, 10, 6, 1, 0),
357 
358             // Nov 3, 2am + 1 day = Nov 4, 2am
359             // Nov 4, 2am + 1 day = Nov 5, 2am
360             // Nov 5, 2am + 1 day = Nov 6, 2am
361             new DateTest(2007, 10, 3, 2, 0, 1, 2007, 10, 4, 2, 0),
362             new DateTest(2007, 10, 4, 2, 0, 1, 2007, 10, 5, 2, 0),
363             new DateTest(2007, 10, 5, 2, 0, 1, 2007, 10, 6, 2, 0),
364     };
365 
366     // The "offset" field in "minuteTests" represents minutes.
367     // Use normalize(false) with these tests.
368     private DateTest[] minuteTests = {
369             // The month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11
370 
371             // Nov 4, 12am + 0 minutes = Nov 4, 12am
372             // Nov 5, 12am + 0 minutes = Nov 5, 12am
373             new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0),
374             new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0),
375 
376             // Nov 3, 12am + 60 minutes = Nov 3, 1am
377             // Nov 4, 12am + 60 minutes = Nov 4, 1am
378             // Nov 5, 12am + 60 minutes = Nov 5, 1am
379             new DateTest(2007, 10, 3, 0, 0, 60, 2007, 10, 3, 1, 0),
380             new DateTest(2007, 10, 4, 0, 0, 60, 2007, 10, 4, 1, 0),
381             new DateTest(2007, 10, 5, 0, 0, 60, 2007, 10, 5, 1, 0),
382 
383             // Nov 3, 1am + 60 minutes = Nov 3, 2am
384             // Nov 4, 1am (PDT) + 30 minutes = Nov 4, 1:30am (PDT)
385             // Nov 4, 1am (PDT) + 60 minutes = Nov 4, 1am (PST)
386             new DateTest(2007, 10, 3, 1, 0, 60, 2007, 10, 3, 2, 0),
387             new DateTest(2007, 10, 4, 1, 0, 1, 30, 2007, 10, 4, 1, 30, 1),
388             new DateTest(2007, 10, 4, 1, 0, 1, 60, 2007, 10, 4, 1, 0, 0),
389 
390             // Nov 4, 1:30am (PDT) + 15 minutes = Nov 4, 1:45am (PDT)
391             // Nov 4, 1:30am (PDT) + 30 minutes = Nov 4, 1:00am (PST)
392             // Nov 4, 1:30am (PDT) + 60 minutes = Nov 4, 1:30am (PST)
393             new DateTest(2007, 10, 4, 1, 30, 1, 15, 2007, 10, 4, 1, 45, 1),
394             new DateTest(2007, 10, 4, 1, 30, 1, 30, 2007, 10, 4, 1, 0, 0),
395             new DateTest(2007, 10, 4, 1, 30, 1, 60, 2007, 10, 4, 1, 30, 0),
396 
397             // Nov 4, 1:30am (PST) + 15 minutes = Nov 4, 1:45am (PST)
398             // Nov 4, 1:30am (PST) + 30 minutes = Nov 4, 2:00am (PST)
399             // Nov 5, 1am + 60 minutes = Nov 5, 2am
400             new DateTest(2007, 10, 4, 1, 30, 0, 15, 2007, 10, 4, 1, 45, 0),
401             new DateTest(2007, 10, 4, 1, 30, 0, 30, 2007, 10, 4, 2, 0, 0),
402             new DateTest(2007, 10, 5, 1, 0, 60, 2007, 10, 5, 2, 0),
403 
404             // Nov 3, 2am + 60 minutes = Nov 3, 3am
405             // Nov 4, 2am + 30 minutes = Nov 4, 2:30am
406             // Nov 4, 2am + 60 minutes = Nov 4, 3am
407             // Nov 5, 2am + 60 minutes = Nov 5, 3am
408             new DateTest(2007, 10, 3, 2, 0, 60, 2007, 10, 3, 3, 0),
409             new DateTest(2007, 10, 4, 2, 0, 30, 2007, 10, 4, 2, 30),
410             new DateTest(2007, 10, 4, 2, 0, 60, 2007, 10, 4, 3, 0),
411             new DateTest(2007, 10, 5, 2, 0, 60, 2007, 10, 5, 3, 0),
412     };
413 
414     @Test
testNormalize1()415     public void testNormalize1() {
416         String tz = "America/Los_Angeles";
417         Time local = new Time(tz);
418 
419         int len = dayTests.length;
420         for (int index = 0; index < len; index++) {
421             DateTest test = dayTests[index];
422             local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
423             // call normalize() to make sure that isDst is set
424             local.normalize(false /* use isDst */);
425             local.monthDay += test.delta;
426             local.normalize(true /* ignore isDst */);
427 
428             Time expected = new Time(tz);
429             Fields.setDateTime(expected, test.year2, test.month2, test.day2, test.hour2,
430                     test.minute2, 0);
431             Fields.verifyTimeEquals("day test index " + index + ", normalize():",
432                     Fields.MAIN_DATE_TIME, expected, local);
433 
434             local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
435             // call normalize() to make sure that isDst is set
436             local.normalize(false /* use isDst */);
437             local.monthDay += test.delta;
438             long millis = local.toMillis(true /* ignore isDst */);
439             local.set(millis);
440 
441             expected = new Time(tz);
442             Fields.setDateTime(expected, test.year2, test.month2, test.day2, test.hour2,
443                     test.minute2, 0);
444             Fields.verifyTimeEquals("day test index " + index + ", toMillis():",
445                     Fields.MAIN_DATE_TIME, expected, local);
446         }
447 
448         len = minuteTests.length;
449         for (int index = 0; index < len; index++) {
450             DateTest test = minuteTests[index];
451             local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
452             local.isDst = test.dst1;
453             // call normalize() to make sure that isDst is set
454             local.normalize(false /* use isDst */);
455             if (test.dst2 == -1) test.dst2 = local.isDst;
456             local.minute += test.delta;
457             local.normalize(false /* use isDst */);
458 
459             Time expected = new Time(tz);
460             Fields.setDateTime(expected, test.year2, test.month2, test.day2, test.hour2,
461                     test.minute2, 0);
462             Fields.setDst(expected, test.dst2 /* isDst */, PstPdt.getUtcOffsetSeconds(test.dst2));
463             Fields.verifyTimeEquals("minute test index " + index + ", normalize():",
464                     Fields.MAIN_DATE_TIME | Fields.DST_FIELDS, expected, local);
465 
466             local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
467             local.isDst = test.dst1;
468             // call normalize() to make sure that isDst is set
469             local.normalize(false /* use isDst */);
470             if (test.dst2 == -1) test.dst2 = local.isDst;
471             local.minute += test.delta;
472             long millis = local.toMillis(false /* use isDst */);
473             local.set(millis);
474 
475             expected = new Time(tz);
476             Fields.setDateTime(expected, test.year2, test.month2, test.day2, test.hour2,
477                     test.minute2, 0);
478             Fields.setDst(expected, test.dst2 /* isDst */, PstPdt.getUtcOffsetSeconds(test.dst2));
479             Fields.verifyTimeEquals("minute test index " + index + ", toMillis():",
480                     Fields.MAIN_DATE_TIME | Fields.DST_FIELDS, expected, local);
481         }
482     }
483 
484     @Test
testSwitchTimezone_simpleUtc()485     public void testSwitchTimezone_simpleUtc() {
486         String originalTz = Time.TIMEZONE_UTC;
487         Time t = new Time(originalTz);
488         Fields.set(t, 2006, 9, 5, 12, 0, 0, -1 /* isDst */, 0, 0, 0);
489 
490         String newTz = "America/Los_Angeles";
491         t.switchTimezone(newTz);
492 
493         Time expected1 = new Time(newTz);
494         Fields.set(expected1, 2006, 9, 5, 5, 0, 0, 1 /* isDst */, -25200, 277, 4);
495         Fields.verifyTimeEquals(expected1, t);
496 
497         t.switchTimezone(originalTz);
498 
499         Time expected2 = new Time(originalTz);
500         Fields.set(expected2, 2006, 9, 5, 12, 0, 0, 0 /* isDst */, 0, 277, 4);
501         Fields.verifyTimeEquals(expected2, t);
502     }
503 
504     @Test
testSwitchTimezone_standardToStandardTime()505     public void testSwitchTimezone_standardToStandardTime() {
506         String zone1 = "Europe/London";
507         String zone2 = "America/Los_Angeles";
508 
509         // A time unambiguously in standard time in both zones.
510         Time t = new Time(zone1);
511         Fields.set(t, 2007, 2, 10, 12, 0, 0, -1 /* isDst */, 0, 0, 0);
512 
513         t.switchTimezone(zone2);
514 
515         Time expected1 = new Time(zone2);
516         Fields.set(expected1, 2007, 2, 10, 4, 0, 0, 0 /* isDst */, -28800, 68, 6);
517         Fields.verifyTimeEquals(expected1, t);
518 
519         t.switchTimezone(zone1);
520 
521         Time expected2 = new Time(zone1);
522         Fields.set(expected2, 2007, 2, 10, 12, 0, 0, 0 /* isDst */, 0, 68, 6);
523         Fields.verifyTimeEquals(expected2, t);
524     }
525 
526     @Test
testSwitchTimezone_dstToDstTime()527     public void testSwitchTimezone_dstToDstTime() {
528         String zone1 = "Europe/London";
529         String zone2 = "America/Los_Angeles";
530 
531         // A time unambiguously in DST in both zones.
532         Time t = new Time(zone1);
533         Fields.set(t, 2007, 2, 26, 12, 0, 0, -1 /* isDst */, 0, 0, 0);
534 
535         t.switchTimezone(zone2);
536 
537         Time expected1 = new Time(zone2);
538         Fields.set(expected1, 2007, 2, 26, 4, 0, 0, 1 /* isDst */, -25200, 84, 1);
539         Fields.verifyTimeEquals(expected1, t);
540 
541         t.switchTimezone(zone1);
542 
543         Time expected2 = new Time(zone1);
544         Fields.set(expected2, 2007, 2, 26, 12, 0, 0, 1 /* isDst */, 3600, 84, 1);
545         Fields.verifyTimeEquals(expected2, t);
546     }
547 
548     @Test
testSwitchTimezone_standardToDstTime()549     public void testSwitchTimezone_standardToDstTime() {
550         String zone1 = "Europe/London";
551         String zone2 = "America/Los_Angeles";
552 
553         // A time that is in standard time in zone1, but in DST in zone2.
554         Time t = new Time(zone1);
555         Fields.set(t, 2007, 2, 24, 12, 0, 0, -1 /* isDst */, 0, 0, 0);
556 
557         t.switchTimezone(zone2);
558 
559         Time expected1 = new Time(zone2);
560         Fields.set(expected1, 2007, 2, 24, 5, 0, 0, 1 /* isDst */, -25200, 82, 6);
561         Fields.verifyTimeEquals(expected1, t);
562 
563         t.switchTimezone(zone1);
564 
565         Time expected2 = new Time(zone1);
566         Fields.set(expected2, 2007, 2, 24, 12, 0, 0, 0 /* isDst */, 0, 82, 6);
567         Fields.verifyTimeEquals(expected2, t);
568     }
569 
570     @Test
testSwitchTimezone_sourceDateInvalid()571     public void testSwitchTimezone_sourceDateInvalid() {
572         String zone1 = "Europe/London";
573         String zone2 = "America/Los_Angeles";
574 
575         // A source wall time known not to normalize because it doesn't "exist" locally.
576         Time t = new Time(zone1);
577         Fields.set(t, 2007, 2, 25, 1, 30, 0, -1 /* isDst */, 0, 0, 0);
578         assertEquals(-1, t.toMillis(false));
579 
580         t.switchTimezone(zone2);
581 
582         // It is assumed a sad trombone noise is also emitted from the device at this point.
583         // This illustrates why using -1 to indicate a problem, when -1 is in range, is a poor idea.
584         Time expected1 = new Time(zone2);
585         Fields.set(expected1, 1969, 11, 31, 15, 59, 59, 0 /* isDst */, -28800, 364, 3);
586         Fields.verifyTimeEquals(expected1, t);
587     }
588 
589     @Test
testSwitchTimezone_dstToStandardTime()590     public void testSwitchTimezone_dstToStandardTime() {
591         String zone1 = "America/Los_Angeles";
592         String zone2 = "Europe/London";
593 
594         // A time that is in DST in zone1, but in standard in zone2.
595         Time t = new Time(zone1);
596         Fields.set(t, 2007, 2, 12, 12, 0, 0, -1 /* isDst */, 0, 0, 0);
597 
598         t.switchTimezone(zone2);
599 
600         Time expected1 = new Time(zone2);
601         Fields.set(expected1, 2007, 2, 12, 19, 0, 0, 0 /* isDst */, 0, 70, 1);
602         Fields.verifyTimeEquals(expected1, t);
603 
604         t.switchTimezone(zone1);
605 
606         Time expected2 = new Time(zone1);
607         Fields.set(expected2, 2007, 2, 12, 12, 0, 0, 1 /* isDst */, -25200, 70, 1);
608         Fields.verifyTimeEquals(expected2, t);
609     }
610 
611     @Test
testCtor()612     public void testCtor() {
613         String tz = Time.TIMEZONE_UTC;
614         Time t = new Time(tz);
615         assertEquals(tz, t.timezone);
616 
617         Time expected = new Time(tz);
618         Fields.set(expected, 1970, 0, 1, 0, 0, 0, -1 /* isDst */, 0, 0, 0);
619         Fields.verifyTimeEquals(expected, t);
620     }
621 
622     @Test
testGetActualMaximum()623     public void testGetActualMaximum() {
624         Time t = new Time(Time.TIMEZONE_UTC);
625         assertEquals(59, t.getActualMaximum(Time.SECOND));
626         assertEquals(59, t.getActualMaximum(Time.MINUTE));
627         assertEquals(23, t.getActualMaximum(Time.HOUR));
628         assertEquals(11, t.getActualMaximum(Time.MONTH));
629         assertEquals(2037, t.getActualMaximum(Time.YEAR));
630         assertEquals(6, t.getActualMaximum(Time.WEEK_DAY));
631         assertEquals(364, t.getActualMaximum(Time.YEAR_DAY));
632         t.year = 2000;
633         assertEquals(365, t.getActualMaximum(Time.YEAR_DAY));
634 
635         try {
636             t.getActualMaximum(Time.WEEK_NUM);
637             fail("should throw runtime exception");
638         } catch (Exception e) {
639             // expected
640         }
641         final int noExistField = -1;
642         try {
643             t.getActualMaximum(noExistField);
644         } catch (Exception e) {
645             // expected
646         }
647 
648         t.year = 2001;
649         final int[] DAYS_PER_MONTH = {
650                 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
651         };
652         verifyMonth(t, DAYS_PER_MONTH);
653 
654         t.year = 2000;
655         DAYS_PER_MONTH[1] = 29;
656         verifyMonth(t, DAYS_PER_MONTH);
657     }
658 
verifyMonth(Time t, final int[] DAYS_PER_MONTH)659     private void verifyMonth(Time t, final int[] DAYS_PER_MONTH) {
660         for (int i = 0; i < t.getActualMaximum(Time.MONTH); i++) {
661             t.month = i;
662             assertEquals(DAYS_PER_MONTH[i], t.getActualMaximum(Time.MONTH_DAY));
663         }
664     }
665 
666     @Test
testClear0()667     public void testClear0() {
668         Time t = new Time(Time.getCurrentTimezone());
669         t.clear(Time.TIMEZONE_UTC);
670         assertEquals(Time.TIMEZONE_UTC, t.timezone);
671         assertFalse(t.allDay);
672         assertEquals(0, t.second);
673         assertEquals(0, t.minute);
674         assertEquals(0, t.hour);
675         assertEquals(0, t.monthDay);
676         assertEquals(0, t.month);
677         assertEquals(0, t.year);
678         assertEquals(0, t.weekDay);
679         assertEquals(0, t.yearDay);
680         assertEquals(0, t.gmtoff);
681         assertEquals(-1, t.isDst);
682     }
683 
684     @Test
testClear()685     public void testClear() {
686         Time t = new Time("America/Los_Angeles");
687         Fields.set(t, 1, 2, 3, 4, 5, 6, 7 /* isDst */, 8, 9, 10);
688 
689         t.clear(Time.TIMEZONE_UTC);
690 
691         Time expected = new Time(Time.TIMEZONE_UTC);
692         Fields.set(expected, 0, 0, 0, 0, 0, 0, -1 /* isDst */, 0, 0, 0);
693         Fields.verifyTimeEquals(expected, t);
694     }
695 
696     @Test
testCompare()697     public void testCompare() {
698         String timezone = "America/New_York";
699         int[] aDateTimeFields = new int[] { 2005, 2, 3, 4, 5, 6 };
700 
701         Time a = new Time(timezone);
702         Fields.setDateTime(a, aDateTimeFields);
703         Fields.setDst(a, 7, 8);
704         Fields.setDerivedDateTime(a, 9, 10);
705 
706         int[] bDateTimeFields = new int[aDateTimeFields.length];
707         System.arraycopy(aDateTimeFields, 0, bDateTimeFields, 0, aDateTimeFields.length);
708         Time b = new Time(timezone);
709         Fields.setDateTime(b, bDateTimeFields);
710         Fields.setDst(b, 7, 8);
711         Fields.setDerivedDateTime(b, 9, 10);
712 
713         // Confirm timezone behavior: When timezones differ the result depends on the millis time.
714         // This means that all date/time and the isDst field can impact compare() when timezones
715         // are different. The result is always -1, 0 or 1.
716 
717         // East of New York. Millis goes down for a given wall time.
718         b.timezone = "Europe/London";
719         assertEquals(1, Time.compare(a, b));
720         assertEquals(-1, Time.compare(b, a));
721 
722         // West of New York. Millis goes up for a given wall time.
723         b.timezone = "America/Los_Angeles";
724         assertEquals(-1, Time.compare(a, b));
725         assertEquals(1, Time.compare(b, a));
726 
727         b.timezone = timezone;
728         assertEquals(0, Time.compare(a, b));
729         assertEquals(0, Time.compare(b, a));
730 
731         // Now confirm behavior when timezones are the same: Only the date/time fields are checked
732         // and the result depends on the difference between the fields and the order in which they
733         // are checked.
734 
735         // Check date/time fields
736         for (int i = 0; i < aDateTimeFields.length; i++) {
737             bDateTimeFields[i] = 99999;
738             Fields.setDateTime(b, bDateTimeFields);
739 
740             assertEquals(aDateTimeFields[i] - bDateTimeFields[i], Time.compare(a, b));
741             assertEquals(bDateTimeFields[i] - aDateTimeFields[i], Time.compare(b, a));
742 
743             bDateTimeFields[i] = -99999;
744             Fields.setDateTime(b, bDateTimeFields);
745 
746             assertEquals(aDateTimeFields[i] - bDateTimeFields[i], Time.compare(a, b));
747             assertEquals(bDateTimeFields[i] - aDateTimeFields[i], Time.compare(b, a));
748 
749             bDateTimeFields[i] = aDateTimeFields[i];
750             Fields.setDateTime(b, bDateTimeFields);
751 
752             assertEquals(0, Time.compare(a, b));
753             assertEquals(0, Time.compare(b, a));
754         }
755 
756         // Show that the derived fields have no effect on compare when timezones are the same.
757         Fields.setDst(b, 999, 999);
758         Fields.setDerivedDateTime(b, 999, 999);
759         assertEquals(0, Time.compare(a, b));
760         assertEquals(0, Time.compare(b, a));
761     }
762 
763     @Test(expected=NullPointerException.class)
testCompareNullSecond()764     public void testCompareNullSecond() {
765         Time a = new Time(Time.TIMEZONE_UTC);
766         Time.compare(a, null);
767     }
768 
769     @Test(expected=NullPointerException.class)
testCompareNullFirst()770     public void testCompareNullFirst() {
771         Time a = new Time(Time.TIMEZONE_UTC);
772         Time.compare(null, a);
773     }
774 
775     @Test(expected=NullPointerException.class)
testCompareNullBoth()776     public void testCompareNullBoth() {
777         Time.compare(null, null);
778     }
779 
780     @Test
testCompare_invalidDatesAreEqualIfTimezoneDiffers()781     public void testCompare_invalidDatesAreEqualIfTimezoneDiffers() {
782         String timezone = "America/New_York";
783         // This date is outside of the valid set of dates that can be calculated so toMillis()
784         // returns -1.
785         int[] dateTimeFields = new int[] { 1, 2, 3, 4, 5, 6 };
786 
787         Time a = new Time(timezone);
788         Fields.setDateTime(a, dateTimeFields);
789         Fields.setDst(a, 7, 8);
790         Fields.setDerivedDateTime(a, 9, 10);
791         assertEquals(-1, a.toMillis(false));
792 
793         Time b = new Time(timezone);
794         Fields.setDateTime(b, dateTimeFields);
795         Fields.setDst(b, 11, 12);
796         Fields.setDerivedDateTime(b, 13, 14);
797         assertEquals(-1, b.toMillis(false));
798 
799         // DST and derived-date time fields are always ignored.
800         assertEquals(0, Time.compare(a, b));
801 
802         // Set a different invalid set of date fields.
803         Fields.setDateTime(b, new int[] { 6, 5, 4, 3, 2, 1 });
804         assertEquals(-5, Time.compare(a, b));
805 
806         // Now change the timezone
807         b.timezone = Time.TIMEZONE_UTC;
808 
809         // >:-(
810         assertEquals(0, Time.compare(a, b));
811     }
812 
813     @Test
testFormat()814     public void testFormat() {
815         Time t = new Time(Time.TIMEZONE_UTC);
816         String r = t.format("%Y%m%dT%H%M%S");
817         assertEquals("19700101T000000", r);
818     }
819 
820     @Test
testFormat_null()821     public void testFormat_null() {
822         Time t = new Time(Time.TIMEZONE_UTC);
823         assertEquals(t.format("%c"), t.format(null));
824     }
825 
826     @Test
testFormat_badPatterns()827     public void testFormat_badPatterns() {
828         Time t = new Time(Time.TIMEZONE_UTC);
829         verifyFormatEquals(t, "%~Y", "~Y");
830         verifyFormatEquals(t, "%", "%");
831     }
832 
833     @Test
testFormat_doesNotNormalize()834     public void testFormat_doesNotNormalize() {
835         Time t = new Time(Time.TIMEZONE_UTC);
836         Fields.set(t, 2005, 13, 32, -1, -1, -1, -2, -2, -2, -2);
837 
838         Time tCopy = new Time(t);
839         Fields.verifyTimeEquals(t, tCopy);
840 
841         verifyFormatEquals(t, "%Y%m%dT%H%M%S", "20051432T-1-1-1");
842 
843         Fields.verifyTimeEquals(t, tCopy);
844     }
845 
verifyFormatEquals(Time t, String formatArg, String expected)846     private static void verifyFormatEquals(Time t, String formatArg, String expected) {
847         assertEquals(expected, t.format(formatArg));
848     }
849 
850     @Test
testFormat_tokensUkLocale()851     public void testFormat_tokensUkLocale() {
852         if (!changeJavaAndAndroidLocale(Locale.UK, false /* force */)) {
853             Log.w(TAG, "Skipping testFormat_tokensUkLocale: no assets found");
854             return;
855         }
856 
857         Time t = new Time("Europe/London");
858         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
859 
860         // Prove the un-normalized fields are used.
861         verifyFormatEquals(t, "%A", "Sunday");
862 
863         // Set fields like weekday.
864         t.normalize(true);
865 
866         verifyFormatEquals(t, "%A", "Wednesday");
867         verifyFormatEquals(t, "%a", "Wed");
868         verifyFormatEquals(t, "%B", "June");
869         verifyFormatEquals(t, "%b", "Jun");
870         verifyFormatEquals(t, "%C", "20");
871         verifyFormatEquals(t, "%c", "1 Jun 2005, 12:30:15");
872         verifyFormatEquals(t, "%D", "06/01/05");
873         verifyFormatEquals(t, "%d", "01");
874         verifyFormatEquals(t, "%E", "E");
875         verifyFormatEquals(t, "%e", " 1");
876         verifyFormatEquals(t, "%F", "2005-06-01");
877         verifyFormatEquals(t, "%G", "2005");
878         verifyFormatEquals(t, "%g", "05");
879         verifyFormatEquals(t, "%H", "12");
880         verifyFormatEquals(t, "%h", "Jun");
881         verifyFormatEquals(t, "%I", "12");
882         verifyFormatEquals(t, "%j", "152");
883         verifyFormatEquals(t, "%K", "K");
884         verifyFormatEquals(t, "%k", "12");
885         verifyFormatEquals(t, "%l", "12");
886         verifyFormatEquals(t, "%M", "30");
887         verifyFormatEquals(t, "%m", "06");
888         verifyFormatEquals(t, "%n", "\n");
889         verifyFormatEquals(t, "%O", "O");
890         verifyFormatEquals(t, "%p", "pm");
891         verifyFormatEquals(t, "%P", "pm");
892         verifyFormatEquals(t, "%R", "12:30");
893         verifyFormatEquals(t, "%r", "12:30:15 pm");
894         verifyFormatEquals(t, "%S", "15");
895         // The original C implementation uses the (native) system default TZ, not the timezone of
896         // the Time to calculate this and was therefore not stable. This changed to use the Time's
897         // timezone when the Time class was re-written in Java.
898         verifyFormatEquals(t, "%s", "1117625415");
899         verifyFormatEquals(t, "%T", "12:30:15");
900         verifyFormatEquals(t, "%t", "\t");
901         verifyFormatEquals(t, "%U", "22");
902         verifyFormatEquals(t, "%u", "3");
903         verifyFormatEquals(t, "%V", "22");
904         verifyFormatEquals(t, "%v", " 1-Jun-2005");
905         verifyFormatEquals(t, "%W", "22");
906         verifyFormatEquals(t, "%w", "3");
907         verifyFormatEquals(t, "%X", "12:30:15");
908         verifyFormatEquals(t, "%x", "1 June 2005");
909         verifyFormatEquals(t, "%y", "05");
910         verifyFormatEquals(t, "%Y", "2005");
911         verifyFormatEquals(t, "%Z", "BST");
912         verifyFormatEquals(t, "%z", "+0100");
913         verifyFormatEquals(t, "%+", "Wed Jun  1 12:30:15 BST 2005");
914         verifyFormatEquals(t, "%%", "%");
915 
916         // Modifiers
917 
918         verifyFormatEquals(t, "%EC", "20");
919         verifyFormatEquals(t, "%OC", "20");
920 
921         verifyFormatEquals(t, "%_+", "Wed Jun  1 12:30:15 BST 2005");
922         verifyFormatEquals(t, "%-+", "Wed Jun  1 12:30:15 BST 2005");
923         verifyFormatEquals(t, "%0+", "Wed Jun  1 12:30:15 BST 2005");
924         verifyFormatEquals(t, "%^+", "Wed Jun  1 12:30:15 BST 2005");
925         verifyFormatEquals(t, "%#+", "Wed Jun  1 12:30:15 BST 2005");
926 
927         verifyFormatEquals(t, "%_A", "Wednesday");
928         verifyFormatEquals(t, "%-A", "Wednesday");
929         verifyFormatEquals(t, "%0A", "Wednesday");
930         verifyFormatEquals(t, "%^A", "WEDNESDAY");
931         verifyFormatEquals(t, "%#A", "wEDNESDAY");
932 
933         verifyFormatEquals(t, "%_Y", "20 5");
934         verifyFormatEquals(t, "%-Y", "205");
935         verifyFormatEquals(t, "%0Y", "2005");
936         verifyFormatEquals(t, "%^Y", "2005");
937         verifyFormatEquals(t, "%#Y", "2005");
938 
939         verifyFormatEquals(t, "%_d", " 1");
940         verifyFormatEquals(t, "%-d", "1");
941         verifyFormatEquals(t, "%0d", "01");
942         verifyFormatEquals(t, "%^d", "01");
943         verifyFormatEquals(t, "%#d", "01");
944     }
945 
946     @Test
testFormat_tokensUsLocale()947     public void testFormat_tokensUsLocale() {
948         if (!changeJavaAndAndroidLocale(Locale.US, false /* force */)) {
949             Log.w(TAG, "Skipping testFormat_tokensUSLocale: no assets found");
950             return;
951         }
952 
953         Time t = new Time("America/New_York");
954         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
955 
956         // Prove the un-normalized fields are used.
957         verifyFormatEquals(t, "%A", "Sunday");
958 
959         // Set fields like weekday.
960         t.normalize(true);
961 
962         verifyFormatEquals(t, "%A", "Wednesday");
963         verifyFormatEquals(t, "%a", "Wed");
964         verifyFormatEquals(t, "%B", "June");
965         verifyFormatEquals(t, "%b", "Jun");
966         verifyFormatEquals(t, "%C", "20");
967         verifyFormatEquals(t, "%c", "Jun 1, 2005, 12:30:15 PM");
968         verifyFormatEquals(t, "%D", "06/01/05");
969         verifyFormatEquals(t, "%d", "01");
970         verifyFormatEquals(t, "%E", "E");
971         verifyFormatEquals(t, "%e", " 1");
972         verifyFormatEquals(t, "%F", "2005-06-01");
973         verifyFormatEquals(t, "%G", "2005");
974         verifyFormatEquals(t, "%g", "05");
975         verifyFormatEquals(t, "%H", "12");
976         verifyFormatEquals(t, "%h", "Jun");
977         verifyFormatEquals(t, "%I", "12");
978         verifyFormatEquals(t, "%j", "152");
979         verifyFormatEquals(t, "%K", "K");
980         verifyFormatEquals(t, "%k", "12");
981         verifyFormatEquals(t, "%l", "12");
982         verifyFormatEquals(t, "%M", "30");
983         verifyFormatEquals(t, "%m", "06");
984         verifyFormatEquals(t, "%n", "\n");
985         verifyFormatEquals(t, "%O", "O");
986         verifyFormatEquals(t, "%p", "PM");
987         verifyFormatEquals(t, "%P", "pm");
988         verifyFormatEquals(t, "%R", "12:30");
989         verifyFormatEquals(t, "%r", "12:30:15 PM");
990         verifyFormatEquals(t, "%S", "15");
991         // The original C implementation uses the (native) system default TZ, not the timezone of
992         // the Time to calculate this and was therefore not stable. This changed to use the Time's
993         // timezone when the Time class was re-written in Java.
994         verifyFormatEquals(t, "%s", "1117643415");
995         verifyFormatEquals(t, "%T", "12:30:15");
996         verifyFormatEquals(t, "%t", "\t");
997         verifyFormatEquals(t, "%U", "22");
998         verifyFormatEquals(t, "%u", "3");
999         verifyFormatEquals(t, "%V", "22");
1000         verifyFormatEquals(t, "%v", " 1-Jun-2005");
1001         verifyFormatEquals(t, "%W", "22");
1002         verifyFormatEquals(t, "%w", "3");
1003         verifyFormatEquals(t, "%X", "12:30:15 PM");
1004         verifyFormatEquals(t, "%x", "June 1, 2005");
1005         verifyFormatEquals(t, "%y", "05");
1006         verifyFormatEquals(t, "%Y", "2005");
1007         verifyFormatEquals(t, "%Z", "EDT");
1008         verifyFormatEquals(t, "%z", "-0400");
1009         verifyFormatEquals(t, "%+", "Wed Jun  1 12:30:15 EDT 2005");
1010         verifyFormatEquals(t, "%%", "%");
1011 
1012         // Modifiers
1013 
1014         verifyFormatEquals(t, "%EC", "20");
1015         verifyFormatEquals(t, "%OC", "20");
1016 
1017         verifyFormatEquals(t, "%_+", "Wed Jun  1 12:30:15 EDT 2005");
1018         verifyFormatEquals(t, "%-+", "Wed Jun  1 12:30:15 EDT 2005");
1019         verifyFormatEquals(t, "%0+", "Wed Jun  1 12:30:15 EDT 2005");
1020         verifyFormatEquals(t, "%^+", "Wed Jun  1 12:30:15 EDT 2005");
1021         verifyFormatEquals(t, "%#+", "Wed Jun  1 12:30:15 EDT 2005");
1022 
1023         verifyFormatEquals(t, "%_A", "Wednesday");
1024         verifyFormatEquals(t, "%-A", "Wednesday");
1025         verifyFormatEquals(t, "%0A", "Wednesday");
1026         verifyFormatEquals(t, "%^A", "WEDNESDAY");
1027         verifyFormatEquals(t, "%#A", "wEDNESDAY");
1028 
1029         verifyFormatEquals(t, "%_Y", "20 5");
1030         verifyFormatEquals(t, "%-Y", "205");
1031         verifyFormatEquals(t, "%0Y", "2005");
1032         verifyFormatEquals(t, "%^Y", "2005");
1033         verifyFormatEquals(t, "%#Y", "2005");
1034 
1035         verifyFormatEquals(t, "%_d", " 1");
1036         verifyFormatEquals(t, "%-d", "1");
1037         verifyFormatEquals(t, "%0d", "01");
1038         verifyFormatEquals(t, "%^d", "01");
1039         verifyFormatEquals(t, "%#d", "01");
1040     }
1041 
1042     @Test
testFormat_tokensFranceLocale()1043     public void testFormat_tokensFranceLocale() {
1044         if (!changeJavaAndAndroidLocale(Locale.FRANCE, false /* force */)) {
1045             Log.w(TAG, "Skipping testFormat_tokensFranceLocale: no assets found");
1046             return;
1047         }
1048 
1049         Time t = new Time("Europe/Paris");
1050         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
1051 
1052         // Prove the un-normalized fields are used.
1053         verifyFormatEquals(t, "%A", "dimanche");
1054 
1055         // Set fields like weekday.
1056         t.normalize(true);
1057 
1058         verifyFormatEquals(t, "%A", "mercredi");
1059         verifyFormatEquals(t, "%a", "mer.");
1060         verifyFormatEquals(t, "%B", "juin");
1061         verifyFormatEquals(t, "%b", "juin");
1062         verifyFormatEquals(t, "%C", "20");
1063         verifyFormatEquals(t, "%c", "1 juin 2005 à 12:30:15");
1064         verifyFormatEquals(t, "%D", "06/01/05");
1065         verifyFormatEquals(t, "%d", "01");
1066         verifyFormatEquals(t, "%E", "E");
1067         verifyFormatEquals(t, "%e", " 1");
1068         verifyFormatEquals(t, "%F", "2005-06-01");
1069         verifyFormatEquals(t, "%G", "2005");
1070         verifyFormatEquals(t, "%g", "05");
1071         verifyFormatEquals(t, "%H", "12");
1072         verifyFormatEquals(t, "%h", "juin");
1073         verifyFormatEquals(t, "%I", "12");
1074         verifyFormatEquals(t, "%j", "152");
1075         verifyFormatEquals(t, "%K", "K");
1076         verifyFormatEquals(t, "%k", "12");
1077         verifyFormatEquals(t, "%l", "12");
1078         verifyFormatEquals(t, "%M", "30");
1079         verifyFormatEquals(t, "%m", "06");
1080         verifyFormatEquals(t, "%n", "\n");
1081         verifyFormatEquals(t, "%O", "O");
1082         verifyFormatEquals(t, "%p", "PM");
1083         verifyFormatEquals(t, "%P", "pm");
1084         verifyFormatEquals(t, "%R", "12:30");
1085         verifyFormatEquals(t, "%r", "12:30:15 PM");
1086         verifyFormatEquals(t, "%S", "15");
1087         // The original C implementation uses the (native) system default TZ, not the timezone of
1088         // the Time to calculate this and was therefore not stable. This changed to use the Time's
1089         // timezone when the Time class was re-written in Java.
1090         verifyFormatEquals(t, "%s", "1117621815");
1091         verifyFormatEquals(t, "%T", "12:30:15");
1092         verifyFormatEquals(t, "%t", "\t");
1093         verifyFormatEquals(t, "%U", "22");
1094         verifyFormatEquals(t, "%u", "3");
1095         verifyFormatEquals(t, "%V", "22");
1096         verifyFormatEquals(t, "%v", " 1-juin-2005");
1097         verifyFormatEquals(t, "%W", "22");
1098         verifyFormatEquals(t, "%w", "3");
1099         verifyFormatEquals(t, "%X", "12:30:15");
1100         verifyFormatEquals(t, "%x", "1 juin 2005");
1101         verifyFormatEquals(t, "%y", "05");
1102         verifyFormatEquals(t, "%Y", "2005");
1103         verifyFormatEquals(t, "%Z", "GMT+02:00");
1104         verifyFormatEquals(t, "%z", "+0200");
1105         verifyFormatEquals(t, "%+", "mer. juin  1 12:30:15 GMT+02:00 2005");
1106         verifyFormatEquals(t, "%%", "%");
1107 
1108         // Modifiers
1109 
1110         verifyFormatEquals(t, "%EC", "20");
1111         verifyFormatEquals(t, "%OC", "20");
1112 
1113         verifyFormatEquals(t, "%_+", "mer. juin  1 12:30:15 GMT+02:00 2005");
1114         verifyFormatEquals(t, "%-+", "mer. juin  1 12:30:15 GMT+02:00 2005");
1115         verifyFormatEquals(t, "%0+", "mer. juin  1 12:30:15 GMT+02:00 2005");
1116         verifyFormatEquals(t, "%^+", "mer. juin  1 12:30:15 GMT+02:00 2005");
1117         verifyFormatEquals(t, "%#+", "mer. juin  1 12:30:15 GMT+02:00 2005");
1118 
1119         verifyFormatEquals(t, "%_A", "mercredi");
1120         verifyFormatEquals(t, "%-A", "mercredi");
1121         verifyFormatEquals(t, "%0A", "mercredi");
1122         verifyFormatEquals(t, "%^A", "MERCREDI");
1123         verifyFormatEquals(t, "%#A", "MERCREDI");
1124 
1125         verifyFormatEquals(t, "%_Y", "20 5");
1126         verifyFormatEquals(t, "%-Y", "205");
1127         verifyFormatEquals(t, "%0Y", "2005");
1128         verifyFormatEquals(t, "%^Y", "2005");
1129         verifyFormatEquals(t, "%#Y", "2005");
1130 
1131         verifyFormatEquals(t, "%_d", " 1");
1132         verifyFormatEquals(t, "%-d", "1");
1133         verifyFormatEquals(t, "%0d", "01");
1134         verifyFormatEquals(t, "%^d", "01");
1135         verifyFormatEquals(t, "%#d", "01");
1136     }
1137 
1138     @Test
testFormat_tokensJapanLocale()1139     public void testFormat_tokensJapanLocale() {
1140         if (!changeJavaAndAndroidLocale(Locale.JAPAN, false /* force */)) {
1141             Log.w(TAG, "Skipping testFormat_tokensJapanLocale: no assets found");
1142             return;
1143         }
1144 
1145         Time t = new Time("Asia/Tokyo");
1146         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
1147 
1148         // Prove the un-normalized fields are used.
1149         verifyFormatEquals(t, "%A", "日曜日");
1150 
1151         // Set fields like weekday.
1152         t.normalize(true);
1153 
1154         verifyFormatEquals(t, "%A", "水曜日");
1155         verifyFormatEquals(t, "%a", "水");
1156         verifyFormatEquals(t, "%B", "6月");
1157         verifyFormatEquals(t, "%b", "6月");
1158         verifyFormatEquals(t, "%C", "20");
1159         verifyFormatEquals(t, "%c", "2005/06/01 12:30:15");
1160         verifyFormatEquals(t, "%D", "06/01/05");
1161         verifyFormatEquals(t, "%d", "01");
1162         verifyFormatEquals(t, "%E", "E");
1163         verifyFormatEquals(t, "%e", " 1");
1164         verifyFormatEquals(t, "%F", "2005-06-01");
1165         verifyFormatEquals(t, "%G", "2005");
1166         verifyFormatEquals(t, "%g", "05");
1167         verifyFormatEquals(t, "%H", "12");
1168         verifyFormatEquals(t, "%h", "6月");
1169         verifyFormatEquals(t, "%I", "12");
1170         verifyFormatEquals(t, "%j", "152");
1171         verifyFormatEquals(t, "%k", "12");
1172         verifyFormatEquals(t, "%l", "12");
1173         verifyFormatEquals(t, "%M", "30");
1174         verifyFormatEquals(t, "%m", "06");
1175         verifyFormatEquals(t, "%n", "\n");
1176         verifyFormatEquals(t, "%O", "O");
1177         verifyFormatEquals(t, "%p", "午後");
1178         verifyFormatEquals(t, "%P", "午後");
1179         verifyFormatEquals(t, "%R", "12:30");
1180         verifyFormatEquals(t, "%r", "12:30:15 午後");
1181         verifyFormatEquals(t, "%S", "15");
1182         // The original C implementation uses the (native) system default TZ, not the timezone of
1183         // the Time to calculate this and was therefore not stable. This changed to use the Time's
1184         // timezone when the Time class was re-written in Java.
1185         verifyFormatEquals(t, "%s", "1117596615");
1186         verifyFormatEquals(t, "%T", "12:30:15");
1187         verifyFormatEquals(t, "%t", "\t");
1188         verifyFormatEquals(t, "%U", "22");
1189         verifyFormatEquals(t, "%u", "3");
1190         verifyFormatEquals(t, "%V", "22");
1191         verifyFormatEquals(t, "%v", " 1-6月-2005");
1192         verifyFormatEquals(t, "%W", "22");
1193         verifyFormatEquals(t, "%w", "3");
1194         verifyFormatEquals(t, "%X", "12:30:15");
1195         verifyFormatEquals(t, "%x", "2005年6月1日");
1196         verifyFormatEquals(t, "%y", "05");
1197         verifyFormatEquals(t, "%Y", "2005");
1198         verifyFormatEquals(t, "%Z", "JST");
1199         verifyFormatEquals(t, "%z", "+0900");
1200         verifyFormatEquals(t, "%+", "水 6月  1 12:30:15 JST 2005");
1201         verifyFormatEquals(t, "%%", "%");
1202 
1203         // Modifiers
1204 
1205         verifyFormatEquals(t, "%EC", "20");
1206         verifyFormatEquals(t, "%OC", "20");
1207 
1208         verifyFormatEquals(t, "%_+", "水 6月  1 12:30:15 JST 2005");
1209         verifyFormatEquals(t, "%-+", "水 6月  1 12:30:15 JST 2005");
1210         verifyFormatEquals(t, "%0+", "水 6月  1 12:30:15 JST 2005");
1211         verifyFormatEquals(t, "%^+", "水 6月  1 12:30:15 JST 2005");
1212         verifyFormatEquals(t, "%#+", "水 6月  1 12:30:15 JST 2005");
1213 
1214         verifyFormatEquals(t, "%_A", "水曜日");
1215         verifyFormatEquals(t, "%-A", "水曜日");
1216         verifyFormatEquals(t, "%0A", "水曜日");
1217         verifyFormatEquals(t, "%^A", "水曜日");
1218         verifyFormatEquals(t, "%#A", "水曜日");
1219 
1220         verifyFormatEquals(t, "%_Y", "20 5");
1221         verifyFormatEquals(t, "%-Y", "205");
1222         verifyFormatEquals(t, "%0Y", "2005");
1223         verifyFormatEquals(t, "%^Y", "2005");
1224         verifyFormatEquals(t, "%#Y", "2005");
1225 
1226         verifyFormatEquals(t, "%_d", " 1");
1227         verifyFormatEquals(t, "%-d", "1");
1228         verifyFormatEquals(t, "%0d", "01");
1229         verifyFormatEquals(t, "%^d", "01");
1230         verifyFormatEquals(t, "%#d", "01");
1231     }
1232 
1233     @Test
testFormat2445()1234     public void testFormat2445() {
1235         Time t = new Time(Time.TIMEZONE_UTC);
1236         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
1237 
1238         // UTC behavior is to add a trailing Z.
1239         String expected = t.format("%Y%m%dT%H%M%SZ");
1240         assertEquals(expected, t.format2445());
1241 
1242         // Only UTC includes the Z.
1243         t.timezone = "America/Los_Angeles";
1244         expected = t.format("%Y%m%dT%H%M%S");
1245         assertEquals(expected, t.format2445());
1246 
1247         // There is odd behavior around negative values.
1248         Fields.setDateTime(t, 2005, -1, -1, -1, -1, -1);
1249         assertEquals("2005000 T0 0 0 ", t.format2445());
1250     }
1251 
1252     @Test
testFormat2445_doesNotNormalize()1253     public void testFormat2445_doesNotNormalize() {
1254         Time t = new Time(Time.TIMEZONE_UTC);
1255         Fields.set(t, 2005, 13, 32, 25, 61, 61, -2, -2, -2, -2);
1256 
1257         Time tCopy = new Time(t);
1258         Fields.verifyTimeEquals(t, tCopy);
1259 
1260         assertEquals("20051432T256161Z", t.format2445());
1261         Fields.verifyTimeEquals(t, tCopy);
1262 
1263         t.timezone = tCopy.timezone = "America/Los_Angeles";
1264         assertEquals("20051432T256161", t.format2445());
1265         Fields.verifyTimeEquals(t, tCopy);
1266     }
1267 
1268     @Test
testToString()1269     public void testToString() {
1270         Time t = new Time(Time.TIMEZONE_UTC);
1271         assertEquals("19700101T000000UTC(0,0,0,-1,0)", t.toString());
1272 
1273         t.timezone = "America/Los_Angeles";
1274         assertEquals("19700101T000000America/Los_Angeles(0,0,0,-1,28800)", t.toString());
1275     }
1276 
1277     @Test
testToString_doesNotNormalize()1278     public void testToString_doesNotNormalize() {
1279         Time t = new Time(Time.TIMEZONE_UTC);
1280         Fields.set(t, 2005, 13, 32, -1, -1, -1, -2, -2, -2, -2);
1281 
1282         Time tCopy = new Time(t);
1283         Fields.verifyTimeEquals(t, tCopy);
1284 
1285         String r = t.toString();
1286         assertEquals("20051432T-1-1-1UTC(-2,-2,-2,-2,1141426739)", r);
1287 
1288         Fields.verifyTimeEquals(t, tCopy);
1289     }
1290 
1291     @Test
testGetCurrentTimezone()1292     public void testGetCurrentTimezone() {
1293         String r = Time.getCurrentTimezone();
1294         assertEquals(TimeZone.getDefault().getID(), r);
1295     }
1296 
1297     @Test
testSetToNow()1298     public void testSetToNow() {
1299         Time t = new Time(Time.TIMEZONE_UTC);
1300 
1301         Instant lowerBound = Instant.now();
1302 
1303         t.setToNow();
1304 
1305         Instant upperBound = Instant.now();
1306 
1307         // Time.toMillis() only works in seconds so lower/upper/toMillis() are converted to seconds,
1308         // otherwise the greater accuracy from using milliseconds for lowerBound/upperBound can
1309         // cause the test to fail.
1310         long actualSeconds =
1311                 Instant.ofEpochMilli(t.toMillis(true /* ignore isDst */)).getEpochSecond();
1312         assertTrue(lowerBound.getEpochSecond() <= actualSeconds
1313                 && actualSeconds <= upperBound.getEpochSecond());
1314     }
1315 
1316     @Test
testToMillis_utc()1317     public void testToMillis_utc() {
1318         Time t = new Time(Time.TIMEZONE_UTC);
1319 
1320         long winterTimeUtcMillis = 1167613323000L;
1321 
1322         Fields.set(t, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 1, 9, 9);
1323         long r = t.toMillis(true /* ignore isDst */);
1324         assertEquals(winterTimeUtcMillis, r);
1325 
1326         Fields.set(t, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 1, 9, 9);
1327         r = t.toMillis(true /* ignore isDst */);
1328         assertEquals(winterTimeUtcMillis, r);
1329 
1330         Fields.set(t, 2007, 0, 1, 1, 2, 3, -1 /* isDst */, 1, 9, 9);
1331         r = t.toMillis(true /* ignore isDst */);
1332         assertEquals(winterTimeUtcMillis, r);
1333 
1334         Fields.set(t, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 1, 9, 9);
1335         r = t.toMillis(false /* ignore isDst */);
1336         assertEquals(winterTimeUtcMillis, r);
1337 
1338         Fields.set(t, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 1, 9, 9);
1339         r = t.toMillis(false /* ignore isDst */);
1340         assertEquals(-1, r);
1341 
1342         Fields.set(t, 2007, 0, 1, 1, 2, 3, -1 /* isDst */, 1, 9, 9);
1343         r = t.toMillis(false /* ignore isDst */);
1344         assertEquals(winterTimeUtcMillis, r);
1345 
1346         long summerTimeUtcMillis = 1180659723000L;
1347 
1348         Fields.set(t, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 1, 9, 9);
1349         r = t.toMillis(true /* ignore isDst */);
1350         assertEquals(summerTimeUtcMillis, r);
1351 
1352         Fields.set(t, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, 1, 9, 9);
1353         r = t.toMillis(true /* ignore isDst */);
1354         assertEquals(summerTimeUtcMillis, r);
1355 
1356         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, 1, 9, 9);
1357         r = t.toMillis(true /* ignore isDst */);
1358         assertEquals(summerTimeUtcMillis, r);
1359 
1360         Fields.set(t, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 1, 9, 9);
1361         r = t.toMillis(false /* ignore isDst */);
1362         assertEquals(summerTimeUtcMillis, r);
1363 
1364         Fields.set(t, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, 1, 9, 9);
1365         r = t.toMillis(false /* ignore isDst */);
1366         assertEquals(-1, r);
1367 
1368         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, 1, 9, 9);
1369         r = t.toMillis(false /* ignore isDst */);
1370         assertEquals(summerTimeUtcMillis, r);
1371     }
1372 
1373     @Test
testToMillis_dstTz()1374     public void testToMillis_dstTz() {
1375         Time t = new Time(PstPdt.ID);
1376 
1377         // A STD time
1378         long stdTimeUtcMillis = 1167613323000L;
1379         long stdTimeMillis = stdTimeUtcMillis - PstPdt.getUtcOffsetMillis(false);
1380 
1381         Fields.set(t, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
1382         long r = t.toMillis(true /* ignore isDst */);
1383         assertEquals(stdTimeMillis, r);
1384 
1385         Fields.set(t, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
1386         verifyToMillisResult(true, t, stdTimeMillis);
1387 
1388         Fields.set(t, 2007, 0, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
1389         verifyToMillisResult(true, t, stdTimeMillis);
1390 
1391         long dstToStdCorrectionMillis =
1392                 PstPdt.getUtcOffsetMillis(false) - PstPdt.getUtcOffsetMillis(true);
1393 
1394         Fields.set(t, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
1395         verifyToMillisResult(false, t, stdTimeMillis);
1396 
1397         Fields.set(t, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
1398         verifyToMillisResult(false, t, stdTimeMillis + dstToStdCorrectionMillis);
1399 
1400         Fields.set(t, 2007, 0, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
1401         verifyToMillisResult(false, t, stdTimeMillis);
1402 
1403         // A DST time
1404         long dstTimeUtcMillis = 1180659723000L;
1405         long dstTimeMillis = dstTimeUtcMillis - PstPdt.getUtcOffsetMillis(true);
1406 
1407         Fields.set(t, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
1408         verifyToMillisResult(true, t, dstTimeMillis);
1409 
1410         Fields.set(t, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
1411         verifyToMillisResult(true, t, dstTimeMillis);
1412 
1413         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
1414         verifyToMillisResult(true, t, dstTimeMillis);
1415 
1416         long stdToDstCorrectionMillis = -dstToStdCorrectionMillis;
1417 
1418         Fields.set(t, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
1419         verifyToMillisResult(false, t, dstTimeMillis + stdToDstCorrectionMillis);
1420 
1421         Fields.set(t, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
1422         verifyToMillisResult(false, t, dstTimeMillis);
1423 
1424         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
1425         verifyToMillisResult(false, t, dstTimeMillis);
1426     }
1427 
verifyToMillisResult(boolean toMillisArgument, Time t, long expectedResult)1428     private static void verifyToMillisResult(boolean toMillisArgument, Time t,
1429             long expectedResult) {
1430         long r = t.toMillis(toMillisArgument /* ignore isDst */);
1431         assertEquals(expectedResult, r);
1432     }
1433 
1434     @Test
testToMillis_doesNotNormalize()1435     public void testToMillis_doesNotNormalize() {
1436         Time t = new Time(Time.TIMEZONE_UTC);
1437 
1438         Fields.set(t, 2007, 13, 32, 25, 60, 60, -2 /* isDst */, Integer.MAX_VALUE, 367, 7);
1439 
1440         Time originalTime = new Time(t);
1441         Fields.verifyTimeEquals(t, originalTime);
1442 
1443         t.toMillis(true);
1444         Fields.verifyTimeEquals(originalTime, t);
1445 
1446         t.toMillis(false);
1447         Fields.verifyTimeEquals(originalTime, t);
1448     }
1449 
1450     @Test
testToMillis_skippedTime()1451     public void testToMillis_skippedTime() {
1452         // Tests behavior around a transition from STD to DST that introduces an hour of "skipped"
1453         // time from 01:00 to 01:59.
1454         String timezone = PstPdt.ID;
1455         long stdBaseTimeMillis = 1173607200000L;
1456         long dstBaseTimeMillis = 1173603600000L;
1457 
1458         // Try each minute from one minute before the skipped hour until one after.
1459         for (int i = -1; i <= 60; i++) {
1460             int minutesInMillis = (int) Duration.ofMinutes(i).toMillis();
1461             int[] timeFields = new int[] { 2007, 2, 11, 2, i, 0, -999 /* not used */, 9, 9, 9 };
1462 
1463             Time time = new Time(timezone);
1464 
1465             // isDst = 0, toMillis(true)
1466             Fields.set(time, timeFields);
1467             time.isDst = 0;
1468             long expectedTimeMillis;
1469             if (i == -1) {
1470                 expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
1471             } else if (i == 60) {
1472                 expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
1473             } else {
1474                 expectedTimeMillis = -1;
1475             }
1476             verifyToMillisResult(true, time, expectedTimeMillis);
1477 
1478             // isDst = 0, toMillis(false)
1479             Fields.set(time, timeFields);
1480             time.isDst = 0;
1481             expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
1482             verifyToMillisResult(false, time, expectedTimeMillis);
1483 
1484             // isDst = 1, toMillis(true)
1485             Fields.set(time, timeFields);
1486             time.isDst = 1;
1487             if (i == -1) {
1488                 expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
1489             } else if (i == 60) {
1490                 expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
1491             } else {
1492                 expectedTimeMillis = -1;
1493             }
1494             verifyToMillisResult(true, time, expectedTimeMillis);
1495 
1496             // isDst = 1, toMillis(false)
1497             Fields.set(time, timeFields);
1498             time.isDst = 1;
1499             expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
1500             verifyToMillisResult(false, time, expectedTimeMillis);
1501 
1502             // isDst = -1, toMillis(true)
1503             Fields.set(time, timeFields);
1504             time.isDst = -1;
1505 
1506             if (i == -1) {
1507                 expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
1508             } else if (i == 60) {
1509                 expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
1510             } else {
1511                 expectedTimeMillis = -1;
1512             }
1513             verifyToMillisResult(false, time, expectedTimeMillis);
1514 
1515             // isDst = -1, toMillis(false)
1516             Fields.set(time, timeFields);
1517             time.isDst = -1;
1518             verifyToMillisResult(false, time, expectedTimeMillis);
1519         }
1520     }
1521 
1522     @Test
testToMillis_duplicateWallTime()1523     public void testToMillis_duplicateWallTime() {
1524         // 1:00 in standard / 2:00 in DST
1525         long timeBaseMillis = 1194163200000L;
1526         long dstCorrectionMillis = 3600000;
1527 
1528         // Try each minute from one minute before the duplicated hour until one after.
1529         for (int i = -1; i <= 60; i++) {
1530             int minutesInMillis = (int) Duration.ofMinutes(i).toMillis();
1531 
1532             Time time = new Time(PstPdt.ID);
1533             int[] timeFields = new int[] { 2007, 10, 4, 1, i, 0, -999 /* not used */, 9, 9, 9};
1534 
1535             // isDst = 0, toMillis(true)
1536             Fields.set(time, timeFields);
1537             time.isDst = 0;
1538             long timeMillis = time.toMillis(true);
1539             if (i == -1) {
1540                 assertEquals("i = " + i, timeBaseMillis + minutesInMillis, timeMillis);
1541             } else if (i == 60) {
1542                 assertEquals("i = " + i, timeBaseMillis + minutesInMillis + dstCorrectionMillis,
1543                         timeMillis);
1544             } else {
1545                 // When ignoreDst the choice between DST and STD is arbitrary when both are
1546                 // possible.
1547                 assertTrue("i = " + i, timeMillis == timeBaseMillis + minutesInMillis
1548                         || timeMillis == timeBaseMillis + minutesInMillis + dstCorrectionMillis);
1549             }
1550 
1551             // isDst = 0, toMillis(false)
1552             Fields.set(time, timeFields);
1553             time.isDst = 0;
1554             verifyToMillisResult(false, time,
1555                     timeBaseMillis + minutesInMillis + dstCorrectionMillis);
1556 
1557             // isDst = 1, toMillis(true)
1558             Fields.set(time, timeFields);
1559             time.isDst = 1;
1560             if (i == -1) {
1561                 assertEquals("i = " + i, timeBaseMillis + minutesInMillis, timeMillis);
1562             } else if (i == 60) {
1563                 assertEquals("i = " + i, timeBaseMillis + minutesInMillis + dstCorrectionMillis,
1564                         timeMillis);
1565             } else {
1566                 // When ignoreDst or isDst == -1 the choice between DST and STD is arbitrary when
1567                 // both are possible.
1568                 assertTrue("i = " + i, timeMillis == timeBaseMillis + minutesInMillis
1569                         || timeMillis == timeBaseMillis + minutesInMillis + dstCorrectionMillis);
1570             }
1571 
1572             // isDst = 1, toMillis(false)
1573             Fields.set(time, timeFields);
1574             time.isDst = 1;
1575             verifyToMillisResult(false, time, timeBaseMillis + minutesInMillis);
1576 
1577             // isDst = -1, toMillis(true)
1578             Fields.set(time, timeFields);
1579             time.isDst = -1;
1580             timeMillis = time.toMillis(true);
1581             if (i == -1) {
1582                 assertEquals("i = " + i, timeBaseMillis + minutesInMillis, timeMillis);
1583             } else if (i == 60) {
1584                 assertEquals("i = " + i, timeBaseMillis + minutesInMillis + dstCorrectionMillis,
1585                         timeMillis);
1586             } else {
1587                 // When ignoreDst or isDst == -1 the choice between DST and STD is arbitrary when
1588                 // both are possible.
1589                 assertTrue("i = " + i, timeMillis == timeBaseMillis + minutesInMillis
1590                         || timeMillis == timeBaseMillis + minutesInMillis + dstCorrectionMillis);
1591             }
1592 
1593             // isDst = -1, toMillis(false)
1594             Fields.set(time, timeFields);
1595             time.isDst = -1;
1596             timeMillis = time.toMillis(false);
1597             if (i == -1) {
1598                 assertEquals("i = " + i, timeBaseMillis + minutesInMillis, timeMillis);
1599             } else if (i == 60) {
1600                 assertEquals("i = " + i, timeBaseMillis + minutesInMillis + dstCorrectionMillis,
1601                         timeMillis);
1602             } else {
1603                 // When ignoreDst or isDst == -1 the choice between DST and STD is arbitrary when
1604                 // both are possible.
1605                 assertTrue("i = " + i, timeMillis == timeBaseMillis + minutesInMillis
1606                         || timeMillis == timeBaseMillis + minutesInMillis + dstCorrectionMillis);
1607             }
1608         }
1609     }
1610 
1611     @Test
testToMillis_before32BitSeconds()1612     public void testToMillis_before32BitSeconds() {
1613         int[] timeFields = new int[] { 1900, 0, 1, 2, 3, 4, -999 /* not used */, 9, 9, 9 };
1614         verifyToMillisInvalid(timeFields, PstPdt.ID);
1615         verifyToMillisInvalid(timeFields, Time.TIMEZONE_UTC);
1616     }
1617 
verifyToMillisInvalid(int[] timeFields, String timezone)1618     private static void verifyToMillisInvalid(int[] timeFields, String timezone) {
1619         Time time = new Time(timezone);
1620 
1621         // isDst = 0, toMillis(true)
1622         Fields.set(time, timeFields);
1623         time.isDst = 0;
1624         verifyToMillisResult(true, time, -1);
1625 
1626         // isDst = 0, toMillis(false)
1627         Fields.set(time, timeFields);
1628         time.isDst = 0;
1629         verifyToMillisResult(false, time, -1);
1630 
1631         // isDst = 1, toMillis(true)
1632         Fields.set(time, timeFields);
1633         time.isDst = 1;
1634         verifyToMillisResult(true, time, -1);
1635 
1636         // isDst = 1, toMillis(false)
1637         Fields.set(time, timeFields);
1638         time.isDst = 1;
1639         verifyToMillisResult(false, time, -1);
1640 
1641         // isDst = -1, toMillis(true)
1642         Fields.set(time, timeFields);
1643         time.isDst = -1;
1644         verifyToMillisResult(true, time, -1);
1645 
1646         // isDst = -1, toMillis(false)
1647         Fields.set(time, timeFields);
1648         time.isDst = -1;
1649         verifyToMillisResult(false, time, -1);
1650     }
1651 
1652     @Test
testToMillis_after32BitSeconds()1653     public void testToMillis_after32BitSeconds() {
1654         int[] timeFields = new int[] { 2039, 0, 1, 2, 3, 4, -999 /* not used */, 9, 9, 9 };
1655         verifyToMillisInvalid(timeFields, PstPdt.ID);
1656         verifyToMillisInvalid(timeFields, Time.TIMEZONE_UTC);
1657     }
1658 
1659     @Test
testToMillis_invalid()1660     public void testToMillis_invalid() {
1661         int[] timeFields = new int[] { 0, 0, 0, 0, 0, 0, -999 /* not used */, 9, 9, 9 };
1662         verifyToMillisInvalid(timeFields, PstPdt.ID);
1663         verifyToMillisInvalid(timeFields, Time.TIMEZONE_UTC);
1664     }
1665 
1666     @Test
testParse_date()1667     public void testParse_date() {
1668         String nonUtcTz = PstPdt.ID;
1669         Time t = new Time(nonUtcTz);
1670         assertFalse(t.parse("12345678"));
1671         Time expected = new Time(nonUtcTz);
1672         Fields.setAllDayDate(expected, 1234, 55, 78);
1673         Fields.setDst(expected, -1 /* isDst */, 0);
1674         Fields.setDerivedDateTime(expected, 0, 0);
1675         Fields.verifyTimeEquals(expected, t);
1676     }
1677 
1678     @Test(expected=NullPointerException.class)
testParse_null()1679     public void testParse_null() {
1680         Time t = new Time(Time.TIMEZONE_UTC);
1681         t.parse(null);
1682     }
1683 
1684     @Test
testParse()1685     public void testParse() {
1686         Time t = new Time(Time.TIMEZONE_UTC);
1687         t.parse("20061005T120000");
1688 
1689         Time expected = new Time(Time.TIMEZONE_UTC);
1690         Fields.set(expected, 2006, 9, 5, 12, 0, 0, -1 /* isDst */, 0, 0, 0);
1691         Fields.verifyTimeEquals(expected, t);
1692     }
1693 
1694     @Test
testParse_dateTime()1695     public void testParse_dateTime() {
1696         String nonUtcTz = PstPdt.ID;
1697         Time t = new Time(nonUtcTz);
1698         assertFalse(t.parse("12345678T901234"));
1699         Time expected = new Time(nonUtcTz);
1700         Fields.set(expected, 1234, 55, 78, 90, 12, 34, -1 /* isDst */, 0, 0, 0);
1701         Fields.verifyTimeEquals(expected, t);
1702 
1703         Time t2 = new Time(nonUtcTz);
1704         assertTrue(t2.parse("12345678T901234Z"));
1705         Time utcExpected = new Time(Time.TIMEZONE_UTC);
1706         Fields.set(utcExpected, 1234, 55, 78, 90, 12, 34, -1 /* isDst */, 0, 0, 0);
1707         Fields.verifyTimeEquals(utcExpected, t2);
1708     }
1709 
1710     @Test(expected=NullPointerException.class)
testParse_pstPdtNull()1711     public void testParse_pstPdtNull() {
1712         Time t = new Time(PstPdt.ID);
1713         t.parse(null);
1714     }
1715 
1716     @Test
testParse_errors()1717     public void testParse_errors() {
1718         // Too short
1719         verifyParseError("");
1720         verifyParseError("1");
1721         verifyParseError("12");
1722         verifyParseError("123");
1723         verifyParseError("1234");
1724         verifyParseError("12345");
1725         verifyParseError("123456");
1726         verifyParseError("1234567");
1727 
1728         // No "T" in the expected place
1729         verifyParseError("12345678S");
1730 
1731         // Invalid character in the first 8 characters.
1732         verifyParseError("12X45678");
1733 
1734         // Too short for a date/time (15 or 16 characters allowed)
1735         verifyParseError("12345678T");
1736         verifyParseError("12345678T0");
1737         verifyParseError("12345678T01");
1738         verifyParseError("12345678T012");
1739         verifyParseError("12345678T0123");
1740         verifyParseError("12345678T01234");
1741 
1742         // Invalid character
1743         verifyParseError("12345678T0X2345");
1744     }
1745 
verifyParseError(String s)1746     private static void verifyParseError(String s) {
1747         Time t = new Time(Time.TIMEZONE_UTC);
1748         try {
1749             t.parse(s);
1750             fail();
1751         } catch (TimeFormatException expected) {
1752         }
1753     }
1754 
1755     @Test
testParse3339()1756     public void testParse3339() {
1757         String tz = Time.TIMEZONE_UTC;
1758         Time expected = new Time(tz);
1759         Fields.setAllDayDate(expected, 1980, 4, 23);
1760         Fields.setDst(expected, -1 /* isDst */, 0);
1761         Fields.setDerivedDateTime(expected, 0, 0);
1762         verifyParse3339Succeeds(tz, "1980-05-23", expected);
1763 
1764         Fields.setDateTime(expected, 1980, 4, 23, 9, 50, 50);
1765         Fields.setDst(expected, -1 /* isDst */, 0);
1766         Fields.setDerivedDateTime(expected, 0, 0);
1767         verifyParse3339Succeeds(tz, "1980-05-23T09:50:50", expected);
1768 
1769         Fields.setDateTime(expected, 1980, 4, 23, 9, 50, 50);
1770         Fields.setDst(expected, -1 /* isDst */, 0);
1771         Fields.setDerivedDateTime(expected, 0, 0);
1772         verifyParse3339Succeeds(tz, "1980-05-23T09:50:50Z", expected);
1773 
1774         Fields.setDateTime(expected, 1980, 4, 23, 9, 50, 50);
1775         Fields.setDst(expected, -1 /* isDst */, 0);
1776         Fields.setDerivedDateTime(expected, 0, 0);
1777         verifyParse3339Succeeds(tz, "1980-05-23T09:50:50.0Z", expected);
1778 
1779         Fields.setDateTime(expected, 1980, 4, 23, 9, 50, 50);
1780         Fields.setDst(expected, -1 /* isDst */, 0);
1781         Fields.setDerivedDateTime(expected, 0, 0);
1782         verifyParse3339Succeeds(tz, "1980-05-23T09:50:50.12Z", expected);
1783 
1784         Fields.setDateTime(expected, 1980, 4, 23, 9, 50, 50);
1785         Fields.setDst(expected, -1 /* isDst */, 0);
1786         Fields.setDerivedDateTime(expected, 0, 0);
1787         verifyParse3339Succeeds(tz, "1980-05-23T09:50:50.123Z", expected);
1788 
1789         // The time should be normalized to UTC
1790         Fields.setDateTime(expected, 1980, 4, 23, 10, 55, 50);
1791         Fields.setDst(expected, -1 /* isDst */, 0);
1792         Fields.setDerivedDateTime(expected, 0, 0);
1793         verifyParse3339Succeeds(tz, "1980-05-23T09:50:50-01:05", expected);
1794 
1795         // The time should be normalized to UTC
1796         Fields.setDateTime(expected, 1980, 4, 23, 10, 55, 50);
1797         Fields.setDst(expected, -1 /* isDst */, 0);
1798         Fields.setDerivedDateTime(expected, 0, 0);
1799         verifyParse3339Succeeds(tz, "1980-05-23T09:50:50.123-01:05", expected);
1800     }
1801 
verifyParse3339Succeeds(String timeZone, String toParse, Time expected)1802     private static void verifyParse3339Succeeds(String timeZone, String toParse, Time expected) {
1803         Time t = new Time(timeZone);
1804         t.parse3339(toParse);
1805         Fields.verifyTimeEquals(expected, t);
1806     }
1807 
1808     @Test
testParse3339_parseErrors()1809     public void testParse3339_parseErrors() {
1810         // Too short
1811         verifyParse3339Error("1980");
1812 
1813         // Timezone too short
1814         verifyParse3339Error("1980-05-23T09:50:50.123+");
1815         verifyParse3339Error("1980-05-23T09:50:50.123+05:0");
1816     }
1817 
1818     @Test(expected=NullPointerException.class)
testParse3339_null()1819     public void testParse3339_null() {
1820         Time t = new Time(Time.TIMEZONE_UTC);
1821         t.parse3339(null);
1822     }
1823 
verifyParse3339Error(String s)1824     private void verifyParse3339Error(String s) {
1825         String tz = Time.TIMEZONE_UTC;
1826         Time t = new Time(tz);
1827         try {
1828             t.parse3339(s);
1829             fail();
1830         } catch (TimeFormatException expected) {
1831         }
1832     }
1833 
1834     @Test
testSetMillis_utc()1835     public void testSetMillis_utc() {
1836         Instant baseTime = Instant.ofEpochSecond(0);
1837         String tz = Time.TIMEZONE_UTC;
1838         Time t = new Time(tz);
1839         t.set(baseTime.plus(1, ChronoUnit.SECONDS).toEpochMilli());
1840 
1841         Time expected = new Time(tz);
1842         Fields.set(expected, 1970, 0, 1, 0, 0, 1, 0 /* isDst */, 0, 0, 4);
1843         Fields.verifyTimeEquals(expected, t);
1844 
1845         t.set(baseTime.plus(2, ChronoUnit.SECONDS).toEpochMilli());
1846         Fields.set(expected, 1970, 0, 1, 0, 0, 2, 0 /* isDst */, 0, 0, 4);
1847         Fields.verifyTimeEquals(expected, t);
1848 
1849         t.set(baseTime.plus(1, ChronoUnit.MINUTES).toEpochMilli());
1850         Fields.set(expected, 1970, 0, 1, 0, 1, 0, 0 /* isDst */, 0, 0, 4);
1851         Fields.verifyTimeEquals(expected, t);
1852 
1853         t.set(baseTime.plus(1, ChronoUnit.DAYS).plus(1, ChronoUnit.SECONDS).toEpochMilli());
1854         Fields.set(expected, 1970, 0, 2, 0, 0, 1, 0 /* isDst */, 0, 1, 5);
1855         Fields.verifyTimeEquals(expected, t);
1856     }
1857 
1858     @Test
testSetMillis_utc_edgeCases()1859     public void testSetMillis_utc_edgeCases() {
1860         String tz = Time.TIMEZONE_UTC;
1861         Time t = new Time(tz);
1862         t.set(Integer.MAX_VALUE + 1L);
1863 
1864         Time expected = new Time(tz);
1865         // This a 32-bit int overflow bug.
1866         Fields.set(expected, 1970, 0, 25, 20, 31, 23, 0 /* isDst */, 0, 24, 0);
1867         Fields.verifyTimeEquals(expected, t);
1868 
1869         t = new Time(tz);
1870         t.set(Integer.MIN_VALUE - 1L);
1871         // This a 32-bit int underflow bug.
1872         Fields.set(expected, 1969, 11, 7, 3, 28, 37, 0 /* isDst */, 0, 340, 0);
1873         Fields.verifyTimeEquals(expected, t);
1874     }
1875 
1876     @Test
testSetFields()1877     public void testSetFields() {
1878         String tz = Time.TIMEZONE_UTC;
1879         Time t = new Time(tz);
1880         Fields.set(t, 9, 9, 9, 9, 9, 9, 9 /* isDst */, 9, 9, 9);
1881 
1882         t.set(1, 2, 3, 4, 5, 6);
1883 
1884         Time expected = new Time(tz);
1885         Fields.set(expected, 6, 5, 4, 3, 2, 1, -1 /* isDst */, 0, 0, 0);
1886         Fields.verifyTimeEquals(expected, t);
1887     }
1888 
1889     // Timezones that cover the world.  Some GMT offsets occur more than
1890     // once in case some cities decide to change their GMT offset.
1891     private static final String[] mTimeZones = {
1892         "UTC",
1893         "Pacific/Kiritimati",
1894         "Pacific/Enderbury",
1895         "Pacific/Fiji",
1896         "Antarctica/South_Pole",
1897         "Pacific/Norfolk",
1898         "Pacific/Ponape",
1899         "Asia/Magadan",
1900         "Australia/Lord_Howe",
1901         "Australia/Sydney",
1902         "Australia/Adelaide",
1903         "Asia/Tokyo",
1904         "Asia/Seoul",
1905         "Asia/Taipei",
1906         "Asia/Singapore",
1907         "Asia/Hong_Kong",
1908         "Asia/Saigon",
1909         "Asia/Bangkok",
1910         "Indian/Cocos",
1911         "Asia/Rangoon",
1912         "Asia/Omsk",
1913         "Antarctica/Mawson",
1914         "Asia/Colombo",
1915         "Asia/Calcutta",
1916         "Asia/Oral",
1917         "Asia/Kabul",
1918         "Asia/Dubai",
1919         "Asia/Tehran",
1920         "Europe/Moscow",
1921         "Asia/Baghdad",
1922         "Africa/Mogadishu",
1923         "Europe/Athens",
1924         "Africa/Cairo",
1925         "Europe/Rome",
1926         "Europe/Berlin",
1927         "Europe/Amsterdam",
1928         "Africa/Tunis",
1929         "Europe/London",
1930         "Europe/Dublin",
1931         "Atlantic/St_Helena",
1932         "Africa/Monrovia",
1933         "Africa/Accra",
1934         "Atlantic/Azores",
1935         "Atlantic/South_Georgia",
1936         "America/Noronha",
1937         "America/Sao_Paulo",
1938         "America/Cayenne",
1939         "America/St_Johns",
1940         "America/Puerto_Rico",
1941         "America/Aruba",
1942         "America/New_York",
1943         "America/Chicago",
1944         "America/Denver",
1945         "America/Los_Angeles",
1946         "America/Anchorage",
1947         "Pacific/Marquesas",
1948         "America/Adak",
1949         "Pacific/Honolulu",
1950         "Pacific/Midway",
1951     };
1952 
1953     /**
1954      * This test uses java.time classes to construct test times so that it can test various years
1955      * including those outside of the int32 seconds range.
1956      */
1957     @Test
testGetJulianDay()1958     public void testGetJulianDay() {
1959         int[] years = { 2008, 1900, 1969, 2100 };
1960         for (int year : years) {
1961             for (String timeZone : mTimeZones) {
1962                 checkGetJulianDayForYearAndTimeZone(year, timeZone);
1963             }
1964         }
1965     }
1966 
checkGetJulianDayForYearAndTimeZone(int year, String timeZone)1967     private static void checkGetJulianDayForYearAndTimeZone(int year, String timeZone) {
1968         final LocalTime midday = LocalTime.of(12, 0);
1969 
1970         // For every 15th day of the year get the Julian day for 12pm and then check that if we
1971         // change the time we get the same Julian day.
1972         final LocalDate startDate = LocalDate.of(year, Month.JANUARY, 1);
1973         final LocalDate stopDate = startDate.plusYears(1);
1974         for (LocalDate testDate = startDate; testDate.isBefore(stopDate);
1975                 testDate = testDate.plusDays(15)) {
1976 
1977             LocalDateTime middayLocalDateTime = LocalDateTime.of(testDate, midday);
1978             ZoneOffset middayOffset = ZoneId.of(timeZone).getRules().getOffset(middayLocalDateTime);
1979             Instant middayInstant = middayLocalDateTime.toInstant(middayOffset);
1980 
1981             // Record the Julian day for the date/time given. Since we want to know the local
1982             // calendar date we have to provide the time zone's UTC offset too.
1983             int middayJulianDay =
1984                     Time.getJulianDay(middayInstant.toEpochMilli(), middayOffset.getTotalSeconds());
1985 
1986             // Check Time.getJulianDay() agrees with java.time's Julian day calculations.
1987             assertEquals(middayJulianDay, JulianFields.JULIAN_DAY.getFrom(middayLocalDateTime));
1988 
1989             checkGetJulianDayVariousTimes(timeZone, testDate);
1990         }
1991     }
1992 
checkGetJulianDayVariousTimes(String timeZone, LocalDate testDate)1993     private static void checkGetJulianDayVariousTimes(String timeZone, LocalDate testDate) {
1994         long expectedJulianDay = JulianFields.JULIAN_DAY.getFrom(testDate);
1995 
1996         // Change the time during the day and check that we get the same Julian day as the one
1997         // for midday.
1998         for (int hour = 0; hour < 24; hour++) {
1999             for (int minute = 0; minute < 60; minute += 15) {
2000                 LocalTime localTime = LocalTime.of(hour, minute);
2001                 LocalDateTime localDateTime = LocalDateTime.of(testDate, localTime);
2002                 ZoneOffset localDateTimeOffset =
2003                         ZoneId.of(timeZone).getRules().getOffset(localDateTime);
2004                 Instant instant = localDateTime.toInstant(localDateTimeOffset);
2005                 long millis = instant.toEpochMilli();
2006 
2007                 // Find the Julian day for the date/time by supplying the offset. Since we want
2008                 // to know the local calendar date we have to provide the UTC offset too.
2009                 int julianDay = Time.getJulianDay(millis, localDateTimeOffset.getTotalSeconds());
2010 
2011                 assertEquals("Julian day: " + julianDay + " at time "
2012                                 + hour + ":" + minute
2013                                 + " != today's Julian day: " + expectedJulianDay
2014                                 + " millis: " + millis
2015                                 + " localDatetime: " + localDateTime
2016                                 + " timeZone: " + timeZone,
2017                         julianDay, expectedJulianDay);
2018             }
2019         }
2020     }
2021 
2022     @Test
testSetJulianDay()2023     public void testSetJulianDay() {
2024         Time time = new Time();
2025 
2026         // For each 20th day of the year in 2008, and for each timezone,
2027         // test that we can set the Julian day correctly.
2028         for (int monthDay = 1; monthDay <= 366; monthDay += 20) {
2029             for (int zoneIndex = 0; zoneIndex < mTimeZones.length; zoneIndex++) {
2030                 // We leave the "month" as zero because we are changing the
2031                 // "monthDay" from 1 to 366. The call to normalize() will
2032                 // then change the "month" (but we don't really care).
2033                 time.set(0, 0, 12, monthDay, 0, 2008);
2034                 time.timezone = mTimeZones[zoneIndex];
2035                 long millis = time.normalize(true);
2036                 if (zoneIndex == 0) {
2037                     Log.i(TAG, time.format("%B %d, %Y"));
2038                 }
2039                 // This is the Julian day for 12pm for this day of the year
2040                 int julianDay = Time.getJulianDay(millis, time.gmtoff);
2041 
2042                 time.setJulianDay(julianDay);
2043 
2044                 // Some places change daylight saving time at 12am and so there
2045                 // is no 12am on some days in some timezones. In those cases,
2046                 // the time is set to 1am.
2047                 // Examples: Africa/Cairo on April 25, 2008
2048                 // America/Sao_Paulo on October 12, 2008
2049                 // Atlantic/Azores on March 30, 2008
2050                 assertTrue(time.hour == 0 || time.hour == 1);
2051                 assertEquals(0, time.minute);
2052                 assertEquals(0, time.second);
2053 
2054                 millis = time.toMillis(false);
2055                 if (millis == -1) {
2056                     // millis == -1 means the wall time does not exist in the chosen
2057                     // timezone due to a DST change. We cannot calculate a JulianDay for -1.
2058                     continue;
2059                 }
2060                 int day = Time.getJulianDay(millis, time.gmtoff);
2061                 assertEquals("Error: gmtoff " + time.gmtoff + " day " + julianDay
2062                                 + " millis " + millis+ " " + time.format("%B %d, %Y")
2063                                 + " " + time.timezone,
2064                         day, julianDay);
2065             }
2066         }
2067     }
2068 
2069     @Test
testGetJulianMondayFromWeeksSinceEpoch()2070     public void testGetJulianMondayFromWeeksSinceEpoch() {
2071         final int mondayBeforeEpoch = Time.MONDAY_BEFORE_JULIAN_EPOCH;
2072         assertEquals(mondayBeforeEpoch, Time.getJulianMondayFromWeeksSinceEpoch(0));
2073         assertEquals(mondayBeforeEpoch + 7, Time.getJulianMondayFromWeeksSinceEpoch(1));
2074         assertEquals(mondayBeforeEpoch + 14, Time.getJulianMondayFromWeeksSinceEpoch(2));
2075         assertEquals(mondayBeforeEpoch - 7, Time.getJulianMondayFromWeeksSinceEpoch(-1));
2076     }
2077 
2078     @Test
testGetWeeksSinceEpochFromJulianDay()2079     public void testGetWeeksSinceEpochFromJulianDay() {
2080         final int epoch = Time.EPOCH_JULIAN_DAY;  // a Thursday
2081         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epoch, Time.SUNDAY));
2082         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epoch, Time.MONDAY));
2083         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epoch, Time.TUESDAY));
2084         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epoch, Time.WEDNESDAY));
2085         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epoch, Time.THURSDAY));
2086         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epoch, Time.FRIDAY));
2087         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epoch, Time.SATURDAY));
2088 
2089         final int epochFriday = epoch + 1;
2090         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochFriday, Time.SUNDAY));
2091         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochFriday, Time.MONDAY));
2092         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochFriday, Time.TUESDAY));
2093         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochFriday, Time.WEDNESDAY));
2094         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochFriday, Time.THURSDAY));
2095         assertEquals(1, Time.getWeeksSinceEpochFromJulianDay(epochFriday, Time.FRIDAY));
2096         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochFriday, Time.SATURDAY));
2097 
2098         final int epochSaturday = epoch + 2;
2099         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochSaturday, Time.SUNDAY));
2100         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochSaturday, Time.MONDAY));
2101         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochSaturday, Time.TUESDAY));
2102         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochSaturday, Time.WEDNESDAY));
2103         assertEquals(0, Time.getWeeksSinceEpochFromJulianDay(epochSaturday, Time.THURSDAY));
2104         assertEquals(1, Time.getWeeksSinceEpochFromJulianDay(epochSaturday, Time.FRIDAY));
2105         assertEquals(1, Time.getWeeksSinceEpochFromJulianDay(epochSaturday, Time.SATURDAY));
2106 
2107         final int tenWeeksLater = epochSaturday + 10 * 7;
2108         assertEquals(10, Time.getWeeksSinceEpochFromJulianDay(tenWeeksLater, Time.SUNDAY));
2109         assertEquals(10, Time.getWeeksSinceEpochFromJulianDay(tenWeeksLater, Time.MONDAY));
2110         assertEquals(10, Time.getWeeksSinceEpochFromJulianDay(tenWeeksLater, Time.TUESDAY));
2111         assertEquals(10, Time.getWeeksSinceEpochFromJulianDay(tenWeeksLater, Time.WEDNESDAY));
2112         assertEquals(10, Time.getWeeksSinceEpochFromJulianDay(tenWeeksLater, Time.THURSDAY));
2113         assertEquals(11, Time.getWeeksSinceEpochFromJulianDay(tenWeeksLater, Time.FRIDAY));
2114         assertEquals(11, Time.getWeeksSinceEpochFromJulianDay(tenWeeksLater, Time.SATURDAY));
2115     }
2116 
2117     @Test
testNormalize_utc()2118     public void testNormalize_utc() {
2119         Time t = new Time(Time.TIMEZONE_UTC);
2120         Time expected = new Time(Time.TIMEZONE_UTC);
2121 
2122         long winterTimeUtcMillis = 1167613323000L;
2123 
2124         Fields.set(t, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
2125         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 0, 0, 1);
2126         verifyNormalizeResult(true, t, expected, winterTimeUtcMillis);
2127 
2128         Fields.set(t, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2129         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 0, 0, 1);
2130         verifyNormalizeResult(true, t, expected, winterTimeUtcMillis);
2131 
2132         Fields.set(t, 2007, 0, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
2133         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 0, 0, 1);
2134         verifyNormalizeResult(true, t, expected, winterTimeUtcMillis);
2135 
2136         Fields.set(t, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
2137         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 0, 0, 1);
2138         verifyNormalizeResult(false, t, expected, winterTimeUtcMillis);
2139 
2140         Fields.set(t, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2141         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2142         verifyNormalizeResult(false, t, expected, -1);
2143 
2144         Fields.set(t, 2007, 0, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
2145         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 0, 0, 1);
2146         verifyNormalizeResult(false, t, expected, winterTimeUtcMillis);
2147 
2148         long summerTimeUtcMillis = 1180659723000L;
2149 
2150         Fields.set(t, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
2151         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 0, 151, 5);
2152         verifyNormalizeResult(true, t, expected, summerTimeUtcMillis);
2153 
2154         Fields.set(t, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2155         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 0, 151, 5);
2156         verifyNormalizeResult(true, t, expected, summerTimeUtcMillis);
2157 
2158         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
2159         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 0, 151, 5);
2160         verifyNormalizeResult(true, t, expected, summerTimeUtcMillis);
2161 
2162         Fields.set(t, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
2163         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 0, 151, 5);
2164         verifyNormalizeResult(false, t, expected, summerTimeUtcMillis);
2165 
2166         Fields.set(t, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2167         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2168         verifyNormalizeResult(false, t, expected, -1);
2169 
2170         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, 1, 9, 9);
2171         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 0, 151, 5);
2172         verifyNormalizeResult(false, t, expected, summerTimeUtcMillis);
2173     }
2174 
2175     @Test
testNormalize_dstTz()2176     public void testNormalize_dstTz() {
2177         Time t = new Time(PstPdt.ID);
2178         Time expected = new Time(PstPdt.ID);
2179 
2180         // A STD time
2181         long stdTimeUtcMillis = 1167613323000L;
2182         long stdTimeMillis = stdTimeUtcMillis - PstPdt.getUtcOffsetMillis(false);
2183 
2184         Fields.set(t, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
2185         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, -28800, 0, 1);
2186         verifyNormalizeResult(true, t, expected, stdTimeMillis);
2187 
2188         Fields.set(t, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2189         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, -28800, 0, 1);
2190         verifyNormalizeResult(true, t, expected, stdTimeMillis);
2191 
2192         Fields.set(t, 2007, 0, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
2193         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, -28800, 0, 1);
2194         verifyNormalizeResult(true, t, expected, stdTimeMillis);
2195 
2196         long dstToStdCorrectionMillis =
2197                 PstPdt.getUtcOffsetMillis(false) - PstPdt.getUtcOffsetMillis(true);
2198 
2199         Fields.set(t, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
2200         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, -28800, 0, 1);
2201         verifyNormalizeResult(false, t, expected, stdTimeMillis);
2202 
2203         Fields.set(t, 2007, 0, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2204         Fields.set(expected, 2007, 0, 1, 0, 2, 3, 0 /* isDst */, -28800, 0, 1);
2205         verifyNormalizeResult(false, t, expected, stdTimeMillis + dstToStdCorrectionMillis);
2206 
2207         Fields.set(t, 2007, 0, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
2208         Fields.set(expected, 2007, 0, 1, 1, 2, 3, 0 /* isDst */, -28800, 0, 1);
2209         verifyNormalizeResult(false, t, expected, stdTimeMillis);
2210 
2211         // A DST time
2212         long dstTimeUtcMillis = 1180659723000L;
2213         long dstTimeMillis = dstTimeUtcMillis - PstPdt.getUtcOffsetMillis(true);
2214 
2215         Fields.set(t, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
2216         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, -25200, 151, 5);
2217         verifyNormalizeResult(true, t, expected, dstTimeMillis);
2218 
2219         Fields.set(t, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, 9, 9, 9);
2220         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, -25200, 151, 5);
2221         verifyNormalizeResult(true, t, expected, dstTimeMillis);
2222 
2223         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
2224         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, -25200, 151, 5);
2225         verifyNormalizeResult(true, t, expected, dstTimeMillis);
2226 
2227         long stdToDstCorrectionMillis = -dstToStdCorrectionMillis;
2228 
2229         Fields.set(t, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 9, 9, 9);
2230         Fields.set(expected, 2007, 5, 1, 2, 2, 3, 1 /* isDst */, -25200, 151, 5);
2231         verifyNormalizeResult(false, t, expected, dstTimeMillis + stdToDstCorrectionMillis);
2232 
2233         Fields.set(t, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, -25200, 151, 5);
2234         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, -25200, 151, 5);
2235         verifyNormalizeResult(false, t, expected, dstTimeMillis);
2236 
2237         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, -25200, 151, 5);
2238         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 1 /* isDst */, -25200, 151, 5);
2239         verifyNormalizeResult(false, t, expected, dstTimeMillis);
2240     }
2241 
2242     @Test
testNormalize_skippedTime()2243     public void testNormalize_skippedTime() {
2244         // Tests behavior around a transition from STD to DST that introduces an hour of "skipped"
2245         // time from 01:00 to 01:59.
2246         String timezone = PstPdt.ID;
2247         long stdBaseTimeMillis = 1173607200000L;
2248         long dstBaseTimeMillis = 1173603600000L;
2249 
2250         // Try each minute from one minute before the skipped hour until one after.
2251         for (int i = -1; i <= 60; i++) {
2252             int minutesInMillis = (int) Duration.ofMinutes(i).toMillis();
2253             int[] dateTimeArgs = new int[] { 2007, 2, 11, 2, i, 0 };
2254 
2255             int[] normalizedAdjustedBackwardDateTimeArgs;
2256             int[] normalizedDateTimeArgs;
2257             int[] normalizedAdjustedForwardDateTimeArgs;
2258             if (i == -1) {
2259                 normalizedAdjustedBackwardDateTimeArgs = new int[] { 2007, 2, 11, 0, 59, 0 };
2260                 normalizedDateTimeArgs = new int[] { 2007, 2, 11, 1, 59, 0 };
2261                 normalizedAdjustedForwardDateTimeArgs = null;
2262             } else if (i == 60) {
2263                 normalizedAdjustedBackwardDateTimeArgs = null;
2264                 normalizedDateTimeArgs = new int[] { 2007, 2, 11, 3, 0, 0 };
2265                 normalizedAdjustedForwardDateTimeArgs = new int[] { 2007, 2, 11, 4, 0, 0 };
2266             } else {
2267                 normalizedAdjustedBackwardDateTimeArgs = new int[] { 2007, 2, 11, 1, i, 0 };
2268                 normalizedDateTimeArgs = null;
2269                 normalizedAdjustedForwardDateTimeArgs =  new int[] { 2007, 2, 11, 3, i, 0 };
2270             }
2271 
2272             Time time = new Time(timezone);
2273             Time expected = new Time(timezone);
2274 
2275             // isDst = 0, normalize(true)
2276             Fields.setDateTime(time, dateTimeArgs);
2277             Fields.setDst(time, 0 /* isDst */, 9);
2278             Fields.setDerivedDateTime(time, 9, 9);
2279 
2280             long timeMillis = time.normalize(true);
2281             long expectedTimeMillis;
2282             if (i == -1) {
2283                 expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
2284                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2285                 Fields.setDst(expected, 0, PstPdt.getUtcOffsetSeconds(0));
2286                 Fields.setDerivedDateTime(expected, 69, 0);
2287             } else if (i == 60) {
2288                 expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
2289                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2290                 Fields.setDst(expected, 1, PstPdt.getUtcOffsetSeconds(1));
2291                 Fields.setDerivedDateTime(expected, 69, 0);
2292             } else {
2293                 expectedTimeMillis = -1;
2294                 Fields.setDateTime(expected, dateTimeArgs);
2295                 Fields.setDst(expected, -1, 9);
2296                 Fields.setDerivedDateTime(expected, 9, 9);
2297             }
2298             assertEquals("i = " + i, expectedTimeMillis, timeMillis);
2299             Fields.verifyTimeEquals("i = " + i, expected, time);
2300 
2301             // isDst = 0, normalize(false)
2302             Fields.setDateTime(time, dateTimeArgs);
2303             Fields.setDst(time, 0 /* isDst */, 9);
2304             Fields.setDerivedDateTime(time, 9, 9);
2305 
2306             timeMillis = time.normalize(false);
2307             expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
2308             assertEquals("i = " + i, expectedTimeMillis, timeMillis);
2309             if (i == -1) {
2310                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2311                 Fields.setDst(expected, 0, PstPdt.getUtcOffsetSeconds(0));
2312             } else {
2313                 Fields.setDateTime(expected, normalizedAdjustedForwardDateTimeArgs);
2314                 Fields.setDst(expected, 1, PstPdt.getUtcOffsetSeconds(1));
2315             }
2316             Fields.setDerivedDateTime(expected, 69, 0);
2317             Fields.verifyTimeEquals("i = " + i, expected, time);
2318 
2319             // isDst = 1, normalize(true)
2320             Fields.setDateTime(time, dateTimeArgs);
2321             Fields.setDst(time, 1 /* isDst */, 9);
2322             Fields.setDerivedDateTime(time, 9, 9);
2323 
2324             timeMillis = time.normalize(true);
2325             if (i == -1) {
2326                 expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
2327                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2328                 Fields.setDst(expected, 0, PstPdt.getUtcOffsetSeconds(0));
2329                 Fields.setDerivedDateTime(expected, 69, 0);
2330             } else if (i == 60) {
2331                 expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
2332                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2333                 Fields.setDst(expected, 1, PstPdt.getUtcOffsetSeconds(1));
2334                 Fields.setDerivedDateTime(expected, 69, 0);
2335             } else {
2336                 expectedTimeMillis = -1;
2337                 Fields.setDateTime(expected, dateTimeArgs);
2338                 Fields.setDst(expected, -1, 9);
2339                 Fields.setDerivedDateTime(expected, 9, 9);
2340             }
2341             assertEquals("i = " + i, expectedTimeMillis, timeMillis);
2342             Fields.verifyTimeEquals("i = " + i, expected, time);
2343 
2344             // isDst = 1, normalize(false)
2345             Fields.setDateTime(time, dateTimeArgs);
2346             Fields.setDst(time, 1 /* isDst */, 9);
2347             Fields.setDerivedDateTime(time, 9, 9);
2348 
2349             timeMillis = time.normalize(false);
2350             expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
2351             assertEquals("i = " + i, expectedTimeMillis, timeMillis);
2352             if (i == 60) {
2353                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2354                 Fields.setDst(expected, 1, PstPdt.getUtcOffsetSeconds(1));
2355             } else {
2356                 Fields.setDateTime(expected, normalizedAdjustedBackwardDateTimeArgs);
2357                 Fields.setDst(expected, 0, PstPdt.getUtcOffsetSeconds(0));
2358             }
2359             Fields.setDerivedDateTime(expected, 69, 0);
2360             Fields.verifyTimeEquals("i = " + i, expected, time);
2361 
2362             // isDst = -1, normalize(true)
2363             Fields.setDateTime(time, dateTimeArgs);
2364             Fields.setDst(time, -1 /* isDst */, 9);
2365             Fields.setDerivedDateTime(time, 9, 9);
2366 
2367             timeMillis = time.normalize(true);
2368             if (i == -1) {
2369                 expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
2370                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2371                 Fields.setDst(expected, 0, PstPdt.getUtcOffsetSeconds(0));
2372                 Fields.setDerivedDateTime(expected, 69, 0);
2373             } else if (i == 60) {
2374                 expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
2375                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2376                 Fields.setDst(expected, 1, PstPdt.getUtcOffsetSeconds(1));
2377                 Fields.setDerivedDateTime(expected, 69, 0);
2378             } else {
2379                 expectedTimeMillis = -1;
2380                 Fields.setDateTime(expected, dateTimeArgs);
2381                 Fields.setDst(expected, -1, 9);
2382                 Fields.setDerivedDateTime(expected, 9, 9);
2383             }
2384             assertEquals("i = " + i, expectedTimeMillis, timeMillis);
2385             Fields.verifyTimeEquals("i = " + i, expected, time);
2386 
2387             // isDst = -1, normalize(false)
2388             Fields.setDateTime(time, dateTimeArgs);
2389             Fields.setDst(time, -1 /* isDst */, 9);
2390             Fields.setDerivedDateTime(time, 9, 9);
2391 
2392             timeMillis = time.normalize(false);
2393             if (i == -1) {
2394                 expectedTimeMillis = stdBaseTimeMillis + minutesInMillis;
2395                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2396                 Fields.setDst(expected, 0, PstPdt.getUtcOffsetSeconds(0));
2397                 Fields.setDerivedDateTime(expected, 69, 0);
2398             } else if (i == 60) {
2399                 expectedTimeMillis = dstBaseTimeMillis + minutesInMillis;
2400                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2401                 Fields.setDst(expected, 1, PstPdt.getUtcOffsetSeconds(1));
2402                 Fields.setDerivedDateTime(expected, 69, 0);
2403             } else {
2404                 expectedTimeMillis = -1;
2405                 Fields.setDateTime(expected, dateTimeArgs);
2406                 Fields.setDst(expected, -1, 9);
2407                 Fields.setDerivedDateTime(expected, 9, 9);
2408             }
2409             assertEquals("i = " + i, expectedTimeMillis, timeMillis);
2410             Fields.verifyTimeEquals("i = " + i, expected, time);
2411         }
2412     }
2413 
2414     @Test
testNormalize_duplicateWallTime()2415     public void testNormalize_duplicateWallTime() {
2416         // 1:00 in standard / 2:00 in DST
2417         long timeBaseMillis = 1194163200000L;
2418         long dstCorrectionMillis = 3600000;
2419 
2420         // Try each minute from one minute before the duplicated hour until one after.
2421         for (int i = -1; i <= 60; i++) {
2422             Time time = new Time(PstPdt.ID);
2423             Time expected = new Time(PstPdt.ID);
2424 
2425             int[] dateTimeArgs = new int[] { 2007, 10, 4, 1, i, 0 };
2426             int[] normalizedAdjustedBackwardDateTimeArgs;
2427             int[] normalizedDateTimeArgs;
2428             int[] normalizedAdjustedForwardDateTimeArgs;
2429             if (i == -1) {
2430                 normalizedAdjustedBackwardDateTimeArgs = null;
2431                 normalizedDateTimeArgs = new int[] { 2007, 10, 4, 0, 59, 0 };
2432                 normalizedAdjustedForwardDateTimeArgs = new int[] { 2007, 10, 4, 1, 59, 0 };
2433             } else if (i == 60) {
2434                 normalizedAdjustedBackwardDateTimeArgs = new int[] { 2007, 10, 4, 1, 0, 0 };
2435                 normalizedDateTimeArgs = new int[] { 2007, 10, 4, 2, 0, 0 };
2436                 normalizedAdjustedForwardDateTimeArgs = null;
2437             } else {
2438                 normalizedAdjustedBackwardDateTimeArgs = null;
2439                 normalizedDateTimeArgs = dateTimeArgs;
2440                 normalizedAdjustedForwardDateTimeArgs =  null;
2441             }
2442 
2443             int minutesInMillis = (int) Duration.ofMinutes(i).toMillis();
2444 
2445             // isDst = 0, normalize(true)
2446             Fields.setDateTime(time, dateTimeArgs);
2447             Fields.setDst(time, 0 /* isDst */, 9);
2448             Fields.setDerivedDateTime(time, 9, 9);
2449 
2450             long timeMillis = time.normalize(true);
2451 
2452             Fields.setDateTime(expected, normalizedDateTimeArgs);
2453             // When ignoreDst == true the choice between DST and STD is arbitrary when both answers
2454             // are possible.
2455             if (timeMillis == timeBaseMillis + minutesInMillis) {
2456                 // i == 60 is unambiguous
2457                 assertTrue("i =" + i, i < 60);
2458                 Fields.setDst(expected, 1 /* isDst */, PstPdt.getUtcOffsetSeconds(1));
2459             } else if (timeMillis == timeBaseMillis + minutesInMillis + dstCorrectionMillis) {
2460                 // i == -1 is unambiguous
2461                 assertTrue("i =" + i, i > -1);
2462                 Fields.setDst(expected, 0 /* isDst */, PstPdt.getUtcOffsetSeconds(0));
2463             } else {
2464                 fail("i =" + i);
2465             }
2466             Fields.setDerivedDateTime(expected, 307, 0);
2467             Fields.verifyTimeEquals("i = " + i, expected, time);
2468 
2469             // isDst = 0, normalize(false)
2470             Fields.setDateTime(time, dateTimeArgs);
2471             Fields.setDst(time, 0 /* isDst */, 9);
2472             Fields.setDerivedDateTime(time, 9, 9);
2473 
2474             timeMillis = time.normalize(false);
2475             long expectedTimeMillis = timeBaseMillis + minutesInMillis + dstCorrectionMillis;
2476             assertEquals("i = " + i, expectedTimeMillis, timeMillis);
2477             if (i == -1) {
2478                 Fields.setDateTime(expected, normalizedAdjustedForwardDateTimeArgs);
2479                 Fields.setDst(expected, 1 /* isDst */, PstPdt.getUtcOffsetSeconds(1));
2480             } else {
2481                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2482                 Fields.setDst(expected, 0 /* isDst */, PstPdt.getUtcOffsetSeconds(0));
2483             }
2484             Fields.setDerivedDateTime(expected, 307, 0);
2485             Fields.verifyTimeEquals("i = " + i, expected, time);
2486 
2487             // isDst = 1, normalize(true)
2488             Fields.setDateTime(time, dateTimeArgs);
2489             Fields.setDst(time, 1 /* isDst */, 9);
2490             Fields.setDerivedDateTime(time, 9, 9);
2491 
2492             timeMillis = time.normalize(true);
2493             Fields.setDateTime(expected, normalizedDateTimeArgs);
2494             // When ignoreDst == true the choice between DST and STD is arbitrary when both answers
2495             // are possible.
2496             if (timeMillis == timeBaseMillis + minutesInMillis) {
2497                 // i == 60 is unambiguous
2498                 assertTrue("i =" + i, i < 60);
2499                 Fields.setDst(expected, 1 /* isDst */, PstPdt.getUtcOffsetSeconds(1));
2500             } else if (timeMillis == timeBaseMillis + minutesInMillis + dstCorrectionMillis) {
2501                 // i == -1 is unambiguous
2502                 assertTrue("i =" + i, i > -1);
2503                 Fields.setDst(expected, 0 /* isDst */, PstPdt.getUtcOffsetSeconds(0));
2504             } else {
2505                 fail("i =" + i);
2506             }
2507             Fields.setDerivedDateTime(expected, 307, 0);
2508             Fields.verifyTimeEquals("i = " + i, expected, time);
2509 
2510             // isDst = 1, normalize(false)
2511             Fields.setDateTime(time, dateTimeArgs);
2512             Fields.setDst(time, 1 /* isDst */, 9);
2513             Fields.setDerivedDateTime(time, 9, 9);
2514 
2515             timeMillis = time.normalize(false);
2516             expectedTimeMillis = timeBaseMillis + minutesInMillis;
2517             if (i == 60) {
2518                 Fields.setDateTime(expected, normalizedAdjustedBackwardDateTimeArgs);
2519                 Fields.setDst(expected, 0 /* isDst */, PstPdt.getUtcOffsetSeconds(0));
2520             } else {
2521                 Fields.setDateTime(expected, normalizedDateTimeArgs);
2522                 Fields.setDst(expected, 1 /* isDst */, PstPdt.getUtcOffsetSeconds(1));
2523             }
2524             Fields.setDerivedDateTime(expected, 307, 0);
2525             assertEquals("i = " + i, expectedTimeMillis, timeMillis);
2526             Fields.verifyTimeEquals("i = " + i, expected, time);
2527 
2528             // isDst = -1, normalize(true)
2529             Fields.setDateTime(time, dateTimeArgs);
2530             Fields.setDst(time, -1 /* isDst */, 9);
2531             Fields.setDerivedDateTime(time, 9, 9);
2532 
2533             timeMillis = time.normalize(true);
2534             Fields.setDateTime(expected, normalizedDateTimeArgs);
2535             // When isDst == -1 the choice between DST and STD is arbitrary when both answers
2536             // are possible.
2537             if (timeMillis == timeBaseMillis + minutesInMillis) {
2538                 // i == 60 is unambiguous
2539                 assertTrue("i =" + i, i < 60);
2540                 Fields.setDst(expected, 1 /* isDst */, PstPdt.getUtcOffsetSeconds(1));
2541             } else if (timeMillis == timeBaseMillis + minutesInMillis + dstCorrectionMillis) {
2542                 // i == -1 is unambiguous
2543                 assertTrue("i =" + i, i > -1);
2544                 Fields.setDst(expected, 0 /* isDst */, PstPdt.getUtcOffsetSeconds(0));
2545             } else {
2546                 fail("i =" + i);
2547             }
2548             Fields.setDerivedDateTime(expected, 307, 0);
2549             Fields.verifyTimeEquals("i = " + i, expected, time);
2550 
2551             // isDst = -1, normalize(false)
2552             Fields.setDateTime(time, dateTimeArgs);
2553             Fields.setDst(time, -1 /* isDst */, 9);
2554             Fields.setDerivedDateTime(time, 9, 9);
2555 
2556             timeMillis = time.normalize(false);
2557             // When isDst == -1 the choice between DST and STD is arbitrary when both answers
2558             // are possible.
2559             if (timeMillis == timeBaseMillis + minutesInMillis) {
2560                 // i == 60 is unambiguous
2561                 assertTrue("i =" + i, i < 60);
2562                 Fields.setDst(expected, 1 /* isDst */, PstPdt.getUtcOffsetSeconds(1));
2563             } else if (timeMillis == timeBaseMillis + minutesInMillis + dstCorrectionMillis) {
2564                 // i == -1 is unambiguous
2565                 assertTrue("i =" + i, i > -1);
2566                 Fields.setDst(expected, 0 /* isDst */, PstPdt.getUtcOffsetSeconds(0));
2567             } else {
2568                 fail("i =" + i);
2569             }
2570             Fields.setDerivedDateTime(expected, 307, 0);
2571             Fields.verifyTimeEquals("i = " + i, expected, time);
2572         }
2573     }
2574 
2575     @Test
testNormalize_before32BitSeconds()2576     public void testNormalize_before32BitSeconds() {
2577         int[] timeFields = new int[] { 1900, 0, 1, 2, 3, 4, -999 /* not used */, 9, 9, 9 };
2578         verifyNormalizeInvalid(timeFields, PstPdt.ID);
2579         verifyNormalizeInvalid(timeFields, Time.TIMEZONE_UTC);
2580     }
2581 
verifyNormalizeInvalid(int[] timeFields, String timezone)2582     private static void verifyNormalizeInvalid(int[] timeFields, String timezone) {
2583         Time time = new Time(timezone);
2584         Time expected = new Time(timezone);
2585 
2586         // isDst = 0, normalize(true)
2587         Fields.set(time, timeFields);
2588         time.isDst = 0;
2589         Fields.set(expected, timeFields);
2590         expected.isDst = -1;
2591         verifyNormalizeResult(true, time, expected, -1);
2592 
2593         // isDst = 0, normalize(false)
2594         Fields.set(time, timeFields);
2595         time.isDst = 0;
2596         Fields.set(expected, timeFields);
2597         expected.isDst = 0;
2598         verifyNormalizeResult(false, time, expected, -1);
2599 
2600         // isDst = 1, normalize(true)
2601         Fields.set(time, timeFields);
2602         time.isDst = 1;
2603         Fields.set(expected, timeFields);
2604         expected.isDst = -1;
2605         verifyNormalizeResult(true, time, expected, -1);
2606 
2607         // isDst = 1, normalize(false)
2608         Fields.set(time, timeFields);
2609         time.isDst = 1;
2610         Fields.set(expected, timeFields);
2611         expected.isDst = 1;
2612         verifyNormalizeResult(false, time, expected, -1);
2613 
2614         // isDst = -1, normalize(true)
2615         Fields.set(time, timeFields);
2616         time.isDst = -1;
2617         Fields.set(expected, timeFields);
2618         expected.isDst = -1;
2619         verifyNormalizeResult(true, time, expected, -1);
2620 
2621         // isDst = -1, normalize(false)
2622         Fields.set(time, timeFields);
2623         time.isDst = -1;
2624         Fields.set(expected, timeFields);
2625         expected.isDst = -1;
2626         verifyNormalizeResult(false, time, expected, -1);
2627     }
2628 
2629     @Test
testNormalize_after32BitSeconds()2630     public void testNormalize_after32BitSeconds() {
2631         int[] timeFields = new int[] { 2039, 0, 1, 2, 3, 4, -999 /* not used */, 9, 9, 9 };
2632         verifyNormalizeInvalid(timeFields, PstPdt.ID);
2633         verifyNormalizeInvalid(timeFields, Time.TIMEZONE_UTC);
2634     }
2635 
2636     @Test
testNormalize_invalid()2637     public void testNormalize_invalid() {
2638         int[] timeFields = new int[] { 0, 0, 0, 0, 0, 0, -999 /* not used */, 9, 9, 9 };
2639         verifyNormalizeInvalid(timeFields, PstPdt.ID);
2640         verifyNormalizeInvalid(timeFields, Time.TIMEZONE_UTC);
2641     }
2642 
2643     @Test
testNormalize_dstToDstSkip()2644     public void testNormalize_dstToDstSkip() {
2645         // In London, 4th May 1941 02:00 - 03:00 was a skip from DST -> DST (+1 hour -> +2 hours)
2646         String timezone = "Europe/London";
2647         Time t = new Time(timezone);
2648         Time expected = new Time(timezone);
2649 
2650         // Demonstrate the data we expect either side of the skipped interval: 01:59
2651         Fields.set(t, 1941, 4, 4, 1, 59, 0, -1 /* isDst */, 9, 9, 9);
2652         Fields.set(expected, 1941, 4, 4, 1, 59, 0, 1 /* isDst */, 3600, 123, 0);
2653         verifyNormalizeResult(true, t, expected, -904518060000L);
2654 
2655         // Demonstrate the data we expect either side of the skipped interval: 03:00
2656         Fields.set(t, 1941, 4, 4, 3, 0, 0, -1 /* isDst */, 9, 9, 9);
2657         Fields.set(expected, 1941, 4, 4, 3, 0, 0, 1 /* isDst */, 7200, 123, 0);
2658         verifyNormalizeResult(true, t, expected, -904518000000L);
2659 
2660         // isDst = 1, normalize(false)
2661         Fields.set(t, 1941, 4, 4, 2, 30, 0, 1 /* isDst */, 9, 9, 9);
2662         Fields.set(expected, 1941, 4, 4, 2, 30, 0, 1 /* isDst */, 9, 9, 9);
2663         verifyNormalizeResult(false, t, expected, -1);
2664 
2665         // isDst = -1, normalize(false)
2666         Fields.set(t, 1941, 4, 4, 2, 30, 0, -1 /* isDst */, 9, 9, 9);
2667         Fields.set(expected, 1941, 4, 4, 2, 30, 0, -1 /* isDst */, 9, 9, 9);
2668         verifyNormalizeResult(false, t, expected, -1);
2669 
2670         // The results below are potentially arbitrary: 01:30 and 02:30 are not a valid standard
2671         // times so normalize() must apply one of the possible STD -> DST adjustments to arrive at a
2672         // date / time.
2673 
2674         // isDst = 0, normalize(false) @ 01:30
2675         Fields.set(t, 1941, 4, 4, 1, 30, 0, 0 /* isDst */, 9, 9, 9);
2676         Fields.set(expected, 1941, 4, 4, 3, 30, 0, 1 /* isDst */, 7200, 123, 0);
2677         verifyNormalizeResult(false, t, expected, -904516200000L);
2678 
2679         // isDst = 0, normalize(false) @ 02:30
2680         Fields.set(t, 1941, 4, 4, 2, 30, 0, 0 /* isDst */, 9, 9, 9);
2681         long timeMillis = t.normalize(false);
2682 
2683         if (timeMillis == -904516200000L) {
2684             // The original C implementation chooses this one.
2685             Fields.set(expected, 1941, 4, 4, 3, 30, 0, 1 /* isDst */, 7200, 123, 0);
2686         } else if (timeMillis == -904512600000L) {
2687             Fields.set(expected, 1941, 4, 4, 4, 30, 0, 1 /* isDst */, 7200, 123, 0);
2688         } else {
2689             fail();
2690         }
2691         Fields.verifyTimeEquals(expected, t);
2692     }
2693 
2694     @Test
testNormalize_dstToDstRepeat()2695     public void testNormalize_dstToDstRepeat() {
2696         // In London, 10th August 1941 02:00 - 03:00 was a repeat from DST -> DST
2697         // (+2 hour -> +1 hour)
2698         String timezone = "Europe/London";
2699         Time t = new Time(timezone);
2700         Time expected = new Time(timezone);
2701 
2702         // Demonstrate the data we expect during the repeated interval: 02:30 (first)
2703         t.set(-896052600000L);
2704         Fields.set(expected, 1941, 7, 10, 2, 30, 0, 1 /* isDst */, 7200, 221, 0);
2705         Fields.verifyTimeEquals(expected, t);
2706 
2707         // Demonstrate the data we expect during the repeated interval: 02:30 (second)
2708         t.set(-896049000000L);
2709         Fields.set(expected, 1941, 7, 10, 2, 30, 0, 1 /* isDst */, 3600, 221, 0);
2710         Fields.verifyTimeEquals(expected, t);
2711 
2712         // Now check times in the repeated hour with different isDst assertions...
2713 
2714         // isDst = 1, normalize(false) @ 02:30
2715         Fields.set(t, 1941, 7, 10, 2, 30, 0, 1 /* isDst */, 9, 9, 9);
2716         Fields.set(expected, 1941, 7, 10, 2, 30, 0, 1 /* isDst */, 3600, 221, 0);
2717         verifyNormalizeResult(false, t, expected, -896049000000L);
2718 
2719         // isDst = -1, normalize(false) @ 02:30
2720         Fields.set(t, 1941, 7, 10, 2, 30, 0, -1 /* isDst */, 9, 9, 9);
2721         Fields.set(expected, 1941, 7, 10, 2, 30, 0, 1 /* isDst */, 3600, 221, 0);
2722         verifyNormalizeResult(false, t, expected, -896049000000L);
2723 
2724         // The results below are potentially arbitrary: 01:30 and 02:30 are not a valid standard
2725         // times so normalize() must apply one of the possible STD -> DST adjustments to arrive at a
2726         // date / time.
2727 
2728         // isDst = 0, normalize(false) @ 01:30
2729         Fields.set(t, 1941, 7, 10, 1, 30, 0, 0 /* isDst */, 9, 9, 9);
2730         long timeMillis = t.normalize(false);
2731         if (timeMillis == -896052600000L) {
2732             // The original C implementation chooses this one.
2733             Fields.set(expected, 1941, 7, 10, 2, 30, 0, 1 /* isDst */, 7200, 221, 0);
2734         } else if (timeMillis == -896049000000L) {
2735             Fields.set(expected, 1941, 7, 10, 2, 30, 0, 1 /* isDst */, 3600, 221, 0);
2736         } else {
2737             fail();
2738         }
2739         Fields.verifyTimeEquals(expected, t);
2740 
2741         // isDst = 0, normalize(false) @ 02:30
2742         Fields.set(t, 1941, 7, 10, 2, 30, 0, 0 /* isDst */, 9, 9, 9);
2743         Fields.set(expected, 1941, 7, 10, 3, 30, 0, 1 /* isDst */, 3600, 221, 0);
2744         verifyNormalizeResult(false, t, expected, -896045400000L);
2745     }
2746 
2747     @Test
testNormalize_stdToStdRepeat()2748     public void testNormalize_stdToStdRepeat() {
2749         // In London, 31st October 1971 02:00 - 03:00 was a repeat from STD -> STD
2750         String timezone = "Europe/London";
2751         Time t = new Time(timezone);
2752         Time expected = new Time(timezone);
2753 
2754         // Demonstrate the data we expect during the repeated interval: 02:30 (first)
2755         t.set(57720600000L);
2756         Fields.set(expected, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 3600, 303, 0);
2757         Fields.verifyTimeEquals(expected, t);
2758 
2759         // Demonstrate the data we expect during the repeated interval: 02:30 (second)
2760         t.set(57724200000L);
2761         Fields.set(expected, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 0, 303, 0);
2762         Fields.verifyTimeEquals(expected, t);
2763 
2764         // isDst = 0, normalize(false) @ 02:30
2765         Fields.set(t, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 9, 9, 9);
2766 
2767         long timeMillis = t.normalize(false);
2768 
2769         // Either answer is valid: the choice is arbitrary.
2770         if (57720600000L == timeMillis) {
2771             // The original C implementation chooses this one.
2772             Fields.set(expected, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 3600, 303, 0);
2773         } else if (57724200000L == timeMillis) {
2774             Fields.set(expected, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 0, 303, 0);
2775         } else {
2776             fail();
2777         }
2778         Fields.verifyTimeEquals(expected, t);
2779 
2780         // isDst = -1, normalize(false) @ 02:30
2781         Fields.set(t, 1971, 9, 31, 2, 30, 0, -1 /* isDst */, 9, 9, 9);
2782 
2783         timeMillis = t.normalize(false);
2784 
2785         Fields.setDateTime(expected, 1971, 9, 31, 2, 30, 0);
2786         // Either answer is valid: the choice is arbitrary.
2787         if (57720600000L == timeMillis) {
2788             // The original C implementation chooses this one.
2789             Fields.set(expected, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 3600, 303, 0);
2790         } else if (57724200000L == timeMillis) {
2791             Fields.set(expected, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 0, 303, 0);
2792         } else {
2793             fail();
2794         }
2795         Fields.verifyTimeEquals(expected, t);
2796 
2797         // The results below are potentially arbitrary: 01:30 and 02:30 are not a valid DST
2798         // so normalize() must apply one of the possible STD -> DST adjustments to arrive at a
2799         // date / time.
2800 
2801         // isDst = 1, normalize(false) @ 01:30
2802         Fields.set(t, 1971, 9, 31, 1, 30, 0, 1 /* isDst */, 9, 9, 9);
2803 
2804         timeMillis = t.normalize(false);
2805 
2806         if (timeMillis == 57713400000L) {
2807             // Original C implementation chooses this one.
2808             Fields.set(expected, 1971, 9, 31, 0, 30, 0, 0 /* isDst */, 3600, 303, 0);
2809         } else if (timeMillis == 57717000000L) {
2810             Fields.set(expected, 1971, 9, 31, 1, 30, 0, 0 /* isDst */, 3600, 303, 0);
2811         } else {
2812             fail();
2813         }
2814         Fields.verifyTimeEquals(expected, t);
2815 
2816         // isDst = 1, normalize(false) @ 02:30
2817         Fields.set(t, 1971, 9, 31, 2, 30, 0, 1 /* isDst */, 9, 9, 9);
2818         timeMillis = t.normalize(false);
2819         if (timeMillis == 57717000000L) {
2820             // The original C implementation chooses this one.
2821             Fields.set(expected, 1971, 9, 31, 1, 30, 0, 0 /* isDst */, 3600, 303, 0);
2822         } else if (timeMillis == 57720600000L) {
2823             Fields.set(expected, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 3600, 303, 0);
2824         } else {
2825             fail();
2826         }
2827         Fields.verifyTimeEquals(expected, t);
2828 
2829         // isDst = 1, normalize(false) @ 03:30
2830         Fields.set(t, 1971, 9, 31, 3, 30, 0, 1 /* isDst */, 9, 9, 9);
2831         Fields.set(expected, 1971, 9, 31, 2, 30, 0, 0 /* isDst */, 0, 303, 0);
2832         verifyNormalizeResult(false, t, expected, 57724200000L);
2833     }
2834 
2835     @Test
testNormalize_stdToStdSkip()2836     public void testNormalize_stdToStdSkip() {
2837         // In Kiritimati, 31st Dec 1994 was skipped entirely by going from UTC-10 to UTC+14 (was a
2838         // skip from STD -> STD, plus they do not observe DST).
2839         String timezone = "Pacific/Kiritimati";
2840         Time t = new Time(timezone);
2841         Time expected = new Time(timezone);
2842 
2843         // isDst = 0, normalize(false)
2844         Fields.set(t, 1994, 11, 31, 10, 20, 0, 0 /* isDst */, 9, 9, 9);
2845         Fields.set(expected, 1994, 11, 31, 10, 20, 0, 0 /* isDst */, 9, 9, 9);
2846         verifyNormalizeResult(false, t, expected, -1);
2847 
2848         // isDst = 1, normalize(false)
2849         Fields.set(t, 1994, 11, 31, 10, 20, 0, 1 /* isDst */, 9, 9, 9);
2850         Fields.set(expected, 1994, 11, 31, 10, 20, 0, 1 /* isDst */, 9, 9, 9);
2851         verifyNormalizeResult(false, t, expected, -1);
2852 
2853         // isDst = -1, normalize(false)
2854         Fields.set(t, 1994, 11, 31, 10, 20, 0, -1 /* isDst */, 9, 9, 9);
2855         Fields.set(expected, 1994, 11, 31, 10, 20, 0, -1 /* isDst */, 9, 9, 9);
2856         verifyNormalizeResult(false, t, expected, -1);
2857     }
2858 
2859     @Test
testNormalize_utcWithDst()2860     public void testNormalize_utcWithDst() {
2861         // In UTC (or other zone without DST), what happens when a DST time is specified and there
2862         // is no DST offset available in the timezone data.
2863         Time t = new Time(Time.TIMEZONE_UTC);
2864         Time expected = new Time(Time.TIMEZONE_UTC);
2865 
2866         // isDst = 1, normalize(false)
2867         Fields.set(t, 2005, 6, 22, 1, 30, 0, 1 /* isDst */, 9, 9, 9);
2868         Fields.set(expected, 2005, 6, 22, 1, 30, 0, 1 /* isDst */, 9, 9, 9);
2869         verifyNormalizeResult(false, t, expected, -1);
2870 
2871         // isDst = -1, normalize(false)
2872         Fields.set(t, 2005, 6, 22, 1, 30, 0, -1 /* isDst */, 9, 9, 9);
2873         Fields.set(expected, 2005, 6, 22, 1, 30, 0, 0 /* isDst */, 0, 202, 5);
2874         verifyNormalizeResult(false, t, expected, 1121995800000L);
2875     }
2876 
2877     @Test
testUnknownTz()2878     public void testUnknownTz() {
2879         // Historically the code used UTC if the timezone is unrecognized.
2880 
2881         String unknownTimezoneId = "THIS_ID_IS_NOT_A_VALID_TZ";
2882         Time t = new Time(unknownTimezoneId);
2883         assertEquals(unknownTimezoneId, t.timezone);
2884         Fields.set(t, 2007, 5, 1, 1, 2, 3, -1 /* isDst */, 9, 9, 9);
2885 
2886         // We can't know for sure which time zone is being used, but we assume it is UTC if the date
2887         // normalizes to isDst == 0 and with an offset of 0 during the summer months.
2888 
2889         long timeMillis = t.normalize(true);
2890         assertEquals(unknownTimezoneId, t.timezone);
2891         assertEquals(1180659723000L, timeMillis);
2892 
2893         Time expected = new Time(unknownTimezoneId);
2894         Fields.set(expected, 2007, 5, 1, 1, 2, 3, 0 /* isDst */, 0, 151, 5);
2895         Fields.verifyTimeEquals(expected, t);
2896     }
2897 
2898     @Test
test_bug118835133_valuesCloseToInt32Saturation()2899     public void test_bug118835133_valuesCloseToInt32Saturation() {
2900         // With http://b/118835133 toMillis() returns -1. This is the original bug case.
2901         LocalDateTime localDateTime = LocalDateTime.of(2018, Month.OCTOBER, 30, 12, 48, 32);
2902         assertLocalTimeMillis("Asia/Singapore", localDateTime, 1540874912000L);
2903 
2904         // The following tests check the upper limits of what Time supports. There's no guarantee
2905         // we can't deal with values higher that this but the exact value depends on the version of
2906         // zic being used as a transition at exactly Integer.MAX_VALUE can affect our ability to
2907         // deal with times. So, we just assert what we know we must be able to do to catch major
2908         // regressions. The following timezones are chosen because of their respective geographical
2909         // locations (positive / negative offset from UTC) plus whether they had transitions at
2910         // Integer.MAX_VALUE with zic > 2014b. The transitions could change in future but there's
2911         // nothing we can do about that.
2912 
2913         // Positive offset, has MAX_VALUE transition with zic 2018e.
2914         checkUpperSupportedLimit("Asia/Singapore");
2915         // Negative offset, has MAX_VALUE transition with zic 2018e.
2916         checkUpperSupportedLimit("America/Argentina/Buenos_Aires");
2917         // Positive offset, has no MAX_VALUE transition with zic 2018e.
2918         checkUpperSupportedLimit("Asia/Shanghai");
2919         // Negative offset, has no MAX_VALUE transition with zic 2018e.
2920         checkUpperSupportedLimit("America/Los_Angeles");
2921 
2922         // See comment above, but for Integer.MIN_VALUE / lower bound. Most zones do not have an
2923         // explicit MIN_VALUE transition with zic 2018e 64-bit data but some zones differ around
2924         // whether there are known transitions before Integer.MIN_VALUE.
2925 
2926         // Positive offset zones with first transition before Integer.MIN_VALUE.
2927         checkLowerSupportedLimit("Asia/Singapore");
2928         checkLowerSupportedLimit("Asia/Shanghai");
2929         // Negative offset zones with first transition before Integer.MIN_VALUE.
2930         checkLowerSupportedLimit("America/Argentina/Buenos_Aires");
2931         checkLowerSupportedLimit("America/Los_Angeles");
2932 
2933         // Positive offset zone with first transition after Integer.MIN_VALUE.
2934         checkLowerSupportedLimit("Asia/Kathmandu");
2935         // Negative offset zone with first transition after Integer.MIN_VALUE.
2936         checkLowerSupportedLimit("America/Yellowknife");
2937     }
2938 
checkUpperSupportedLimit(String timeZoneId)2939     private static void checkUpperSupportedLimit(String timeZoneId) {
2940         // Integer.MAX_VALUE == Jan 19, 2038 03:14:07 GMT.
2941         LocalDateTime max32BitTime = LocalDateTime.of(2038, Month.JANUARY, 19, 3, 14, 7);
2942 
2943         // We take off one extra day in all cases because we can't guarantee we can handle
2944         // exactly Integer.MAX_VALUE in all cases due to integer overflow when applying zone
2945         // offsets. It gets fiddly if we try to be exact and this is good enough.
2946         LocalDateTime testLocalTime = max32BitTime.minusDays(1);
2947 
2948         long expectedMillis = calculateExpectedMillis(timeZoneId, testLocalTime);
2949         assertLocalTimeMillis(timeZoneId, testLocalTime, expectedMillis);
2950     }
2951 
checkLowerSupportedLimit(String timeZoneId)2952     private static void checkLowerSupportedLimit(String timeZoneId) {
2953         // Integer.MIN_VALUE == 13 Dec 1901 20:45:52 GMT.
2954         LocalDateTime min32BitTime = LocalDateTime.of(1901, Month.DECEMBER, 13, 20, 45, 52);
2955 
2956         // We add on one extra day in all cases because we can't guarantee we can handle
2957         // exactly Integer.MIN_VALUE in all cases due to integer underflow when applying
2958         // zone offsets.  It gets fiddly if we try to be exact and this is good enough.
2959         LocalDateTime testLocalTime = min32BitTime.plusDays(1);
2960 
2961         long expectedMillis = calculateExpectedMillis(timeZoneId, testLocalTime);
2962         assertLocalTimeMillis(timeZoneId, testLocalTime, expectedMillis);
2963     }
2964 
calculateExpectedMillis(String timeZoneId, LocalDateTime localDateTime)2965     private static long calculateExpectedMillis(String timeZoneId, LocalDateTime localDateTime) {
2966         // We use java.util.TimeZone with Calendar because that's the same data as used by Time and
2967         // so they should generally agree. ICU, and java.time which is backed by ICU on Android, are
2968         // generally better because they handle things outside of the Integer range.
2969         TimeZone tz = TimeZone.getTimeZone(timeZoneId);
2970         Calendar calendar = new GregorianCalendar(tz);
2971         calendar.set(
2972                 localDateTime.getYear(),
2973                 localDateTime.getMonthValue() - 1 /* calendar uses [0-11] */,
2974                 localDateTime.getDayOfMonth(),
2975                 localDateTime.getHour(),
2976                 localDateTime.getMinute(),
2977                 localDateTime.getSecond()
2978         );
2979         calendar.set(Calendar.MILLISECOND, 0);
2980         return calendar.getTimeInMillis();
2981     }
2982 
assertLocalTimeMillis(String timeZoneId, LocalDateTime testLocalTime, long expectedMillis)2983     private static void assertLocalTimeMillis(String timeZoneId, LocalDateTime testLocalTime,
2984             long expectedMillis) {
2985 
2986         Time t = new Time(timeZoneId);
2987         Fields.set(t,
2988                 testLocalTime.getYear(),
2989                 testLocalTime.getMonthValue() - 1 /* Time class uses [0-11] */,
2990                 testLocalTime.getDayOfMonth(),
2991                 testLocalTime.getHour(),
2992                 testLocalTime.getMinute(),
2993                 testLocalTime.getSecond(),
2994                 0 /* isDst */, 0, 0, 0);
2995         assertEquals(expectedMillis, t.toMillis(true /* ignoreDst */));
2996 
2997         // Check that Time agrees with native localtime / mktime.
2998         NativeTimeFunctions.assertNativeTimeResults(timeZoneId, testLocalTime, expectedMillis);
2999     }
3000 
3001     /**
3002      * This is a test to ensure that Time generally works in the time period that apps will be
3003      * interested in. There are various specific tests that exist to catch known edge cases but this
3004      * test gives more general coverage looking for obvious issues in simple cases. Inspired by
3005      * b/118835133 which revealed there was an obvious problem with recent dates in some zones after
3006      * Android was upgraded to a newer zic tool.
3007      */
3008     @Test
test_toMillisAndNormalizeGenerallyWork()3009     public void test_toMillisAndNormalizeGenerallyWork() {
3010         String[] tzIds = TimeZone.getAvailableIDs();
3011         for (String tzId : tzIds) {
3012             TimeZone timeZone = TimeZone.getTimeZone(tzId);
3013             Calendar calendar = new GregorianCalendar(timeZone);
3014             for (long timeInMillis : calculateToMillisTestTimes(tzId)) {
3015                 // We use Calendar to obtain local time fields for timeInMillis.
3016                 calendar.setTimeInMillis(timeInMillis);
3017 
3018                 Time time = new Time(tzId);
3019                 Fields.setDateTime(time, calendar);
3020 
3021                 // Now we check that Time can work out the time in millis from the local time fields
3022                 // we give it.
3023                 assertEquals("toMillis() must match calendar in " + tzId + " at "
3024                         + time, calendar.getTimeInMillis(), time.toMillis(true /* ignoreDst */));
3025 
3026                 // Repeat with normalize(), which allows us to also check isDst / gmtoff.
3027                 long normalizedMillis = time.normalize(true /* ignoreDst */);
3028                 assertEquals("normalize() must match calendar in " + tzId + " at "
3029                         + time, calendar.getTimeInMillis(), normalizedMillis);
3030                 assertEquals("isDst failed to match calendar in " + tzId + " at "
3031                         + time, timeZone.inDaylightTime(new Date(timeInMillis)), (time.isDst != 0));
3032                 assertEquals(timeZone.getOffset(timeInMillis),
3033                         Duration.ofSeconds(time.gmtoff).toMillis());
3034 
3035                 // Check that Time agrees with native localtime / mktime.
3036                 NativeTimeFunctions.assertNativeTimeResults(
3037                         tzId, createLocalDateTime(calendar), timeInMillis);
3038             }
3039         }
3040     }
3041 
3042     /**
3043      * Creates a LocalDateTime from a Calendar by reading out the current date/time (to second
3044      * precision only!).
3045      */
createLocalDateTime(Calendar calendar)3046     private static LocalDateTime createLocalDateTime(Calendar calendar) {
3047         return LocalDateTime.of(
3048                 calendar.get(Calendar.YEAR),
3049                 calendar.get(Calendar.MONTH) + 1,
3050                 calendar.get(Calendar.DAY_OF_MONTH),
3051                 calendar.get(Calendar.HOUR_OF_DAY),
3052                 calendar.get(Calendar.MINUTE),
3053                 calendar.get(Calendar.SECOND));
3054     }
3055 
calculateToMillisTestTimes(String tzId)3056     private static List<Long> calculateToMillisTestTimes(String tzId) {
3057         List<Long> times = new ArrayList<>();
3058 
3059         // Some local times can be ambiguous (when there's a "fall back") or missing (when there's
3060         // a "spring forward"). Calendar and Time may reasonably do different things for these. We
3061         // try to avoid those cases by picking the middle of intervals with a given offset, or at
3062         // least a day away from a transition if we can't do that.
3063 
3064         // java.time is used to calculate times below. java.time is built on ICU on Android
3065         // which is independently developed. It enables us to get to the transition / interval
3066         // information we need to generate mid-points using public APIs, something we can't
3067         // currently do any other way.
3068 
3069         // Start generating times using the interval around 1970, then iterate through following
3070         // intervals. Stop when we get close to the top of the supported range for Time.
3071         final Instant startTime = Instant.ofEpochSecond(0);
3072         final Instant stopTime = Instant.ofEpochSecond(Integer.MAX_VALUE).minus(1, ChronoUnit.DAYS);
3073 
3074         ZoneId zoneId = ZoneId.of(tzId);
3075         ZoneRules rules = zoneId.getRules();
3076         ZoneOffsetTransition low = rules.previousTransition(startTime);
3077         if (low == null) {
3078             low = rules.nextTransition(startTime);
3079             if (low == null) {
3080                 // Make sure we always test some times even for simple time zones without
3081                 // transitions.
3082                 return Arrays.asList(startTime.toEpochMilli(), stopTime.toEpochMilli());
3083             }
3084         }
3085 
3086         ZoneOffsetTransition high;
3087         while ((high = rules.nextTransition(low.getInstant())) != null) {
3088             Instant highTime = high.getInstant();
3089             if (highTime.isAfter(stopTime)) {
3090                 break;
3091             }
3092 
3093             Instant lowTime = low.getInstant();
3094             Instant midPointTime = calculateMidPoint(highTime, lowTime);
3095             if (midPointTime.isBefore(startTime)) {
3096                 // Avoid the first-loop case where the mid point can be before the start time.
3097                 // The next transition must be after start time so use a time before that.
3098                 times.add(highTime.minus(1, ChronoUnit.DAYS).toEpochMilli());
3099             } else {
3100                 times.add(midPointTime.toEpochMilli());
3101             }
3102 
3103             low = high;
3104         }
3105 
3106         // Add a time for the last interval we encountered. We either ran out of transitions or the
3107         // end point of the next transition is above the stop time.
3108         Instant finalTime = low.getInstant().plus(1, ChronoUnit.DAYS);
3109         if (finalTime.isAfter(startTime) && finalTime.isBefore(stopTime)) {
3110             times.add(finalTime.toEpochMilli());
3111         }
3112         return times;
3113     }
3114 
calculateMidPoint(Instant highStart, Instant lowStart)3115     private static Instant calculateMidPoint(Instant highStart, Instant lowStart) {
3116         // Use seconds so we don't end up with half seconds that Time can't deal with.
3117         long midPointSeconds = (lowStart.getEpochSecond() + highStart.getEpochSecond()) / 2;
3118         return Instant.ofEpochSecond(midPointSeconds);
3119     }
3120 
3121     @Test
test_bug118835133_fixEarliestRawOffsetValue()3122     public void test_bug118835133_fixEarliestRawOffsetValue() {
3123         // This test confirms the behavior between Integer.MIN_VALUE seconds and the next
3124         // transition. With zic <= 2014b there is no explicit transition at Integer.MIN_VALUE
3125         // seconds. Between 2014c and 2018e there is one, and it goes away again in 2018f. This test
3126         // should pass regardless of zic version used as the Android behavior is stable for this
3127         // zone as we have fixed the logic used to determine the offset before the first transition
3128         // AND the first transition for the zone is after Integer.MIN_VALUE.
3129         String tzId = "Africa/Abidjan";
3130         Time t = new Time(tzId);
3131         // Jan 1, 1912 12:16:08 AM UTC / Jan 1, 1912 00:00:00 local time
3132         Instant oldEarliestTransition = Instant.ofEpochSecond(-1830383032);
3133         Instant beforeOldEarliestTransition = oldEarliestTransition.minus(Duration.ofDays(1));
3134         t.set(beforeOldEarliestTransition.toEpochMilli());
3135 
3136         // The expected local time equivalent to the oldEarliestTransition time minus 1 day and
3137         // offset by -968 seconds.
3138         Time expected = new Time(tzId);
3139         Fields.set(expected, 1911, 11, 31, 0, 0, 0, 0 /* isDst */, -968, 364, 0);
3140         Fields.verifyTimeEquals(expected, t);
3141 
3142         // Check that Time agrees with native localtime / mktime.
3143         NativeTimeFunctions.assertNativeTimeResults(
3144                 tzId, LocalDateTime.of(1911, Month.DECEMBER, 31, 0, 0, 0),
3145                 beforeOldEarliestTransition.toEpochMilli());
3146     }
3147 
verifyNormalizeResult(boolean normalizeArgument, Time toNormalize, Time expectedTime, long expectedTimeMillis)3148     private static void verifyNormalizeResult(boolean normalizeArgument, Time toNormalize,
3149             Time expectedTime, long expectedTimeMillis) {
3150         long actualTimeMillis = toNormalize.normalize(normalizeArgument /* ignore isDst */);
3151         assertEquals(expectedTimeMillis, actualTimeMillis);
3152         Fields.verifyTimeEquals(expectedTime, toNormalize);
3153     }
3154 
3155     /** A helper class for manipulating / testing fields on Time objects. */
3156     private static class Fields {
3157         final static int MAIN_DATE_TIME = 1;
3158         final static int DST_FIELDS = 2;
3159         final static int DERIVED_DATE_TIME = 4;
3160 
3161         final static int ALL = MAIN_DATE_TIME | DST_FIELDS | DERIVED_DATE_TIME;
3162 
verifyTimeEquals(Time expected, Time actual)3163         public static void verifyTimeEquals(Time expected, Time actual) {
3164             verifyTimeEquals("", ALL, expected, actual);
3165         }
3166 
verifyTimeEquals(String message, Time expected, Time actual)3167         public static void verifyTimeEquals(String message, Time expected, Time actual) {
3168             verifyTimeEquals(message, Fields.ALL, expected, actual);
3169         }
3170 
verifyTimeEquals(String message, int fields, Time expected, Time actual)3171         public static void verifyTimeEquals(String message, int fields, Time expected,
3172                 Time actual) {
3173             boolean mainDateTimeOk = (fields & Fields.MAIN_DATE_TIME) == 0
3174                     || (Objects.equals(expected.timezone, actual.timezone)
3175                     && expected.year == actual.year
3176                     && expected.month == actual.month
3177                     && expected.monthDay == actual.monthDay
3178                     && expected.hour == actual.hour
3179                     && expected.minute == actual.minute
3180                     && expected.second == actual.second
3181                     && expected.allDay == actual.allDay);
3182 
3183             boolean dstFieldsOk = (fields & Fields.DST_FIELDS) == 0
3184                     || (expected.isDst == actual.isDst && expected.gmtoff == actual.gmtoff);
3185 
3186             boolean derivedDateTimeOk = (fields & Fields.DERIVED_DATE_TIME) == 0
3187                     || (expected.yearDay == actual.yearDay && expected.weekDay == actual.weekDay);
3188 
3189             if (!mainDateTimeOk || !dstFieldsOk || !derivedDateTimeOk) {
3190                 String expectedTime = timeToString(fields, expected);
3191                 String actualTime = timeToString(fields, actual);
3192                 fail(message + " [Time fields differed. Expected: " + expectedTime + "Actual: "
3193                         + actualTime + "]");
3194             }
3195         }
3196 
timeToString(int fields, Time time)3197         private static String timeToString(int fields, Time time) {
3198             List<Object> values = new ArrayList<>();
3199             StringBuilder format = new StringBuilder();
3200             if ((fields & Fields.MAIN_DATE_TIME) > 0) {
3201                 format.append("%d-%02d-%02d %02d:%02d:%02d allDay=%b timezone=%s ");
3202                 values.addAll(
3203                         Arrays.asList(time.year, time.month, time.monthDay, time.hour, time.minute,
3204                                 time.second, time.allDay, time.timezone));
3205             }
3206             if ((fields & Fields.DST_FIELDS) > 0) {
3207                 format.append("isDst=%d, gmtoff=%d ");
3208                 values.add(time.isDst);
3209                 values.add(time.gmtoff);
3210             }
3211             if ((fields & Fields.DERIVED_DATE_TIME) > 0) {
3212                 format.append("yearDay=%d, weekDay=%d ");
3213                 values.add(time.yearDay);
3214                 values.add(time.weekDay);
3215 
3216             }
3217             return String.format(format.toString(), values.toArray());
3218         }
3219 
3220         /**
3221          * Set date/time fields (only) on the supplied time. month is [0-11].
3222          */
setDateTime(Time t, int year, int month, int monthDay, int hour, int minute, int second)3223         public static void setDateTime(Time t, int year, int month, int monthDay, int hour,
3224                 int minute, int second) {
3225             t.year = year;
3226             t.month = month;
3227             t.monthDay = monthDay;
3228             t.hour = hour;
3229             t.minute = minute;
3230             t.second = second;
3231             t.allDay = false;
3232         }
3233 
3234         /**
3235          * Set date/time fields (only) on the supplied time using the equivalent Calendar fields.
3236          */
setDateTime(Time t, Calendar calendar)3237         public static void setDateTime(Time t, Calendar calendar) {
3238             setDateTime(t,
3239                     calendar.get(Calendar.YEAR),
3240                     calendar.get(Calendar.MONTH),
3241                     calendar.get(Calendar.DAY_OF_MONTH),
3242                     calendar.get(Calendar.HOUR_OF_DAY),
3243                     calendar.get(Calendar.MINUTE),
3244                     calendar.get(Calendar.SECOND));
3245         }
3246 
3247         /**
3248          * See {@link #setDateTime(Time, int, int, int, int, int, int)} for array order.
3249          */
setDateTime(Time t, int[] args)3250         public static void setDateTime(Time t, int[] args) {
3251             assertEquals(6, args.length);
3252             setDateTime(t, args[0], args[1], args[2], args[3], args[4], args[5]);
3253         }
3254 
setDst(Time t, int isDst, int gmtoff)3255         public static void setDst(Time t, int isDst, int gmtoff) {
3256             t.isDst = isDst;
3257             t.gmtoff = gmtoff;
3258         }
3259 
setDerivedDateTime(Time t, int yearDay, int weekDay)3260         public static void setDerivedDateTime(Time t, int yearDay, int weekDay) {
3261             t.yearDay = yearDay;
3262             t.weekDay = weekDay;
3263         }
3264 
3265         /**
3266          * Set fields on the supplied time. month is [0-11].
3267          */
setAllDayDate(Time t, int year, int month, int monthDay)3268         public static void setAllDayDate(Time t, int year, int month, int monthDay) {
3269             t.year = year;
3270             t.month = month;
3271             t.monthDay = monthDay;
3272             t.allDay = true;
3273         }
3274 
3275         /**
3276          * See {@link #set(Time, int, int, int, int, int, int, int, int, int, int)} for array order.
3277          */
set(Time t, int[] args)3278         public static void set(Time t, int[] args) {
3279             assertEquals(10, args.length);
3280             set(t, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8],
3281                     args[9]);
3282         }
3283 
3284         /**
3285          * Set fields on the supplied time. month is [0-11].
3286          */
set(Time t, int year, int month, int monthDay, int hour, int minute, int second, int isDst, int gmtoff, int yearDay, int weekDay)3287         public static void set(Time t, int year, int month, int monthDay, int hour, int minute,
3288                 int second, int isDst, int gmtoff, int yearDay, int weekDay) {
3289             setDateTime(t, year, month, monthDay, hour, minute, second);
3290             setDst(t, isDst, gmtoff);
3291             setDerivedDateTime(t, yearDay, weekDay);
3292         }
3293     }
3294 
3295     private static class PstPdt {
3296         public static final String ID = "America/Los_Angeles";
3297 
getUtcOffsetSeconds(int isDst)3298         public static int getUtcOffsetSeconds(int isDst) {
3299             if (isDst == 0) {
3300                 return -28800;
3301             } else if (isDst == 1) {
3302                 return -25200;
3303             }
3304             throw new IllegalArgumentException();
3305         }
3306 
getUtcOffsetSeconds(boolean isDst)3307         public static int getUtcOffsetSeconds(boolean isDst) {
3308             return getUtcOffsetSeconds(isDst ? 1 : 0);
3309         }
3310 
getUtcOffsetMillis(boolean isDst)3311         public static int getUtcOffsetMillis(boolean isDst) {
3312             return (int) Duration.ofSeconds(getUtcOffsetSeconds(isDst)).toMillis();
3313         }
3314     }
3315 
maybeInitializeSystemLocales()3316     private static void maybeInitializeSystemLocales() {
3317         if (sSystemLocales == null) {
3318             String[] locales = Resources.getSystem().getAssets().getLocales();
3319             List<Locale> systemLocales = new ArrayList<Locale>(locales.length);
3320             for (String localeStr : locales) {
3321                 systemLocales.add(Locale.forLanguageTag(localeStr));
3322             }
3323 
3324             sSystemLocales = systemLocales;
3325         }
3326     }
3327 
changeJavaAndAndroidLocale(Locale locale, boolean force)3328     private static boolean changeJavaAndAndroidLocale(Locale locale, boolean force) {
3329         // The Time class uses the Android-managed locale for string resources containing format
3330         // patterns and the Java-managed locale for other things (e.g. month names, week days names)
3331         // that are placed into those patterns. For example the format "%c" expands to include
3332         // a pattern that includes month names.
3333         // Changing the Java-managed Locale does not affect the Android-managed locale.
3334         // Changing the Android-managed locale does not affect the Java-managed locale.
3335         //
3336         // In order to ensure consistent behavior in the tests the device Locale must not be
3337         // assumed. To simulate the most common behavior (i.e. when the Java and the Android-managed
3338         // locales agree), when the Java-managed locale is changed during this test the locale in
3339         // the runtime-local copy of the system resources is modified as well.
3340 
3341         if (!force && !sSystemLocales.contains(locale)) {
3342             return false;
3343         }
3344 
3345         // Change the Java-managed locale.
3346         Locale.setDefault(locale);
3347 
3348         // Change the local copy of the Android system configuration: this simulates the device
3349         // being set to the locale and forces a reload of the string resources.
3350         Configuration configuration = Resources.getSystem().getConfiguration();
3351         configuration.locale = locale;
3352         Resources.getSystem().updateConfiguration(configuration, null);
3353         return true;
3354     }
3355 }
3356