Android AlarmManager 通过时区或夏令时进行调度

Android AlarmManager scheduling through time zone or daylight shifts

我有一个使用 AlarmManager 安排提醒的逻辑。我需要执行以下操作:

逻辑1:当用户改变时区时,例如他从英国(UTC+0)到中欧(UTC​​+1)旅行,警报应该跟随时区. 例如,安排在下午 3 点 UTC+0 的提醒应该在下午 4 点 UTC+1

逻辑2:当时间偏移发生时,例如时间偏移到spring的夏令时(从UTC+1到UTC+2),告警应该保持原来的时间 例如,安排在下午 3 点 UTC+1 的提醒应该在下午 3 点 UTC+2

触发

我怎样才能做到这一点?截至目前,我没有特定的逻辑,所有警报都遵循 逻辑 1。我找不到确定时移何时发生的方法。

调度逻辑很简单:

LocalDateTime reminderTime = LocalDateTime.of(...)
ZoneOffset currentOffsetForMyZone = ZoneId.systemDefault().getRules().getOffset(Instant.now());
reminderTime.toInstant(currentOffsetForMyZone).toEpochMilli();
alarmManager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);

为每个闹钟存储一天中的时间和设置闹钟的时区。这足以在正确的时间触发警报,无论用户当前是否处于不同的时区。 Java 将考虑夏令时 (DST)。

您的示例时间和 UTC 偏移对应于那些时区的标准时间,所以让我们从标准时间的示例日期开始,即使现在是几天前:

    LocalTime alarmTime = LocalTime.of(15, 0);
    ZoneId alarmTimeZone = ZoneId.of("Europe/London");
    
    // Travel to Paris and see the alarm go off at 4, assuming standard time
    ZoneId currentTimeZone = ZoneId.of("Europe/Paris");
    Instant actualAlarmTime = LocalDate.of(2021, Month.MARCH, 18)
            .atTime(alarmTime)
            .atZone(alarmTimeZone)
            .toInstant();
    ZonedDateTime timeOnLocation = actualAlarmTime.atZone(currentTimeZone);
    System.out.format("Scheduled at %s or %d millis, goes off at %s local time%n",
            actualAlarmTime, actualAlarmTime.toEpochMilli(), timeOnLocation);

代码打印:

Scheduled at 2021-03-18T15:00:00Z or 1616079600000 millis, goes off at 2021-03-18T16:00+01:00[Europe/Paris] local time

让我们也尝试在一年中的夏令时约会。我已将 Paris 更改为 London 并将 MARCH 更改为 APRIL:

    // Stay back home in the UK
    ZoneId currentTimeZone = ZoneId.of("Europe/London");
    Instant actualAlarmTime = LocalDate.of(2021, Month.APRIL, 18)
            .atTime(alarmTime)
            .atZone(alarmTimeZone)
            .toInstant();

Scheduled at 2021-04-18T14:00:00Z or 1618754400000 millis, goes off at 2021-04-18T15:00+01:00[Europe/London] local time

基本技巧是:不要对设置闹钟的时区使用当前偏移量。让 Java 自动应用警报响起的日期和时间的偏移量。

如果有人感兴趣,修复方法是为警报发出的日期和时间应用正确的偏移量,正如 Ole 所指出的那样。我的愚蠢错误是始终应用​​ 当前 时区。

LocalDateTime alarmTime = LocalDateTime.of(...)
ZoneId zone = ZoneId.systemDefault();
ZonedDateTime zoneDateTime = ZonedDateTime.of(alarmTime , zone);
long startAtMillis = zoneDateTime.toInstant().toEpochMilli();
//Fire alarm
notificationAlarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, startAtMillis, pendingIntent);