如何在Java中安排任务在午夜发生,即使刚刚发生了闰秒



首先,"发生在午夜"的定义是,当任务运行时,当转换为人类可读格式时,new DateTime()或类似内容将在该时间段显示00:00:00或更晚。重要的一点是,它不能显示前一天的23:59:59。

实现这一点的一种常见方法是计算从现在到所需时间点之间的毫秒数,然后使用ScheduledExecutorService在正确的时间执行任务。然而,当插入闰秒时,这将导致任务提前一秒运行(或提前几毫秒运行,具体取决于闰秒的"涂抹"方式和您安排任务的时间):

Runnable task = ...
long numberOfMillisUntilMidnight = ...
ScheduledExecutorService executor = ...
// task runs too early when leap seconds are inserted
executor.schedule(task, numberOfMillisUntilMidnight, TimeUnit.MILLISECONDS);

原因是executor.schedule()是以System.nanoTime()为基础的,明显忽略了闰秒。我想我需要的是一些基于"在这个时候运行"而不是"在这个时间之后运行"的调度器。

对于那些感兴趣的人来说,任务必须在午夜运行的原因与以下事实有关:我的系统中的所有事件都必须根据它们发生的日期进行分类,并且尽可能地,这需要与另一个系统同步。当然,如果另一个系统在每个事件上标记当天,那会更好,但我们不在那里。

我想我需要的是一些基于"此时运行"而不是"在这段时间后运行"的调度器

这将是一个全唱全跳的解决方案。但是:

首先,"发生在午夜"的定义是,当任务运行时,新的DateTime()或类似内容将在时间部分显示00:00:00或更晚。。。重要的一点是,它不能显示前一天的23:59:59。

(我的重点)

简单的方法总是加一秒钟,甚至两秒钟。因此,在常见情况下为00:00:01(00:00:00或更晚),在闰秒情况下为0:000,而不是23:59:59。

根据由此产生的讨论,很明显,如果"墙时间"对你来说很重要,那么一般来说,依靠你的调度程序在"正确"的时间运行任务是不明智的。当在夏令时轮班的同一"墙时间"运行日常任务时也是如此,尽管与闰秒情况不同,夏令时情况得到了现有工具(例如Quartz)的良好支持。

相反,我认为对于这种"墙时间敏感"的进程,最好的方法是在运行任务时检查此时的系统时钟。如果您的时间表因任何原因不准确(闰秒并不是系统时钟相对于System.nanoTime()测量的运行时间进行调整的唯一时间),并且尚未达到该时间,则不采取任何措施,将任务重新安排到正确的时间。这种方法也适用于响应夏令时变化的时间表,但如上所述,这已经得到了通用工具的支持。

这种方法的灵感来自乔纳森·莱因哈特的评论。不过,重新安排而不是睡觉似乎更好。

假设您的具体ScheduledExecutorService实现依赖于System.nanoTime()(正如您所说),并考虑到您的需求/配置,方法调度的初始延迟参数(…)计算到下一个午夜之前经过的毫秒,包括可能的闰秒,

我建议您使用闰秒感知解决方案。使用我的库Time4J的一个示例显示了如何计算延迟参数:

Moment now = SystemClock.currentMoment(); // should not be called during a leap second
Moment nextMidnight =
now.with(
PlainTime.COMPONENT.setToNext(PlainTime.midnightAtStartOfDay()).in(
Timezone.ofSystem().with(
GapResolver.NEXT_VALID_TIME.and(OverlapResolver.EARLIER_OFFSET)
)
)
);
long delayInMilliseconds = SI.NANOSECONDS.between(now, nextMidnight) / 1_000_000;

如果夏令时发生变化,此代码还将选择午夜后最早的有效本地时间(标准Java宁愿将时间提前一段时间,可能会导致本地时间晚于第一个有效时间)。对于大多数区域,只有选择午夜后的任意开始时间(取决于业务需求),这才是相关的。

无论如何,您还应该关心将系统连接到同一NTP时钟。您可以依赖OS NTP配置,也可以使用基于Java的时钟连接到NTP(Time4J在这里也提供了一个解决方案)。

如果您选择的时钟只是任意跳跃(即,如果有人手动调整了它,或者NTP配置不好),那么在再次检查本地walltime后重新安排任务可能更安全。然而,我仍然认为通过上面的Time4J代码计算延迟参数是个好主意,因为匹配午夜的机会比仅仅运行任务并重新检查本地时间的机会更高。

您也可以将这两种方法结合起来:精确计算延迟和检查/重新安排。

相关内容

  • 没有找到相关文章

最新更新