1 /*
2  * Copyright (C) 2017 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 com.android.server.notification;
18 
19 import static junit.framework.Assert.assertFalse;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 
24 import android.service.notification.ScheduleCalendar;
25 import android.service.notification.ZenModeConfig;
26 import android.test.suitebuilder.annotation.SmallTest;
27 
28 import androidx.test.filters.FlakyTest;
29 import androidx.test.runner.AndroidJUnit4;
30 
31 import com.android.server.UiServiceTestCase;
32 
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.util.Calendar;
38 import java.util.GregorianCalendar;
39 import java.util.TimeZone;
40 
41 @SmallTest
42 @RunWith(AndroidJUnit4.class)
43 public class ScheduleCalendarTest extends UiServiceTestCase {
44 
45     private ScheduleCalendar mScheduleCalendar;
46     private ZenModeConfig.ScheduleInfo mScheduleInfo;
47 
48     @Before
setUp()49     public void setUp() throws Exception {
50         mScheduleCalendar = new ScheduleCalendar();
51         mScheduleInfo = new ZenModeConfig.ScheduleInfo();
52         mScheduleInfo.days = new int[] {1, 2, 3, 4, 5};
53     }
54 
55     @Test
testNullScheduleInfo()56     public void testNullScheduleInfo() throws Exception {
57         mScheduleCalendar.setSchedule(null);
58 
59         mScheduleCalendar.maybeSetNextAlarm(1000, 1999);
60         assertEquals(0, mScheduleCalendar.getNextChangeTime(1000));
61         assertFalse(mScheduleCalendar.isInSchedule(100));
62         assertFalse(mScheduleCalendar.shouldExitForAlarm(100));
63     }
64 
65     @Test
testGetNextChangeTime_startToday()66     public void testGetNextChangeTime_startToday() throws Exception {
67         Calendar cal = new GregorianCalendar();
68         cal.set(Calendar.HOUR_OF_DAY, 1);
69         cal.set(Calendar.MINUTE, 15);
70         cal.set(Calendar.SECOND, 0);
71         cal.set(Calendar.MILLISECOND, 0);
72         mScheduleInfo.days = new int[] {getTodayDay()};
73         mScheduleInfo.startHour = cal.get(Calendar.HOUR_OF_DAY) + 1;
74         mScheduleInfo.endHour = cal.get(Calendar.HOUR_OF_DAY) + 3;
75         mScheduleInfo.startMinute = 15;
76         mScheduleInfo.endMinute = 15;
77         mScheduleInfo.exitAtAlarm = false;
78         mScheduleCalendar.setSchedule(mScheduleInfo);
79 
80         Calendar expected = new GregorianCalendar();
81         expected.setTimeInMillis(cal.getTimeInMillis());
82         expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.startHour);
83 
84         long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis());
85         GregorianCalendar actual = new GregorianCalendar();
86         actual.setTimeInMillis(actualMs);
87         assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
88                 actualMs);
89     }
90 
91     @Test
testGetNextChangeTime_endToday()92     public void testGetNextChangeTime_endToday() throws Exception {
93         Calendar cal = new GregorianCalendar();
94         cal.set(Calendar.HOUR_OF_DAY, 2);
95         cal.set(Calendar.MINUTE, 15);
96         cal.set(Calendar.SECOND, 0);
97         cal.set(Calendar.MILLISECOND, 0);
98         mScheduleInfo.days = new int[] {getTodayDay()};
99         mScheduleInfo.startHour = cal.get(Calendar.HOUR_OF_DAY) - 1;
100         mScheduleInfo.endHour = cal.get(Calendar.HOUR_OF_DAY) + 3;
101         mScheduleInfo.startMinute = 15;
102         mScheduleInfo.endMinute = 15;
103         mScheduleInfo.exitAtAlarm = false;
104         mScheduleCalendar.setSchedule(mScheduleInfo);
105 
106         Calendar expected = new GregorianCalendar();
107         expected.setTimeInMillis(cal.getTimeInMillis());
108         expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.endHour);
109         expected.set(Calendar.MINUTE, mScheduleInfo.endMinute);
110 
111         long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis());
112         GregorianCalendar actual = new GregorianCalendar();
113         actual.setTimeInMillis(actualMs);
114         assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
115                 actualMs);
116     }
117 
118     @Test
testGetNextChangeTime_startTomorrow()119     public void testGetNextChangeTime_startTomorrow() throws Exception {
120         Calendar cal = new GregorianCalendar();
121         cal.set(Calendar.HOUR_OF_DAY, 23);
122         cal.set(Calendar.MINUTE, 15);
123         cal.set(Calendar.SECOND, 0);
124         cal.set(Calendar.MILLISECOND, 0);
125         mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
126         mScheduleInfo.startHour = 1;
127         mScheduleInfo.endHour = 3;
128         mScheduleInfo.startMinute = 15;
129         mScheduleInfo.endMinute = 15;
130         mScheduleInfo.exitAtAlarm = false;
131         mScheduleCalendar.setSchedule(mScheduleInfo);
132 
133         Calendar expected = new GregorianCalendar();
134         expected.setTimeInMillis(cal.getTimeInMillis());
135         expected.add(Calendar.DATE, 1);
136         expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.startHour);
137         expected.set(Calendar.MINUTE, mScheduleInfo.startMinute);
138 
139         long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis());
140         GregorianCalendar actual = new GregorianCalendar();
141         actual.setTimeInMillis(actualMs);
142         assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
143                 actualMs);
144     }
145 
146     @Test
testGetNextChangeTime_endTomorrow()147     public void testGetNextChangeTime_endTomorrow() throws Exception {
148         Calendar cal = new GregorianCalendar();
149         cal.set(Calendar.HOUR_OF_DAY, 23);
150         cal.set(Calendar.MINUTE, 15);
151         cal.set(Calendar.SECOND, 0);
152         cal.set(Calendar.MILLISECOND, 0);
153         mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
154         mScheduleInfo.startHour = 22;
155         mScheduleInfo.endHour = 3;
156         mScheduleInfo.startMinute = 15;
157         mScheduleInfo.endMinute = 15;
158         mScheduleInfo.exitAtAlarm = false;
159         mScheduleCalendar.setSchedule(mScheduleInfo);
160 
161         Calendar expected = new GregorianCalendar();
162         expected.setTimeInMillis(cal.getTimeInMillis());
163         expected.add(Calendar.DATE, 1);
164         expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.endHour);
165         expected.set(Calendar.MINUTE, mScheduleInfo.endMinute);
166 
167         long actualMs = mScheduleCalendar.getNextChangeTime(cal.getTimeInMillis());
168         GregorianCalendar actual = new GregorianCalendar();
169         actual.setTimeInMillis(actualMs);
170         assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
171                 actualMs);
172     }
173 
174     @Test
testGetNextChangeTime_startTomorrowInDaylight()175     public void testGetNextChangeTime_startTomorrowInDaylight() {
176         // Test that the correct thing happens when the next start time would be tomorrow, during
177         // a schedule start time that doesn't exist that day. Consistent with "start times" as
178         // implemented in isInSchedule, this should get adjusted to the closest actual time.
179         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
180 
181         // "today" = the day before the skipped hour for daylight savings.
182         Calendar today = getDaylightSavingsForwardDay();
183         today.set(Calendar.HOUR_OF_DAY, 23);
184         today.set(Calendar.MINUTE, 15);
185         Calendar tomorrow = getDaylightSavingsForwardDay();
186         tomorrow.add(Calendar.DATE, 1);
187         mScheduleInfo.days = new int[] {today.get(Calendar.DAY_OF_WEEK),
188                 tomorrow.get(Calendar.DAY_OF_WEEK)};
189         mScheduleInfo.startHour = 2;
190         mScheduleInfo.endHour = 4;
191         mScheduleInfo.startMinute = 15;
192         mScheduleInfo.endMinute = 15;
193         mScheduleInfo.exitAtAlarm = false;
194         mScheduleCalendar.setSchedule(mScheduleInfo);
195 
196         // The expected next change time should be tomorrow, 3AM as 2:15AM doesn't exist.
197         Calendar expected = new GregorianCalendar(TimeZone.getTimeZone("America/New_York"));
198         expected.setTimeInMillis(tomorrow.getTimeInMillis());
199         expected.set(Calendar.HOUR_OF_DAY, 3);
200         expected.set(Calendar.MINUTE, 0);
201         expected.set(Calendar.SECOND, 0);
202         expected.set(Calendar.MILLISECOND, 0);
203 
204         long actualMs = mScheduleCalendar.getNextChangeTime(today.getTimeInMillis());
205         GregorianCalendar actual = new GregorianCalendar(TimeZone.getTimeZone("America/New_York"));
206         actual.setTimeInMillis(actualMs);
207         assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
208                 actualMs);
209     }
210 
211     @Test
testGetNextChangeTime_startTomorrowWhenTodayIsDaylight()212     public void testGetNextChangeTime_startTomorrowWhenTodayIsDaylight() {
213         // Test that the correct thing happens when the next start time would be tomorrow, but
214         // today is the day when daylight time switches over (so the "schedule start time" today
215         // may not exist).
216         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
217 
218         // "today" = the day with the skipped hour for daylight savings.
219         Calendar today = getDaylightSavingsForwardDay();
220         today.add(Calendar.DATE, 1);
221         today.set(Calendar.HOUR_OF_DAY, 23);
222         today.set(Calendar.MINUTE, 15);
223         Calendar tomorrow = getDaylightSavingsForwardDay();
224         tomorrow.add(Calendar.DATE, 2);
225         mScheduleInfo.days = new int[] {today.get(Calendar.DAY_OF_WEEK),
226                 tomorrow.get(Calendar.DAY_OF_WEEK)};
227         mScheduleInfo.startHour = 2;
228         mScheduleInfo.endHour = 4;
229         mScheduleInfo.startMinute = 15;
230         mScheduleInfo.endMinute = 15;
231         mScheduleInfo.exitAtAlarm = false;
232         mScheduleCalendar.setSchedule(mScheduleInfo);
233 
234         // The expected next change time should be tomorrow, 2:15AM.
235         Calendar expected = new GregorianCalendar(TimeZone.getTimeZone("America/New_York"));
236         expected.setTimeInMillis(tomorrow.getTimeInMillis());
237         expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.startHour);
238         expected.set(Calendar.MINUTE, mScheduleInfo.startMinute);
239         expected.set(Calendar.SECOND, 0);
240         expected.set(Calendar.MILLISECOND, 0);
241 
242         long actualMs = mScheduleCalendar.getNextChangeTime(today.getTimeInMillis());
243         GregorianCalendar actual = new GregorianCalendar(TimeZone.getTimeZone("America/New_York"));
244         actual.setTimeInMillis(actualMs);
245         assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
246                 actualMs);
247     }
248 
249     @Test
testGetNextChangeTime_startTomorrowWhenTodayIsDaylightBackward()250     public void testGetNextChangeTime_startTomorrowWhenTodayIsDaylightBackward() {
251         // Test that the correct thing happens when the next start time would be tomorrow, but
252         // today is the day when clocks are adjusted backwards (so the "schedule start time" today
253         // exists twice).
254         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
255 
256         // "today" = the day with the extra hour for daylight savings.
257         Calendar today = getDaylightSavingsBackwardDay();
258         today.add(Calendar.DATE, 1);
259         today.set(Calendar.HOUR_OF_DAY, 23);
260         today.set(Calendar.MINUTE, 15);
261         Calendar tomorrow = getDaylightSavingsBackwardDay();
262         tomorrow.add(Calendar.DATE, 2);
263         mScheduleInfo.days = new int[] {today.get(Calendar.DAY_OF_WEEK),
264                 tomorrow.get(Calendar.DAY_OF_WEEK)};
265         mScheduleInfo.startHour = 1;
266         mScheduleInfo.endHour = 4;
267         mScheduleInfo.startMinute = 15;
268         mScheduleInfo.endMinute = 15;
269         mScheduleInfo.exitAtAlarm = false;
270         mScheduleCalendar.setSchedule(mScheduleInfo);
271 
272         // The expected next change time should be tomorrow, 1:15AM.
273         Calendar expected = new GregorianCalendar(TimeZone.getTimeZone("America/New_York"));
274         expected.setTimeInMillis(tomorrow.getTimeInMillis());
275         expected.set(Calendar.HOUR_OF_DAY, mScheduleInfo.startHour);
276         expected.set(Calendar.MINUTE, mScheduleInfo.startMinute);
277         expected.set(Calendar.SECOND, 0);
278         expected.set(Calendar.MILLISECOND, 0);
279 
280         long actualMs = mScheduleCalendar.getNextChangeTime(today.getTimeInMillis());
281         GregorianCalendar actual = new GregorianCalendar(TimeZone.getTimeZone("America/New_York"));
282         actual.setTimeInMillis(actualMs);
283         assertEquals("Expected " + expected + " was " + actual, expected.getTimeInMillis(),
284                 actualMs);
285     }
286 
287     @Test
testShouldExitForAlarm_settingOff()288     public void testShouldExitForAlarm_settingOff() {
289         mScheduleInfo.exitAtAlarm = false;
290         mScheduleInfo.nextAlarm = 1000;
291         mScheduleCalendar.setSchedule(mScheduleInfo);
292 
293         assertFalse(mScheduleCalendar.shouldExitForAlarm(1000));
294     }
295 
296     @Test
testShouldExitForAlarm_beforeAlarm()297     public void testShouldExitForAlarm_beforeAlarm() {
298         mScheduleInfo.exitAtAlarm = true;
299         mScheduleInfo.nextAlarm = 1000;
300         mScheduleCalendar.setSchedule(mScheduleInfo);
301 
302         assertFalse(mScheduleCalendar.shouldExitForAlarm(999));
303     }
304 
305     @Test
testShouldExitForAlarm_noAlarm()306     public void testShouldExitForAlarm_noAlarm() {
307         mScheduleInfo.exitAtAlarm = true;
308         mScheduleInfo.nextAlarm = 0;
309         mScheduleCalendar.setSchedule(mScheduleInfo);
310 
311         assertFalse(mScheduleCalendar.shouldExitForAlarm(999));
312     }
313 
314     @Test
testShouldExitForAlarm()315     public void testShouldExitForAlarm() {
316         mScheduleInfo.exitAtAlarm = true;
317         mScheduleInfo.nextAlarm = 1000;
318         mScheduleCalendar.setSchedule(mScheduleInfo);
319 
320         assertTrue(mScheduleCalendar.shouldExitForAlarm(1000));
321     }
322 
323     @Test
testShouldExitForAlarm_oldAlarm()324     public void testShouldExitForAlarm_oldAlarm() {
325         // Cal: today 2:15pm
326         Calendar now = new GregorianCalendar();
327         now.set(Calendar.HOUR_OF_DAY, 14);
328         now.set(Calendar.MINUTE, 15);
329         now.set(Calendar.SECOND, 0);
330         now.set(Calendar.MILLISECOND, 0);
331 
332         // ScheduleInfo: today 12:16pm  - today 3:15pm
333         mScheduleInfo.days = new int[] {getTodayDay()};
334         mScheduleInfo.startHour = 12;
335         mScheduleInfo.endHour = 3;
336         mScheduleInfo.startMinute = 16;
337         mScheduleInfo.endMinute = 15;
338         mScheduleInfo.exitAtAlarm = true;
339         mScheduleInfo.nextAlarm = 1000; // very old alarm
340 
341         mScheduleCalendar.setSchedule(mScheduleInfo);
342         assertTrue(mScheduleCalendar.isInSchedule(now.getTimeInMillis()));
343 
344         // don't exit for an alarm if it's an old alarm
345         assertFalse(mScheduleCalendar.shouldExitForAlarm(now.getTimeInMillis()));
346     }
347 
348     @Test
testShouldExitForAlarm_oldAlarmInSchedule()349     public void testShouldExitForAlarm_oldAlarmInSchedule() {
350         // calNow: day 2 at 9pm
351         Calendar calNow = new GregorianCalendar();
352         calNow.set(Calendar.HOUR_OF_DAY, 21);
353         calNow.set(Calendar.MINUTE, 0);
354         calNow.set(Calendar.SECOND, 0);
355         calNow.set(Calendar.MILLISECOND, 0);
356         calNow.add(Calendar.DATE, 1); // add a day
357 
358         // calAlarm: day 2 at 5am
359         Calendar calAlarm = new GregorianCalendar();
360         calAlarm.set(Calendar.HOUR_OF_DAY, 5);
361         calAlarm.set(Calendar.MINUTE, 0);
362         calAlarm.set(Calendar.SECOND, 0);
363         calAlarm.set(Calendar.MILLISECOND, 0);
364         calAlarm.add(Calendar.DATE, 1); // add a day
365 
366         // ScheduleInfo: day 1, day 2: 9pm-7am
367         mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
368         mScheduleInfo.startHour = 21;
369         mScheduleInfo.endHour = 7;
370         mScheduleInfo.startMinute = 0;
371         mScheduleInfo.endMinute = 0;
372         mScheduleInfo.exitAtAlarm = true;
373         mScheduleInfo.nextAlarm = calAlarm.getTimeInMillis(); // old alarm (5am day 2)
374 
375         mScheduleCalendar.setSchedule(mScheduleInfo);
376         assertTrue(mScheduleCalendar.isInSchedule(calNow.getTimeInMillis()));
377         assertTrue(mScheduleCalendar.isInSchedule(calAlarm.getTimeInMillis()));
378 
379         // don't exit for an alarm if it's an old alarm
380         assertFalse(mScheduleCalendar.shouldExitForAlarm(calNow.getTimeInMillis()));
381     }
382 
383     @Test
testMaybeSetNextAlarm_settingOff()384     public void testMaybeSetNextAlarm_settingOff() {
385         mScheduleInfo.exitAtAlarm = false;
386         mScheduleInfo.nextAlarm = 0;
387         mScheduleCalendar.setSchedule(mScheduleInfo);
388 
389         mScheduleCalendar.maybeSetNextAlarm(1000, 2000);
390 
391         assertEquals(0, mScheduleInfo.nextAlarm);
392     }
393 
394     @Test
testMaybeSetNextAlarm_settingOn()395     public void testMaybeSetNextAlarm_settingOn() {
396         mScheduleInfo.exitAtAlarm = true;
397         mScheduleInfo.nextAlarm = 0;
398         mScheduleCalendar.setSchedule(mScheduleInfo);
399 
400         mScheduleCalendar.maybeSetNextAlarm(1000, 2000);
401 
402         assertEquals(2000, mScheduleInfo.nextAlarm);
403     }
404 
405     @Test
testMaybeSetNextAlarm_alarmCanceled()406     public void testMaybeSetNextAlarm_alarmCanceled() {
407         mScheduleInfo.exitAtAlarm = true;
408         mScheduleInfo.nextAlarm = 10000;
409         mScheduleCalendar.setSchedule(mScheduleInfo);
410 
411         mScheduleCalendar.maybeSetNextAlarm(1000, 0);
412 
413         assertEquals(0, mScheduleInfo.nextAlarm);
414     }
415 
416     @Test
testMaybeSetNextAlarm_earlierAlarm()417     public void testMaybeSetNextAlarm_earlierAlarm() {
418         mScheduleInfo.exitAtAlarm = true;
419         mScheduleInfo.nextAlarm = 2000;
420         mScheduleCalendar.setSchedule(mScheduleInfo);
421 
422         mScheduleCalendar.maybeSetNextAlarm(1000, 1500);
423 
424         assertEquals(1500, mScheduleInfo.nextAlarm);
425     }
426 
427     @Test
testMaybeSetNextAlarm_laterAlarm()428     public void testMaybeSetNextAlarm_laterAlarm() {
429         mScheduleInfo.exitAtAlarm = true;
430         mScheduleCalendar.setSchedule(mScheduleInfo);
431         mScheduleInfo.nextAlarm = 2000;
432 
433         // next alarm updated to 3000 (alarm for 2000 was changed to 3000)
434         mScheduleCalendar.maybeSetNextAlarm(1000, 3000);
435 
436         assertEquals(3000, mScheduleInfo.nextAlarm);
437     }
438 
439     @Test
testMaybeSetNextAlarm_expiredAlarm()440     public void testMaybeSetNextAlarm_expiredAlarm() {
441         mScheduleInfo.exitAtAlarm = true;
442         mScheduleInfo.nextAlarm = 998;
443         mScheduleCalendar.setSchedule(mScheduleInfo);
444 
445         mScheduleCalendar.maybeSetNextAlarm(1000, 999);
446 
447         assertEquals(0, mScheduleInfo.nextAlarm);
448     }
449 
450     @Test
testMaybeSetNextAlarm_expiredOldAlarm()451     public void testMaybeSetNextAlarm_expiredOldAlarm() {
452         mScheduleInfo.exitAtAlarm = true;
453         mScheduleInfo.nextAlarm = 998;
454         mScheduleCalendar.setSchedule(mScheduleInfo);
455 
456         mScheduleCalendar.maybeSetNextAlarm(1000, 1001);
457 
458         assertEquals(1001, mScheduleInfo.nextAlarm);
459     }
460 
461     @Test
462     @FlakyTest
testIsInSchedule_inScheduleOvernight()463     public void testIsInSchedule_inScheduleOvernight() {
464         Calendar cal = new GregorianCalendar();
465         cal.set(Calendar.HOUR_OF_DAY, 23);
466         cal.set(Calendar.MINUTE, 15);
467         cal.set(Calendar.SECOND, 0);
468         cal.set(Calendar.MILLISECOND, 0);
469         mScheduleInfo.days = new int[] {getTodayDay()};
470         mScheduleInfo.startHour = 22;
471         mScheduleInfo.endHour = 3;
472         mScheduleInfo.startMinute = 15;
473         mScheduleInfo.endMinute = 15;
474         mScheduleCalendar.setSchedule(mScheduleInfo);
475 
476         assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis()));
477     }
478 
479     @Test
480     @FlakyTest
testIsInSchedule_inScheduleSingleDay()481     public void testIsInSchedule_inScheduleSingleDay() {
482         Calendar cal = new GregorianCalendar();
483         cal.set(Calendar.HOUR_OF_DAY, 14);
484         cal.set(Calendar.MINUTE, 15);
485         cal.set(Calendar.SECOND, 0);
486         cal.set(Calendar.MILLISECOND, 0);
487         mScheduleInfo.days = new int[] {getTodayDay()};
488         mScheduleInfo.startHour = 12;
489         mScheduleInfo.endHour = 3;
490         mScheduleInfo.startMinute = 16;
491         mScheduleInfo.endMinute = 15;
492         mScheduleCalendar.setSchedule(mScheduleInfo);
493 
494         assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis()));
495     }
496 
497     @Test
testIsInSchedule_notToday()498     public void testIsInSchedule_notToday() {
499         Calendar cal = new GregorianCalendar();
500         cal.set(Calendar.HOUR_OF_DAY, 14);
501         cal.set(Calendar.MINUTE, 15);
502         cal.set(Calendar.SECOND, 0);
503         cal.set(Calendar.MILLISECOND, 0);
504         cal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
505         mScheduleInfo.days = new int[] {Calendar.FRIDAY, Calendar.SUNDAY};
506         mScheduleInfo.startHour = 12;
507         mScheduleInfo.startMinute = 16;
508         mScheduleInfo.endHour = 15;
509         mScheduleInfo.endMinute = 15;
510         mScheduleCalendar.setSchedule(mScheduleInfo);
511 
512         assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis()));
513     }
514 
515     @Test
testIsInSchedule_startingSoon()516     public void testIsInSchedule_startingSoon() {
517         Calendar cal = new GregorianCalendar();
518         cal.set(Calendar.HOUR_OF_DAY, 14);
519         cal.set(Calendar.MINUTE, 15);
520         cal.set(Calendar.SECOND, 59);
521         cal.set(Calendar.MILLISECOND, 0);
522         mScheduleInfo.days = new int[] {getTodayDay()};
523         mScheduleInfo.startHour = 14;
524         mScheduleInfo.endHour = 3;
525         mScheduleInfo.startMinute = 16;
526         mScheduleInfo.endMinute = 15;
527         mScheduleCalendar.setSchedule(mScheduleInfo);
528 
529         assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis()));
530     }
531 
532     @Test
testIsInSchedule_daylightSavingsForward_startDuringChange()533     public void testIsInSchedule_daylightSavingsForward_startDuringChange() {
534         // Test that if the start time of a ScheduleCalendar is during the nonexistent
535         // hour of daylight savings forward time, the evaluation of whether a time is in the
536         // schedule still works.
537 
538         // Set timezone to make sure we're evaluating the correct days.
539         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
540 
541         // Set up schedule for 2:30AM - 4:00AM.
542         final Calendar dstYesterday = getDaylightSavingsForwardDay();
543         final Calendar dstToday = getDaylightSavingsForwardDay();
544         dstToday.add(Calendar.DATE, 1);
545         mScheduleInfo.days = new int[] {dstYesterday.get(Calendar.DAY_OF_WEEK),
546                 dstToday.get(Calendar.DAY_OF_WEEK)};
547         mScheduleInfo.startHour = 2;
548         mScheduleInfo.startMinute = 30;
549         mScheduleInfo.endHour = 4;
550         mScheduleCalendar.setSchedule(mScheduleInfo);
551 
552         // Test cases: there are 2 "on" periods. These cover: before the first schedule
553         // (1AM previous day), during the first schedule (2:30AM), two between the two schedules
554         // (one on each calendar day), during the second (3:30AM), and after the second (4:30AM)
555         Calendar out1 = getDaylightSavingsForwardDay();
556         out1.set(Calendar.HOUR_OF_DAY, 1);
557         out1.set(Calendar.MINUTE, 00);
558         out1.set(Calendar.SECOND, 0);
559         out1.set(Calendar.MILLISECOND, 0);
560 
561         Calendar in1 = getDaylightSavingsForwardDay();
562         in1.set(Calendar.HOUR_OF_DAY, 2);
563         in1.set(Calendar.MINUTE, 45);
564         in1.set(Calendar.SECOND, 0);
565         in1.set(Calendar.MILLISECOND, 0);
566 
567         Calendar midOut1 = getDaylightSavingsForwardDay();
568         midOut1.set(Calendar.HOUR_OF_DAY, 7);
569         midOut1.set(Calendar.MINUTE, 30);
570         midOut1.set(Calendar.SECOND, 0);
571         midOut1.set(Calendar.MILLISECOND, 0);
572 
573         Calendar midOut2 = getDaylightSavingsForwardDay();
574         midOut2.add(Calendar.DATE, 1);
575         midOut2.set(Calendar.HOUR_OF_DAY, 1);
576         midOut2.set(Calendar.MINUTE, 30);
577         midOut2.set(Calendar.SECOND, 0);
578         midOut2.set(Calendar.MILLISECOND, 0);
579 
580         // Question: should 3:15AM be in the 2:30-4 schedule on a day when 2:30-3 doesn't exist?
581         Calendar in2 = getDaylightSavingsForwardDay();
582         in2.add(Calendar.DATE, 1);
583         in2.set(Calendar.HOUR_OF_DAY, 3);
584         in2.set(Calendar.MINUTE, 30);
585         in2.set(Calendar.SECOND, 0);
586         in2.set(Calendar.MILLISECOND, 0);
587 
588         Calendar out2 = getDaylightSavingsForwardDay();
589         out2.add(Calendar.DATE, 1);
590         out2.set(Calendar.HOUR_OF_DAY, 4);
591         out2.set(Calendar.MINUTE, 30);
592         out2.set(Calendar.SECOND, 0);
593         out2.set(Calendar.MILLISECOND, 0);
594 
595         assertFalse(mScheduleCalendar.isInSchedule(out1.getTimeInMillis()));
596         assertTrue(mScheduleCalendar.isInSchedule(in1.getTimeInMillis()));
597         assertFalse(mScheduleCalendar.isInSchedule(midOut1.getTimeInMillis()));
598         assertFalse(mScheduleCalendar.isInSchedule(midOut2.getTimeInMillis()));
599         assertTrue(mScheduleCalendar.isInSchedule(in2.getTimeInMillis()));
600         assertFalse(mScheduleCalendar.isInSchedule(out2.getTimeInMillis()));
601     }
602 
603     @Test
testIsInSchedule_daylightSavingsForward_endDuringChange()604     public void testIsInSchedule_daylightSavingsForward_endDuringChange() {
605         // Test that if the end time of a ScheduleCalendar is during the nonexistent
606         // hour of daylight savings forward time, the evaluation of whether a time is in the
607         // schedule still works.
608 
609         // Set timezone to make sure we're evaluating the correct days.
610         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
611 
612         // Set up schedule for 11:00PM - 2:30AM. On the day when 2AM doesn't exist, this should
613         // effectively finish at 3:30AM(?)
614         final Calendar dstYesterday = getDaylightSavingsForwardDay();
615         final Calendar dstToday = getDaylightSavingsForwardDay();
616         dstToday.add(Calendar.DATE, 1);
617         mScheduleInfo.days = new int[] {dstYesterday.get(Calendar.DAY_OF_WEEK),
618                 dstToday.get(Calendar.DAY_OF_WEEK)};
619         mScheduleInfo.startHour = 23;
620         mScheduleInfo.endHour = 2;
621         mScheduleInfo.endMinute = 30;
622         mScheduleCalendar.setSchedule(mScheduleInfo);
623 
624         // Test cases: before the time period on the previous day; during the time period when
625         // the calendar day is still the previous day; during the time period when the calendar
626         // day is the change day; afterwards.
627         Calendar out1 = getDaylightSavingsForwardDay();
628         out1.set(Calendar.HOUR_OF_DAY, 22);
629         out1.set(Calendar.MINUTE, 00);
630         out1.set(Calendar.SECOND, 0);
631         out1.set(Calendar.MILLISECOND, 0);
632 
633         Calendar in1 = getDaylightSavingsForwardDay();
634         in1.set(Calendar.HOUR_OF_DAY, 23);
635         in1.set(Calendar.MINUTE, 30);
636         in1.set(Calendar.SECOND, 0);
637         in1.set(Calendar.MILLISECOND, 0);
638 
639         Calendar in2 = getDaylightSavingsForwardDay();
640         in2.add(Calendar.DATE, 1);
641         in2.set(Calendar.HOUR_OF_DAY, 1);
642         in2.set(Calendar.MINUTE, 30);
643         in2.set(Calendar.SECOND, 0);
644         in2.set(Calendar.MILLISECOND, 0);
645 
646         // Question: Should 3:15AM be out of the schedule on a day when 2-3 doesn't exist?
647         Calendar out2 = getDaylightSavingsForwardDay();
648         out2.add(Calendar.DATE, 1);
649         out2.set(Calendar.HOUR_OF_DAY, 3);
650         out2.set(Calendar.MINUTE, 45);
651         out2.set(Calendar.SECOND, 0);
652         out2.set(Calendar.MILLISECOND, 0);
653 
654         assertFalse(mScheduleCalendar.isInSchedule(out1.getTimeInMillis()));
655         assertTrue(mScheduleCalendar.isInSchedule(in1.getTimeInMillis()));
656         assertTrue(mScheduleCalendar.isInSchedule(in2.getTimeInMillis()));
657         assertFalse(mScheduleCalendar.isInSchedule(out2.getTimeInMillis()));
658     }
659 
660     @Test
testIsInSchedule_daylightSavingsBackward_startDuringChange()661     public void testIsInSchedule_daylightSavingsBackward_startDuringChange() {
662         // Test that if the start time of a ScheduleCalendar is during the duplicated
663         // hour of daylight savings backward time, the evaluation of whether a time is in the
664         // schedule still works. It's not clear what correct behavior is during the duplicated
665         // 1:00->1:59->1:00->1:59 time period, but times outside that should still work.
666 
667         // Set timezone to make sure we're evaluating the correct days.
668         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
669 
670         // Set up schedule for 1:15AM - 4:00AM.
671         final Calendar dstYesterday = getDaylightSavingsBackwardDay();
672         final Calendar dstToday = getDaylightSavingsBackwardDay();
673         dstToday.add(Calendar.DATE, 1);
674         mScheduleInfo.days = new int[] {dstYesterday.get(Calendar.DAY_OF_WEEK),
675                 dstToday.get(Calendar.DAY_OF_WEEK)};
676         mScheduleInfo.startHour = 1;
677         mScheduleInfo.startMinute = 15;
678         mScheduleInfo.endHour = 4;
679         mScheduleCalendar.setSchedule(mScheduleInfo);
680 
681         // Test cases: there are 2 "on" periods. These cover: before the first schedule
682         // (1AM previous day), during the first schedule (2:30AM), two between the two schedules
683         // (one on each calendar day), during the second (2:30AM), and after the second (4:30AM)
684         Calendar out1 = getDaylightSavingsBackwardDay();
685         out1.set(Calendar.HOUR_OF_DAY, 1);
686         out1.set(Calendar.MINUTE, 00);
687         out1.set(Calendar.SECOND, 0);
688         out1.set(Calendar.MILLISECOND, 0);
689 
690         Calendar in1 = getDaylightSavingsBackwardDay();
691         in1.set(Calendar.HOUR_OF_DAY, 2);
692         in1.set(Calendar.MINUTE, 30);
693         in1.set(Calendar.SECOND, 0);
694         in1.set(Calendar.MILLISECOND, 0);
695 
696         Calendar midOut1 = getDaylightSavingsBackwardDay();
697         midOut1.set(Calendar.HOUR_OF_DAY, 7);
698         midOut1.set(Calendar.MINUTE, 30);
699         midOut1.set(Calendar.SECOND, 0);
700         midOut1.set(Calendar.MILLISECOND, 0);
701 
702         Calendar midOut2 = getDaylightSavingsBackwardDay();
703         midOut2.add(Calendar.DATE, 1);
704         midOut2.set(Calendar.HOUR_OF_DAY, 0);
705         midOut2.set(Calendar.MINUTE, 30);
706         midOut2.set(Calendar.SECOND, 0);
707         midOut2.set(Calendar.MILLISECOND, 0);
708 
709         Calendar in2 = getDaylightSavingsBackwardDay();
710         in2.add(Calendar.DATE, 1);
711         in2.set(Calendar.HOUR_OF_DAY, 2);
712         in2.set(Calendar.MINUTE, 30);
713         in2.set(Calendar.SECOND, 0);
714         in2.set(Calendar.MILLISECOND, 0);
715 
716         Calendar out2 = getDaylightSavingsBackwardDay();
717         out2.add(Calendar.DATE, 1);
718         out2.set(Calendar.HOUR_OF_DAY, 4);
719         out2.set(Calendar.MINUTE, 30);
720         out2.set(Calendar.SECOND, 0);
721         out2.set(Calendar.MILLISECOND, 0);
722 
723         assertFalse(mScheduleCalendar.isInSchedule(out1.getTimeInMillis()));
724         assertTrue(mScheduleCalendar.isInSchedule(in1.getTimeInMillis()));
725         assertFalse(mScheduleCalendar.isInSchedule(midOut1.getTimeInMillis()));
726         assertFalse(mScheduleCalendar.isInSchedule(midOut2.getTimeInMillis()));
727         assertTrue(mScheduleCalendar.isInSchedule(in2.getTimeInMillis()));
728         assertFalse(mScheduleCalendar.isInSchedule(out2.getTimeInMillis()));
729     }
730 
731     @Test
testIsInSchedule_daylightSavings_flippedSchedule()732     public void testIsInSchedule_daylightSavings_flippedSchedule() {
733         // This test is for the unlikely edge case where the skipped hour due to daylight savings
734         // causes the evaluated start time to be "later" than the schedule's end time on that day,
735         // for instance if the schedule is 2:30AM-3:15AM; 2:30AM may evaluate to 3:30AM on the day
736         // of daylight change.
737         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
738 
739         // Set up schedule for 2:30AM - 3:15AM.
740         final Calendar dstYesterday = getDaylightSavingsForwardDay();
741         final Calendar dstToday = getDaylightSavingsForwardDay();
742         dstToday.add(Calendar.DATE, 1);
743         mScheduleInfo.days = new int[] {dstYesterday.get(Calendar.DAY_OF_WEEK),
744                 dstToday.get(Calendar.DAY_OF_WEEK)};
745         mScheduleInfo.startHour = 2;
746         mScheduleInfo.startMinute = 30;
747         mScheduleInfo.endHour = 3;
748         mScheduleInfo.endMinute = 15;
749         mScheduleCalendar.setSchedule(mScheduleInfo);
750 
751         // It may not be well-defined what times around the 2-3AM range one might expect to be
752         // included or not included on the weird day when 2AM doesn't exist, but other unrelated
753         // times of day (here, 3PM) should definitely be out.
754         Calendar out1 = getDaylightSavingsForwardDay();
755         out1.set(Calendar.HOUR_OF_DAY, 15);
756         out1.set(Calendar.MINUTE, 0);
757         out1.set(Calendar.SECOND, 0);
758         out1.set(Calendar.MILLISECOND, 0);
759 
760         Calendar out2 = getDaylightSavingsForwardDay();
761         out2.add(Calendar.DATE, 1);
762         out2.set(Calendar.HOUR_OF_DAY, 15);
763         out2.set(Calendar.MINUTE, 0);
764         out2.set(Calendar.SECOND, 0);
765         out2.set(Calendar.MILLISECOND, 0);
766 
767         assertFalse(mScheduleCalendar.isInSchedule(out1.getTimeInMillis()));
768         assertFalse(mScheduleCalendar.isInSchedule(out2.getTimeInMillis()));
769     }
770 
771     @Test
testIsAlarmInSchedule_alarmAndNowInSchedule_sameScheduleTrigger_daylightSavings()772     public void testIsAlarmInSchedule_alarmAndNowInSchedule_sameScheduleTrigger_daylightSavings() {
773         // Need to set the time zone explicitly to a US one so that the daylight savings time day is
774         // correct.
775         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
776         Calendar alarm = getDaylightSavingsForwardDay();
777         alarm.set(Calendar.HOUR_OF_DAY, 23);
778         alarm.set(Calendar.MINUTE, 15);
779         alarm.set(Calendar.SECOND, 0);
780         alarm.set(Calendar.MILLISECOND, 0);
781 
782         Calendar now = getDaylightSavingsForwardDay();
783         now.set(Calendar.HOUR_OF_DAY, 2);
784         now.set(Calendar.MINUTE, 10);
785         now.set(Calendar.SECOND, 0);
786         now.set(Calendar.MILLISECOND, 0);
787         now.add(Calendar.DATE, 1); // add a day, on daylight savings this becomes 3:10am
788 
789         final Calendar tempToday = getDaylightSavingsForwardDay();
790         final Calendar tempTomorrow = getDaylightSavingsForwardDay();
791         tempTomorrow.add(Calendar.DATE, 1);
792         mScheduleInfo.days = new int[] {tempToday.get(Calendar.DAY_OF_WEEK),
793                 tempTomorrow.get(Calendar.DAY_OF_WEEK)};
794 
795         mScheduleInfo.startHour = 22;
796         mScheduleInfo.startMinute = 15;
797         mScheduleInfo.endHour = 3;
798         mScheduleInfo.endMinute = 15;
799         mScheduleCalendar.setSchedule(mScheduleInfo);
800 
801         assertTrue(mScheduleCalendar.isInSchedule(alarm.getTimeInMillis()));
802         assertTrue(mScheduleCalendar.isInSchedule(now.getTimeInMillis()));
803         assertTrue(mScheduleCalendar.isAlarmInSchedule(alarm.getTimeInMillis(),
804                 now.getTimeInMillis()));
805     }
806 
807     @Test
testIsAlarmInSchedule_alarmAndNowInSchedule_sameScheduleTrigger()808     public void testIsAlarmInSchedule_alarmAndNowInSchedule_sameScheduleTrigger() {
809         Calendar alarm = new GregorianCalendar();
810         alarm.set(Calendar.HOUR_OF_DAY, 23);
811         alarm.set(Calendar.MINUTE, 15);
812         alarm.set(Calendar.SECOND, 0);
813         alarm.set(Calendar.MILLISECOND, 0);
814 
815         Calendar now = new GregorianCalendar();
816         now.set(Calendar.HOUR_OF_DAY, 2);
817         now.set(Calendar.MINUTE, 10);
818         now.set(Calendar.SECOND, 0);
819         now.set(Calendar.MILLISECOND, 0);
820         now.add(Calendar.DATE, 1); // add a day, on daylight savings this becomes 3:10am
821 
822         mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
823         mScheduleInfo.startHour = 22;
824         mScheduleInfo.startMinute = 15;
825         mScheduleInfo.endHour = 3;
826         mScheduleInfo.endMinute = 15;
827         mScheduleCalendar.setSchedule(mScheduleInfo);
828 
829         assertTrue(mScheduleCalendar.isInSchedule(alarm.getTimeInMillis()));
830         assertTrue(mScheduleCalendar.isInSchedule(now.getTimeInMillis()));
831         assertTrue(mScheduleCalendar.isAlarmInSchedule(alarm.getTimeInMillis(),
832                 now.getTimeInMillis()));
833     }
834 
835     @Test
testIsAlarmInSchedule_alarmAndNowInSchedule_differentScheduleTrigger()836     public void testIsAlarmInSchedule_alarmAndNowInSchedule_differentScheduleTrigger() {
837         Calendar alarm = new GregorianCalendar();
838         alarm.set(Calendar.HOUR_OF_DAY, 23);
839         alarm.set(Calendar.MINUTE, 15);
840         alarm.set(Calendar.SECOND, 0);
841         alarm.set(Calendar.MILLISECOND, 0);
842 
843         Calendar now = new GregorianCalendar();
844         now.set(Calendar.HOUR_OF_DAY, 23);
845         now.set(Calendar.MINUTE, 15);
846         now.set(Calendar.SECOND, 0);
847         now.set(Calendar.MILLISECOND, 0);
848         now.add(Calendar.DATE, 1); // add a day
849 
850         mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
851         mScheduleInfo.startHour = 22;
852         mScheduleInfo.startMinute = 15;
853         mScheduleInfo.endHour = 3;
854         mScheduleInfo.endMinute = 15;
855         mScheduleCalendar.setSchedule(mScheduleInfo);
856 
857         // even though both alarm and now are in schedule, they are not in the same part of
858         // the schedule (alarm is in schedule for the previous day's schedule compared to now)
859         assertTrue(mScheduleCalendar.isInSchedule(alarm.getTimeInMillis()));
860         assertTrue(mScheduleCalendar.isInSchedule(now.getTimeInMillis()));
861         assertFalse(mScheduleCalendar.isAlarmInSchedule(alarm.getTimeInMillis(),
862                 now.getTimeInMillis()));
863     }
864 
865     @Test
testClosestActualTime_regularTimesAndSkippedTime()866     public void testClosestActualTime_regularTimesAndSkippedTime() {
867         // Make sure we're operating in the relevant time zone for the assumed Daylight Savings day
868         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
869         Calendar day = getDaylightSavingsForwardDay();
870         day.set(Calendar.HOUR_OF_DAY, 15);
871         day.set(Calendar.MINUTE, 25);
872         day.set(Calendar.SECOND, 0);
873         day.set(Calendar.MILLISECOND, 0);
874         assertEquals(day.getTimeInMillis(),
875                 mScheduleCalendar.getClosestActualTime(day.getTimeInMillis(), 15, 25));
876 
877         // Check a skipped time
878         day.add(Calendar.DATE, 1);
879         day.set(Calendar.HOUR_OF_DAY, 3);
880         day.set(Calendar.MINUTE, 0);
881         day.set(Calendar.SECOND, 0);
882         day.set(Calendar.MILLISECOND, 0);
883         assertEquals(day.getTimeInMillis(),
884                 mScheduleCalendar.getClosestActualTime(day.getTimeInMillis(), 2, 15));
885 
886         // Check a non-skipped time after the clocks have moved forward
887         day.set(Calendar.HOUR_OF_DAY, 15);
888         day.set(Calendar.MINUTE, 25);
889         day.set(Calendar.SECOND, 0);
890         day.set(Calendar.MILLISECOND, 0);
891         assertEquals(day.getTimeInMillis(),
892                 mScheduleCalendar.getClosestActualTime(day.getTimeInMillis(), 15, 25));
893     }
894 
895     @Test
testClosestActualTime_otherTimeZones()896     public void testClosestActualTime_otherTimeZones() {
897         // Make sure this doesn't only work for US/Eastern time.
898         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("Europe/London"));
899         Calendar ukDstDay = new GregorianCalendar(TimeZone.getTimeZone("Europe/London"));
900         ukDstDay.set(2021, Calendar.MARCH, 28);
901 
902         // Check a skipped time, which is 01:xx on that day in the UK
903         ukDstDay.set(Calendar.HOUR_OF_DAY, 2);
904         ukDstDay.set(Calendar.MINUTE, 0);
905         ukDstDay.set(Calendar.SECOND, 0);
906         ukDstDay.set(Calendar.MILLISECOND, 0);
907         assertEquals(ukDstDay.getTimeInMillis(),
908                 mScheduleCalendar.getClosestActualTime(ukDstDay.getTimeInMillis(), 1, 25));
909 
910         // Check a non-skipped time
911         ukDstDay.set(Calendar.HOUR_OF_DAY, 11);
912         ukDstDay.set(Calendar.MINUTE, 23);
913         ukDstDay.set(Calendar.SECOND, 0);
914         ukDstDay.set(Calendar.MILLISECOND, 0);
915         assertEquals(ukDstDay.getTimeInMillis(),
916                 mScheduleCalendar.getClosestActualTime(ukDstDay.getTimeInMillis(), 11, 23));
917 
918         mScheduleCalendar.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
919         Calendar frDstDay = new GregorianCalendar(TimeZone.getTimeZone("Europe/Paris"));
920         frDstDay.set(2021, Calendar.MARCH, 28);
921 
922         // Check a skipped time, which is 02:xx on that day in France
923         frDstDay.set(Calendar.HOUR_OF_DAY, 3);
924         frDstDay.set(Calendar.MINUTE, 0);
925         frDstDay.set(Calendar.SECOND, 0);
926         frDstDay.set(Calendar.MILLISECOND, 0);
927         assertEquals(frDstDay.getTimeInMillis(),
928                 mScheduleCalendar.getClosestActualTime(frDstDay.getTimeInMillis(), 2, 25));
929 
930         // Check a regular time
931         frDstDay.set(Calendar.HOUR_OF_DAY, 14);
932         frDstDay.set(Calendar.MINUTE, 59);
933         frDstDay.set(Calendar.SECOND, 0);
934         frDstDay.set(Calendar.MILLISECOND, 0);
935         assertEquals(frDstDay.getTimeInMillis(),
936                 mScheduleCalendar.getClosestActualTime(frDstDay.getTimeInMillis(), 14, 59));
937     }
938 
getTodayDay()939     private int getTodayDay() {
940         return new GregorianCalendar().get(Calendar.DAY_OF_WEEK);
941     }
942 
getTodayDay(int offset)943     private int getTodayDay(int offset) {
944         Calendar cal = new GregorianCalendar();
945         cal.add(Calendar.DATE, offset);
946         return cal.get(Calendar.DAY_OF_WEEK);
947     }
948 
949 
getDaylightSavingsForwardDay()950     private Calendar getDaylightSavingsForwardDay() {
951         // the day before daylight savings rolls forward in the US - March 9, 2019
952         // 2AM March 10, 2019 does not exist -- goes straight from 1:59 to 3:00
953         // Specifically set to US/Eastern time zone rather than relying on a default time zone
954         // to make sure the date is the correct one, since DST changes vary by region.
955         Calendar daylightSavingsDay = new GregorianCalendar(
956                 TimeZone.getTimeZone("America/New_York"));
957         daylightSavingsDay.set(2019, Calendar.MARCH, 9);
958         return daylightSavingsDay;
959     }
960 
getDaylightSavingsBackwardDay()961     private Calendar getDaylightSavingsBackwardDay() {
962         // the day before daylight savings rolls backward in the US - November 2, 2019
963         // In this instance, 1AM November 3 2019 is repeated twice; 1:00->1:59->1:00->1:59->2:00
964         Calendar daylightSavingsDay = new GregorianCalendar(
965                 TimeZone.getTimeZone("America/New_York"));
966         daylightSavingsDay.set(2019, Calendar.NOVEMBER, 2);
967         return daylightSavingsDay;
968     }
969 }
970