【问题标题】:Calendar : don't allow past timestamp日历:不允许过去的时间戳
【发布时间】:2017-11-27 22:21:22
【问题描述】:

我正在安排重复闹钟,每周二早上 08:30 响铃。

因为打瞌睡,我不能使用AlarmManager.setRepeating,因为闹钟的时间不准确(必须准确,我了解对电池的影响)。

所以,我使用这段代码来安排第一个警报:

Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.setFirstDayOfWeek(Calendar.WEDNESDAY); // Trick not to get a "tuesday" timestamp in the past if today is wednesday+ !
calendar.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
long scheduledTime = calendar.getTimeInMillis();

AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
} else {
    alarmManager.set(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
}

而且它完美无瑕。

问题是,当我的BroadcastReceiver 在星期二上午 8:30 使用 Intent 时,我必须重新安排闹钟。

但是在星期二 08:30 和星期二 23:59 之间,该方法会给我一个“过去”的时间戳,这是不正确的。

除了“如果提供的时间戳是过去的,添加 1 周”之外,还有什么方法可以解决这个问题吗?

【问题讨论】:

  • 抱歉,我不明白为什么您在重新安排闹钟时无法计算下一个安排日期/时间。
  • 因为代码的第一部分(第 0 - 8 行)会给我一个现在之前的时间戳?
  • 比较if (calculated.before(now)) {...
  • @LewBloch 这就是我目前正在做的事情,但感觉如此不雅,我正在寻找一种更“通用”的方法。
  • “感觉”是一种情感。工程原理是什么?无论如何,您已经将calendar 的初始值设置为当前时间两次,按照工程标准,这是“不雅”的。如果您坚持使用老式的时间操作库,您可以考虑使用 docs.oracle.com/javase/8/docs/api/java/util/… 方法。

标签: java android datetime calendar


【解决方案1】:

我建议为此计算使用新的java.time API。 ThreeTenABP project 是专门针对 Android 对该库的改编。

以下是使用java.time API 获取每周二上午 08:30 的示例:

LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
now.with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
   .withHour(8)
   .withMinute(30)
   .withSecond(0));  

【讨论】:

    【解决方案2】:

    旧的类(DateCalendarSimpleDateFormat)有 lots of problemsdesign issues,它们正在被新的 API 取代。

    在 Android 中,您可以使用 ThreeTen Backport,这是 Java 8 新日期/时间类的一个很好的反向移植,以及 ThreeTenABP(更多关于如何使用它的信息 here)。

    虽然你也可以使用 Joda-Time,但它处于维护模式并被新的 API 取代,所以我不建议用它来启动新项目。即使在joda's website 中它说:“请注意,Joda-Time 被认为是一个基本上“完成”的项目。没有计划进行重大改进。如果使用 Java SE 8,请迁移到 java.time (JSR-310 )。”

    以下所有类都在包org.threeten.bp下。


    首先,我检查了AlarmManager 的文档,它似乎可以使用时间戳(set 方法中的long 参数),即1970-01-01T00:00Z 的毫秒数。

    所以,您需要一个ZonedDateTime(特定时区的日期和时间),因为您需要知道日期、时间和时区才能获得特定的时间戳毫秒:

    import org.threeten.bp.DayOfWeek;
    import org.threeten.bp.LocalTime;
    import org.threeten.bp.ZonedDateTime;
    import org.threeten.bp.temporal.TemporalAdjusters;
    
    // current date/time at system's default timezone
    ZonedDateTime now = ZonedDateTime.now();
    
    // get next Tuesday at 08:30 AM
    ZonedDateTime nextTuesday = now
        // get the next Tuesday
        .with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
        // at 08:30 AM
        .with(LocalTime.of(8, 30));
    System.out.println(nextTuesday);
    
    // get the millis timestamp
    long scheduledTime = nextTuesday.toInstant().toEpochMilli();
    

    TemporalAdjusters.next() 方法可以找到下周二。如果当前日期已经是星期二,而您不想得到下周的星期二,则可以将其替换为 TemporalAdjusters.nextOrSame()

    LocalTime.of(8, 30) 将小时设置为上午 08:30(它已将 毫秒 设置为零)。


    上面的代码使用系统的默认时区(可能是设备中配置的时区)获取当前日期/时间。如果你想要一个特定的时区,你可以使用org.threeten.bp.ZoneIdorg.threeten.bp.ZoneOffset 类:

    // current date/time at London
    ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Europe/London"));
    
    // current date/time in UTC
    ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
    

    请注意,API 使用IANA timezones names(始终采用Continent/City 格式,如America/Sao_PauloEurope/London)。 避免使用三个字母的缩写(如CSTPST),因为它们是ambiguous and not standard


    如果您已经拥有java.util.Calendar,您可以使用org.threeten.bp.DateTimeUtils 类与新API 相互转换:

    // convert calendar to ZonedDateTime, using system's default timezone
    ZonedDateTime z = DateTimeUtils.toInstant(calendar).atZone(ZoneId.systemDefault());
    
    // convert back to calendar
    Calendar c = DateTimeUtils.toGregorianCalendar(z);
    

    【讨论】:

      【解决方案3】:

      您可以使用此代码计算下周二上午 8:30。 它使用 JodaTime 库。

      在回答这个问题时,这是要添加到 build.gradle 的依赖项:compile 'joda-time:joda-time:2.9.9'

      public DateTime getNextTuesday830(DateTime now){
              DateTime rtn = now;
      
              int thisDay = now.getDayOfWeek();
              int deltaNexTuesday = -1;
      
              if(thisDay >= DateTimeConstants.TUESDAY){
                  deltaNexTuesday =  DateTimeConstants.TUESDAY + 7 - thisDay;
              }else{
                  deltaNexTuesday = DateTimeConstants.TUESDAY - thisDay;
              }
      
              rtn = rtn.plusDays(deltaNexTuesday).withHourOfDay(8).withMinuteOfHour(30).withSecondOfMinute(0);
      
              return rtn;
          }
      

      计算now参数:

      DateTime now = new DateTime(System.currentTimeMillis());
      

      从 DateTime 中取回 Date() 对象:

      Date d = now.toDate();
      

      【讨论】:

      • Joda-Time 项目现在处于维护模式,团队建议迁移到 java.time 类。请参阅 ThreeTen-BackportThreeTenABP 项目。
      • @BasilBourque Thx,我不知道。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多