【问题标题】:How can a task be scheduled in Java to occur at midnight, even if a leap second has just occurred?即使闰秒刚刚发生,如何在 Java 中将任务安排在午夜发生?
【发布时间】:2017-01-04 10:32:04
【问题描述】:

首先,“发生在午夜”的定义是当任务运行时,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() 显然忽略了闰秒。我想我需要的是一些基于“此时运行”而不是“在这段时间后运行”的调度程序。

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

【问题讨论】:

  • 以一种方式或另一种方式运行第二次真的很重要吗?
  • 关于它是否真的很重要,从业务角度来看,是的,但从技术角度来看,不是,因为我可以编写代码说“如果还不是午夜,只需重新安排任务即可执行它”。但这似乎不优雅。关于 Crowder 的建议,这是可行的,但它似乎又不优雅,如果我的应用程序有事件发生在 00:00:00 和 00:00:01 之间,那么这些事件将被错误地报告为发生在错误的日期,这将导致 24 小时内 2 个系统之间出现协调问题,必须手动查看。
  • 任务可以在运行时检查小时是否为 0,否则休眠一秒钟。
  • 但是基本要求是什么,它必须/应该/应该是午夜的真正原因是什么?请提供更多背景信息。
  • 你为什么说System.nanoTime() 忽略闰秒?我认为恰恰相反,因为它只计算经过的秒数(因此也是经过的 SI 秒)。另一件事是你如何显示它。这里 Java-8 不支持闰秒,而是盲目地依赖操作系统时钟(通常忽略闰秒)。顺便说一句:表达式System.currentTimeMillis()System.nanoTime() 更容易受到时钟变化的影响。

标签: java time leap-second


【解决方案1】:

我想我需要的是一些基于“此时运行”而不是“在这段时间后运行”的调度程序

这将是全唱全能的解决方案。但是:

首先,“发生在午夜”的定义是,当任务运行时,new DateTime() 或类似的将显示时间部分的 00:00:00 或稍后...重点是它不能显示前一天的 23:59:59。

(我的重点)

简单的方法总是添加第二个,甚至两个。所以通常情况下是 00:00:01 (00:00:00 或更晚),而 00:00:00 (不是 23:59:59) em> 在闰秒情况下。

【讨论】:

  • 这可行(ish)但它不优雅,如果我的应用程序有事件发生在 00:00:00 和 00:00:01 之间,那么这些事件将被错误地报告为发生在错误的日期.不是世界末日,但我觉得我们可以做得更好!同意认为“全部唱歌,全部跳舞”的方法可能不是正确的方法(因为似乎没有图书馆这样做,所以我不得不自己动手)。
  • @tonicsoft:嗯,你确实说过“00:00:00 或更晚”。有了问题中给出的约束,在我看来,这 is 是一个优雅的解决方案。如果您希望它恰好在 00:00:00,那不是问题中给出的约束。
  • @tonicsoft 发生在 00:00:00 和 00:00:01 之间的时间不在前一天,至少不在 java.time-package 或更旧的 Java-Date-API 的上下文中。然后,它的所有格式化内容将永远不会显示前一天。当然,如果底层时钟遵循任何类型的涂抹算法,可能会在下一天完成涂抹,那么这是 Java 无法观察到的(因为这里闰秒对你来说是隐藏的)。因此,除非您尝试避免这种跳跃涂抹算法,否则您无能为力。尽量避免这样的时钟。
【解决方案2】:

根据由此产生的讨论,似乎很清楚,一般来说,如果“挂时”对您很重要,那么依靠您的调度程序在“正确”时间运行任务是不明智的。在夏令时期间以相同的“墙上时间”运行日常任务时也是如此,尽管与闰秒情况不同,现有工具(例如 Quartz)很好地支持夏令时情况。

相反,我认为这种“对时间敏感”的进程的最佳方法是在任务运行时检查系统时钟。如果您的计划由于某种原因不准确(闰秒不是您的系统时钟相对于System.nanoTime() 测量的经过时间进行调整的唯一时间)并且尚未达到时间,那么什么都不做并重新安排任务正确的时间。这种方法也适用于响应夏令时变化的时间表,但如上所述,这已经得到通用工具的支持。

这种方法的灵感来自上述 Jonathon Reinhart 的评论。重新安排而不是睡觉似乎更好。

【讨论】:

    【解决方案3】:

    假设您的具体 ScheduledExecutorService-implementation 依赖于 System.nanoTime()(如您所说)并考虑到您的要求/配置,方法 schedule(...) 的初始延迟参数计算直到下一个午夜经过的毫秒数,包括可能的闰秒,

    我建议您使用闰秒感知解决方案。使用我的库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 在这里也提供了solution)。

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

    您还可以结合使用这两种方法:精确计算延迟和检查/重新安排。

    【讨论】:

      猜你喜欢
      • 2010-11-15
      • 1970-01-01
      • 2012-10-22
      • 1970-01-01
      • 2011-04-25
      • 2012-12-19
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      相关资源
      最近更新 更多