【问题标题】:Duration with leap seconds闰秒持续时间
【发布时间】:2016-02-15 09:13:14
【问题描述】:

我需要在我的代码中安排一个固定日期时间的任务。为此,我使用了带有 schedule(Runnable command, long delay, TimeUnit unit); 方法的 ScheduledExecutorService

如何根据闰秒计算此延迟?

目前我使用Duration.between(),但它似乎不知道闰秒:

ZonedDateTime date1 = ZonedDateTime.of(2015, 06, 30, 23, 59, 59, 000000, ZoneOffset.UTC);
ZonedDateTime date2 = ZonedDateTime.of(2015, 07, 01, 00, 00, 00, 000000, ZoneOffset.UTC);
ZonedDateTime date3 = ZonedDateTime.of(2015, 07, 01, 00, 00, 01, 000000, ZoneOffset.UTC);
System.out.println(Duration.between(date1, date2)); // PT1S
System.out.println(Duration.between(date2, date3)); // PT1S

【问题讨论】:

  • 您的计算机时钟是否真的足够准确以至于这实际上很重要?
  • 另外:用前导零编写时间/日期组件并不是最好的主意。带前导零的 int 文字以八进制解释,因此您不能写 08、09 等,并且写 012345 与 12345 不同。
  • 闰秒是hidden from you 通过UTC-SLS 平滑。基本上,在闰秒附近,“Java 秒”比实际的 UTC 秒短一点或长一点。 Duration 以 Java 时间测量,因此您永远不会看到闰秒。
  • 一般不支持...因为我们不知道未来的所有闰秒...或遥远的过去。这是不可预测的。您可以编译和维护一个已知闰秒列表(截至目前只有 36 个)并自己添加。
  • @dhke 这里官方确认 JDK 不包含任何闰秒信息:github.com/threeten/threeten/issues/197 是的,由 IANA 托管的原始 tzdb 包含此类信息,但不包含 Oracle 或 OpenJDK 的分布当他们将 tzdb 提取/评估为自己的格式时。

标签: java datetime utc java-time leap-second


【解决方案1】:

要支持闰秒,您需要 ThreeTen-Extra 扩展 jar 文件。它为UTC and TAI 加上table of leap seconds 提供了专门的课程。

要计算持续时间,请使用专用的durationUntil() 方法。

【讨论】:

  • 这是一个有趣的答案。但是我已经用了threetenbp(项目是Java 7,不能在java8上迁移),而且Threetenbp-extra好像不活跃,不支持:(
【解决方案2】:

使用我的库 Time4J(v3.x 适用于 Java 7 - 或适用于 Android 的 Time4A)将为您提供完整的解决方案,包括对不同时区的格式化和解析的广泛支持。例子:

// Time4J - transformation from old world
Moment m1 = TemporalType.JAVA_UTIL_DATE.translate(d1);
Moment m2 = TemporalType.JAVA_UTIL_DATE.translate(d2);
System.out.println("Time4J - from j.u.Date: " + SI.SECONDS.between(m1, m2)); // 601

// Time4J - programmatical construction of timestamps (like in your example)
m1 = PlainTimestamp.of(2012, 6, 30, 23, 50, 0).atUTC();
m2 = PlainTimestamp.of(2012, 7, 1, 0, 0, 0).atUTC();
System.out.println("Time4J - from utc-timestamps: " + SI.SECONDS.between(m1, m2)); // 601

// Time4J - parsing zoned inputs
ChronoFormatter<Moment> cf =
    ChronoFormatter.ofMomentPattern(
      "d. MMM uuuu HH:mm:ss[XXX]", // optional offset
      PatternType.CLDR, 
      Locale.ENGLISH, 
      EUROPE.PARIS); // only used if the offset is missing in input
m1 = cf.parse("1. Jul 2015 01:50:00");
m2 = cf.parse("1. Jul 2015 09:00:00+09:00");
System.out.println("Time4J - from offsets or zones: " + SI.SECONDS.between(m1, m2)); // 601

// the leap second itself can also be parsed
Moment ls = cf.parse("1. Jul 2015 08:59:60+09:00"); // offset of Tokyo
System.out.println("leap second in 2015: " + ls);
// 2015-06-30T23:59:60Z

还有更多,即支持知道闰秒的时钟以及从 IANA-TZDB 传输闰秒数据。例子见我的article on DZone。请注意,Time4J 可以完全替代 ThreetenBP,但也可以与 Java-8 互操作(只需将版本更改为 v4.x),并提供更多超出此处问题范围的功能。


旁注:大多数人显然选择 ThreetenBP 是为了使将来更容易迁移到 Java-8(只需更改一些导入语句)。但你的问题是

  • Java 标准(任何版本)本身对闰秒一无所知(甚至不知道必要的数据)。

  • 建议的其他第 3 方库 Threeten-Extra 不适用于 Java-7。它需要 Java-8。

迁移到 Java-8 之后,Threeten-Extra 似乎提出了一个解决方案。但是,我现在已经完成了自己的测试,并建议不要使用该库,请参阅以下代码:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Date d1 = sdf.parse("2012-06-30T23:50:00Z");
Date d2 = sdf.parse("2012-07-01T00:00:00Z");
System.out.println(
  "Old world in Posix - ignorant of leap seconds: " 
  + (d2.getTime() - d1.getTime()) / 1000);
// 600 (OK, SI-seconds without counting leap second, a limitation of POSIX)

Instant instant1 = d1.toInstant();
Instant instant2 = d2.toInstant();
System.out.println(
  "Java 8 without Threeten-Extra: " 
  + (instant2.getEpochSecond() - instant1.getEpochSecond()));
// 600 (obviously using more or less fuzzy "rubber seconds", not SI-seconds)
// internally a simple 1:1-mapping of POSIX is applied within 'toInstant()'

UtcInstant utc1 = UtcInstant.of(instant1);
UtcInstant utc2 = UtcInstant.of(instant2);
System.out.println(
  "Threeten-Extra-impl of UTC-SLS: " 
  + utc1.durationUntil(utc2).getSeconds()); // pitfall, see next output!
// 600 (??? - where is the leap second, should be 601 because of original POSIX-input?!)
System.out.println(
  "Threeten-Extra-impl of UTC-SLS: " 
  + utc1.durationUntil(utc2)); // <= printing the duration object
// PT10M0.600600601S (OK, here the UTC-SLS-specific fraction of second appears
// Reason: Smoothing starts 1000s before leap second)

// only offset "Z=UTC+00:00" can be parsed
utc1 = UtcInstant.parse("2012-06-30T23:50:00Z");
utc2 = UtcInstant.parse("2012-07-01T00:00:00Z");
System.out.println(
  "Threeten-Extra-impl of UTC-SLS: " + utc1.durationUntil(utc2).getSeconds());
// 601 (expected, the only way where seconds are really SI-seconds)

撇开格式化和解析方面的一些明显限制不谈,主要问题似乎是时间尺度定义的混乱处理。秒的定义在这里取决于上下文。例如,如果您只考虑使用 UTC-SLS 进行转换,则结果“PT10M0.600600601S”是可以的,但如果您考虑从 POSIX 通过 UTC-SLS 到 UTC 的整个转换,那就不行了。如前所述,POSIX 时间在闰秒前 10 分钟明确定义,因此闰秒期间 POSIX 的任何模糊都不是借口。

请记住,java.time-world 之外的真正没有人谈论 UTC-SLS,这是 Markus Kuhn 的一项(正式过期)提案,用于解决 NTP 时间服务器或操作系统内核的内部实现。相反,像谷歌这样的其他人和企业引入了他们自己不同的跳跃涂抹实现。我看不到 UTC-SLS 的未来。

并且由于java.util.Date 与 UTC-SLS 无关(更早),但对于普通时间戳也有很好的定义(遵循 UTC 定义的 Si-秒,但闰秒的限制在all),我们在这里看到外部 IT 世界(它不想知道任何关于闰秒或 UTC-SLS 的任何事情)和 Threeten-Extra 之间的互操作性问题,这可能会导致计算出的持续时间出现意外差异。

【讨论】:

    【解决方案3】:

    考虑将QuartzCronTrigger 一起使用

    【讨论】:

    • 使用 Quartz 并不会在闰秒方面添加任何东西……或者如果确实如此,您需要提供更多上下文作为它带来的内容……
    猜你喜欢
    • 1970-01-01
    • 2015-11-12
    • 1970-01-01
    • 2012-06-05
    • 1970-01-01
    • 2014-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多