环境:Amazon Linux AMI上的Java 7(已修补)。请注意,在此环境中使用NTP。

我们的一部分代码会定期执行计划的任务。通常,执行的确切时间并不是特别重要。但是,我们有类似的逻辑可用于计算报告间隔。预计报告期间将落在确切的小时界限内。

样例代码:

protected Calendar increment(ScheduledPeriodEnum scheduledPeriod, Calendar date) {
    Calendar next = CalendarUtils.getCalendar();
    next.setTimeInMillis(date.getTimeInMillis());
    switch (scheduledPeriod) {
    case ONE_DAY:
        next.add(Calendar.DAY_OF_MONTH, 1);
        break;
    case ONE_HOUR:
        next.add(Calendar.HOUR_OF_DAY, 1);
        break;
    case FIFTEEN_MINUTE:
        next.add(Calendar.MINUTE, 15);
        break;
    case ONE_MONTH:
        next.add(Calendar.MONTH, 1);
        break;
    default:
        throw new RuntimeException("Unhandled case: " + scheduledPeriod);
    }
    return next;
}


我们将时间保留为unix时间戳(长)值。 Calendar实例全部位于UTC时区。

我们的理解是Java 7的Calendar实现不会解决leap秒问题。我们还相信NTP将更新操作系统时钟。

我们知道亚马逊在2012年发生了第二次崩溃。我们正在与他们直接沟通以了解他们的操作准备。

具体问题:


如果我们是increment the秒前一个小时边界内的日历,它将在the秒之后一个小时边界内吗?还是一小时前一秒钟?
我们应该如何测试/验证此代码将如何跨越the秒边界运行? NIST被入侵的文件是否是不错的选择?
是否有一种更合适的模式/实施方式可以避开leap秒问题?

最佳答案

我不太担心2015年的下一个leap秒,并假设Linux团队在解决leap秒处理代码的任何问题方面做了很多工作,另请参见Linus Torvalds的有趣采访。如果linux软件再次出错,那么您只能做一些事情,而不仅仅是重新启动Java程序(这里Java只是后端)

现在,让我们考虑一下可能发生的其他情况。关于完全不知道java.util.Datejava.util.Calendar之类的leap秒的代码,请记住,此类代码仅查看操作系统时钟提供的功能。大多数操作系统仅提供UNIX时间戳。如果它们与NTP同步,则请记住NTP时间戳记不计算NTP协议中指定的leap秒。他们只是重复相同的时间戳。然后,Windows可能会在以后的任何时间触发时钟跳变,而Linux内核会尝试更精确,应用一些leap秒处理代码并立即处理系统时钟。无论如何,两个OS都只提供类似POSIX的时间戳。 Java只是什么都看不到。

这也回答了您的第一个特定问题:GregorianCalendar对象在添加一个小时后将保留在一个小时边界上。

关于第二个问题:

这里只是问题的一秒钟。但是,可能更麻烦的是,本地时钟即使在几分钟内也可能出错(然后在与NTP时钟同步后突然跳转)。后期行为可能随时发生,而不仅仅是在2015-06-30结束时。因此,我认为您真正的问题是时钟的单调性。这通常表明,任何测试都应使用可注入时钟机制。例如,您可以编写一个TimeSource接口,该接口通过方法public long currentTime()产生任何unix时间戳(甚至可以通过计时器创建模拟跳转),然后在JUnit-test类中使用此接口提供(伪)时间并观察代码的行为。

广告3。)大多数人只会使用不知道任何leap秒的库,因为POSIX易于理解和计算(尽管在这些特殊的秒上是不正确的)。如果没有leap秒处理代码,则几乎不可能直接在此类标准库中产生问题。否则,如果您不希望对用户隐藏leap秒,则也可以在此SO-post中咨询我的答案。

无论您做出什么决定,leap秒对于选择合适的库/实现而言实际上都不足够重要(其他主题,例如线程安全性,国际化等更为重要)。关于这些标准,旧的Calendar东西非常糟糕。

09-28 14:51