将持续时间转换为 Java8 日期 API 中的年数?

Converting duration to years in Java8 Date API?

我在很久以前有个约会。
我发现了这个日期和现在之间的持续时间。
现在我想知道 - 这是多少年?

我使用 Java8 API 想出了这个解决方案。
这是一个可怕的解决方案,因为我必须首先手动将持续时间转换为天数,因为否则会有 UnsupportedTemporalTypeException - LocalDate.plus(SECONDS) 出于任何原因不受支持。
即使编译器允许这个调用。

是否可以更简洁地将 Duration 转换为年?

LocalDate dateOne = LocalDate.of(1415, Month.JULY, 6);
Duration durationSinceGuss1 = Duration.between(LocalDateTime.of(dateOne, LocalTime.MIDNIGHT),LocalDateTime.now());

long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(), 
        LocalDate.now().plus(
                TimeUnit.SECONDS.toDays(
                        durationSinceGuss1.getSeconds()), 
                ChronoUnit.DAYS) );

/*
 * ERROR - 
 * LocalDate.now().plus(durationSinceGuss1) causes an Exception. 
 * Seconds are not Supported for LocalDate.plus()!!!
 * WHY OR WHY CAN'T JAVA DO WHAT COMPILER ALLOWS ME TO DO?
 */
//long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(), LocalDate.now().plus(durationSinceGuss) );

/*
 * ERROR - 
 * Still an exception! 
 * Even on explicitly converting duration to seconds. 
 * Everything like above. Seconds are just not allowed. Have to convert them manually first e.g. to Days?!
 * WHY OR WHY CAN'T YOU CONVERT SECONDS TO DAYS OR SOMETHING AUTOMATICALLY, JAVA?
 */
//long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDate.now(), LocalDate.now().plus(durationSinceGuss.getSeconds(), ChronoUnit.SECONDS) );

您是否尝试过使用 LocalDateTimeDateTime 而不是 LocalDate?根据设计,后者不支持 hours/minutes/seconds/etc,因此当您尝试向其添加秒数时 UnsupportedTemporalTypeException

例如,这个有效:

LocalDateTime dateOne = LocalDateTime.of(1415, Month.JULY, 6, 0, 0);
Duration durationSinceGuss1 = Duration.between(dateOne, LocalDateTime.now());
long yearsSinceGuss = ChronoUnit.YEARS.between(LocalDateTime.now(), LocalDateTime.now().plus(durationSinceGuss1) );
System.out.println(yearsSinceGuss); // prints 600

使用Period获取两个LocalDate对象之间的年数:

    LocalDate before    = LocalDate.of(1415, Month.JULY, 6);
    LocalDate now       = LocalDate.now();
    Period    period    = Period.between(before, now);

    int yearsPassed     = period.getYears();

    System.out.println(yearsPassed);

虽然@Matt Ball 接受的答案试图巧妙地使用 Java-8-API,但我会提出以下异议:

您的要求不准确,因为无法将秒准确转换为年。

原因是:

  • 最重要的是:月份的天数不同(从 28 天到 31 天)。
  • 年份有时会有闰日(2 月 29 日),这也会影响计算年份增量。
  • 公历转换:您从 1415 年开始,这远远早于第一次公历改革,该改革取消了整整十天,在英国甚至是 11 天,在俄罗斯更多。旧儒略历的年份有不同的闰年规则。
  • 历史日期未定义为精确到秒。例如,你能描述一下黑斯廷斯战役的 instant/moment 吗?我们甚至不知道确切的时间,只知道那天。假设午夜在一天的开始已经是一个粗略的并且可能是错误的假设。
  • 影响一天长度(23 小时、24 小时、25 小时或什至其他不同长度)的时区效应。
  • 闰秒(异国情调)

也许对您的代码最重要的反对意见:

我无法想象1415年日期的供应商有意将这样的日期解释为公历

我理解从秒到年的愿望,但它只能是一个近似值,无论您选择什么作为解决方案。因此,如果您有像 1415 这样的年份,我建议您遵循非常简单的近似值:

Duration d = ...;
int approximateYears = (int) (d.toDays() / 365.2425);

对我来说,只要我们真的想为这样的用例使用基于秒的持续时间,在历史背景下就足够了。似乎您无法更改从外部来源获得的输入(否则最好联系持续时间供应商并询问是否可以提供天数)。不管怎样,你得问问自己你想应用什么样的年份定义。

旁注:

您的投诉"WHY OR WHY CAN'T JAVA DO WHAT COMPILER ALLOWS ME TO DO?"与新java.time-API.

的字符不匹配

您希望 API 是类型安全的,但 java.time (JSR-310) 并未设计为类型安全的并且严重依赖运行时异常。编译器不会帮助你 API。相反,如果任何给定的时间单位是否适用于任何给定的时间类型,您必须在有疑问时查阅文档。您可以在 Temporal.isSupported(TemporalUnit) 的任何具体实现的文档中找到这样的答案。无论如何,编译安全的愿望是可以理解的(我自己已经尽最大努力将我自己的时间库 Time4J 实现为类型安全的)但是 JSR-310 的设计已经是一成不变的。

如果在 LocalDateTimeInstant 上应用 java.time.Duration 也会有一个微妙的陷阱,因为结果不完全可比(第一种类型的秒数在本地时间轴上定义而 Instant 的秒数在全球时间轴上定义)。所以即使没有像@Matt Ball 接受的答案那样的运行时异常,我们也必须仔细考虑这样的计算结果是否合理可信。