使用 time4j 为片刻添加持续时间

Add duration to a moment using time4j

因为我正在将使用 java.time 实现的代码转换为 time4j,我想添加一个 DurationMoment 但我收到编译错误。在 java.time 下,我将执行以下操作:

val startTime: ZonedDateTime = ...
val duration: TemporalAmount = ...
val endTime: ZonedDateTime = startTime.plus(duration)

虽然使用 time4j,但同样不起作用:

val startTime: Moment = ...
val duration: Duration[ClockUnit] = ...
val endTime: Moment = startTime.plus(duration)

编译器抱怨 DurationMoment 之间的泛型交互。无论我用哪种方式创建 Duration(至少我发现了),它都需要有一个关联的泛型 java.util.concurrent.TimeUnit,因为 Moment 实现了 TimePoint[java.util.concurrent.TimeUnit]Moment#plus 方法因此需要一个 Duration[java.util.concurrent.TimeUnit] 与时间单位的 Moment 具有相同的关联泛型。

令我惊讶的是 java.util.concurrent.TimeUnit 被使用,因为这不是 time4j 类型。我是否错过了有关此选择的更详细信息?我的印象是这是设计决定的。

一种可行的方法是,如果我使用 PlainTimestamp 并添加 Duration[ClockUnit | CalendarUnit | IsoUnit],因为前一种类型实现了 TimePoint[IsoUnit, PlainTime] 并重载了额外的支持单元。然后我可以将 PlainTimestamp 转换为我之后需要的任何时区。这是预期的设计吗?

(还有:)与 Moment.plus 方法一起使用的正确 Duration 类型是什么?

我正在使用 time4j 版本 4.27.2

简答如何迁移zonedDateTime.plus(Duration.ofHours(24)):

Moment.from(zonedDateTime.toInstant()).plus(MachineTime.of(24, TimeUnit.HOURS));

另请参阅全局持续时间类型 MachineTime 的 API。

详细回答

java.time-API 相比,后者只知道一个 class ChronoUnit 来表示适用于任意时间实体的最常用时间单位(和通用接口 TemporalUnit),库 Time4J 的单元和持续时间设计更加细粒度。

  • 每个带有时间线的日历或时间类型都有自己的单位类型,它也以不同的方式表征时间类型的时间线,因此即使是编译器也会告诉您不要混淆不同实体的不同单位类型,例如 Moment(对应 java.time.Instant)或 PlainTimestamp(对应 LocalDateTime)。
  • 这个区别被设计编码为超级 class TimePoint 中的泛型类型参数 U。对于具体的 classes:Moment 主要与 java.util.concurrent.TimeUnit 一起工作,而 PlainTimestampIsoUnit 的任何实现一起工作,尤其是枚举 CalendarUnitClockUnit.
  • 广义持续时间概念由接口 TimeSpan 描述,该接口也由要使用的单位类型 U 指定。如果单位类型为 java.util.concurrent.TimeUnit,则只能将时间跨度添加到时刻。这是由特殊实现 class MachineTime 提供的。此外,另一个名为 net.time4j.Duration 的持续时间实现仅与 PlainTimestamp.
  • 等本地类型兼容
  • 特殊时间实体有自己的一套合适的单位。例如,纪元的单位只存在于日本日历中,而其他历法则不存在,这些日历要么有零纪元,要么只有一个纪元,要么只有两个纪元。

为什么 MomentPlainTimestamp 的单位类型不同?

关于合适的单位集的最后一项已经部分回答了这个问题。例如,月份和年份对于 Moment 这样的类机器类型没有多大意义。因此,选择的枚举 java.util.concurrent.TimeUnit 涵盖了 Moment.

所需的单位

此外,Time4J 的不同单位类型有助于区分。 net.time4j.Duration<ClockUnit> 是在本地上下文中计算的,而 MachineTime<TimeUnit> 是作为全局持续时间计算的。这不仅适用于与时钟相关的单位,如小时,也适用于日历单位。一年不仅仅是一年。我们有 ISO 日历年(对应公历年)。我们有基于 ISO 周的年(长度为 364 或 371 天)。我们有伊斯兰年(354 或 355 天)等等。因此 Time4J 知道很多不同的日历单位(注意日历模块的 API)。 所以 Time4J 最终采用了一种设计来防止比较不同单位类型的持续时间(这就像比较苹果和橘子)。

以下萨摩亚更改国际日期变更线的罕见案例(2011 年 12 月 30 日被排除在外)说明了单位类型的区别可能有多么重要:

在Time4J中,我们只是使用不同的单元类型来表示运算发生在本地时间轴上还是全局时间轴上。结论:在 Java-8 中,我们必须仔细研究上下文,在 Time4J 中,单位类型提供了有价值的额外信息。

ZonedDateTime zdt = ZonedDateTime.of(2011, 12, 29, 0, 0, 0, 0, "Pacific/Apia");
Moment m1 = Moment.from(zdt.toInstant());
Moment m2 = m1.plus(MachineTime.of(24, TimeUnit.HOURS));
assertThat(m2.isSimultaneous(m1.plus(MachineTime.of(1, TimeUnit.DAYS))), is(true));
System.out.println(m2); // 2011-12-30T10:00:00Z
System.out.println(m2.toZonalTimestamp(PACIFIC.APIA)); // 2011-12-31T00
System.out.println(m1.toZonalTimestamp(PACIFIC.APIA).plus(2, CalendarUnit.DAYS)); // 2011-12-31T00

对象TimeUnit.DAYSCalendarUnit.DAYS明显不一样。他们甚至需要不同的数量(1 对 2)才能产生相同的结果。

旁注:

我现在已经缩短了第一个版本的答案——主要是省略了与 Java-8 相关的内容,因为我认为很容易写太多关于 unit/duration-design 的主题在你问题的狭义背景下(我什至没有给出任何完整的答案)。 SO 上的教程页面或额外的文档页面确实是一个更好的地方。

但至少还有两点我的回答中没有提到,您可能也会感兴趣(关注 net.time4j.Duration):Sign handling and specialized timezone-metric。在某些情况下,最后一个甚至可以替代 MachineTime