为啥 Instant 不支持 ChronoUnit.YEARS 的操作?

Posted

技术标签:

【中文标题】为啥 Instant 不支持 ChronoUnit.YEARS 的操作?【英文标题】:Why Instant does not support operations with ChronoUnit.YEARS?为什么 Instant 不支持 ChronoUnit.YEARS 的操作? 【发布时间】:2016-10-07 01:15:21 【问题描述】:

这出乎我的意料:

> Clock clock = Clock.systemUTC();

> Instant.now(clock).minus(3, ChronoUnit.DAYS);
java.time.Instant res4 = 2016-10-04T00:57:20.840Z

> Instant.now(clock).minus(3, ChronoUnit.YEARS);
java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years

作为一种解决方法,我必须这样做:

> Instant.now(clock).atOffset(ZoneOffset.UTC).minus(3, ChronoUnit.YEARS).toInstant();
java.time.Instant res11 = 2013-10-07T01:02:56.361Z

我很好奇 Instant 为什么不支持 YEARS。开发者就放弃了吗?

(在我的实际代码中,我尝试减去 Period.ofYears(3),但引用的 Instant 方法是最后被调用的方法)。

【问题讨论】:

我的实际问题是,为什么支持ChronoUnit.DAYS。这是不一致的...... 与什么不一致?我的期望是,如果一个方法需要一个时间单元(Period),那么它知道如何处理它,所以当它失败时会令人惊讶。 当使用更高精度的精确单位(即秒、微秒甚至纳秒)进行测量时,天的长度不是恒定的。一旦你开始支持天,假设它们正好是 86400 秒,你就放弃了,例如秒、微秒和纳秒,以及构建为秒的明确倍数的单位,即分钟和小时。所以奇怪的是有一个类支持 nanos、micros、millis、seconds、minutes、hours、和 days,其中对天数的支持将所有前者变成了伪单位。 有趣的是,这个问题还没有答案。我确信一定有原因,但我无法在任何地方找到解释。只建议改用什么... “我清楚地说,我的问题是“为什么”” - 开发人员做出了选择。除非你能从开发者那里得到答案,否则问题是“基于意见的”,不是吗? 【参考方案1】:

我正在尝试一些在我看来非常合乎逻辑的东西。

这里是方法plus(long, TemporalUnit)的代码(在minus(...)中使用):

@Override
public Instant plus(long amountToAdd, TemporalUnit unit) 
    if (unit instanceof ChronoUnit) 
        switch ((ChronoUnit) unit) 
            case NANOS: return plusNanos(amountToAdd);
            case MICROS: return plus(amountToAdd / 1000_000, (amountToAdd % 1000_000) * 1000);
            case MILLIS: return plusMillis(amountToAdd);
            case SECONDS: return plusSeconds(amountToAdd);
            case MINUTES: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_MINUTE));
            case HOURS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_HOUR));
            case HALF_DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY / 2));
            case DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY));
        
        throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
    
    return unit.addTo(this, amountToAdd);

我们可以看到,结果是用单位表示的秒数相乘来计算的,一年不能用秒表示,原因很明显。


加法

我可以看到另一个明显的原因:上述方法中使用的常量来自java.time.LocalTime。常量只定义最多天的单位。以上天数未定义常量(LocalDateLocalDateTime 均未定义)。

【讨论】:

@AlexeyRomanov Instant 定义了它自己的 Java 时间尺度。在这个时间尺度上,每天无论如何都有86400秒。这些秒数始终与 SI 秒数不匹配。只要你留在这个系统中,一天就被精确定义了。 @Jens 当您定义自己的时间尺度时,每天都有 86400 秒,无论如何,是什么阻止您进一步定义每年有 365 天,无论如何?这就是重点,因为这个时间尺度已经使用了伪秒,这并不能解释为什么不支持 YEARS 单位。 另外,可以在LocalDateTime 中添加年份,这个逻辑也可以应用于 Instant 吗? @Holger 我从来没有说过这解释了为什么它不支持年。我只是指出问题不在于天、小时或分钟的秒数不同,它与时间测量的分辨率无关。也许是因为不同的年份定义与通常的太阳年相比,与秒的定义不同? @YassinHajaj 再说一遍,你为什么不能在Instant 中添加一个WEEK?这是一天乘以7,这种情况下没有太多解释的余地​​……【参考方案2】:

我猜这是因为 Instant 不包含有关时区的信息。这意味着相同的 Instant 可以解释为不同时区的不同日期时间值。假设我们有 Instant,它在 UTC+2 时区表示为 2016.01.01 00:30:00。相同的 Instant 表示 UTC+1 时区的 2015.12.31 23:30:00。 2016 年是闰年,它的长度是 366 天,所以为了得到 Instant - 1 年,我们必须从中减去 366 天。但是 2015 年不是闰年,它的长度是 365 天,所以我们必须从 Instant 中减去 365 天。这种模糊性导致 Instant 不支持 ChronoUnit.YEARS。类似的问题导致 Instant 不支持 ChronoUnit.MONTHS。并且可能缺少 DST 信息导致 Instant 不支持 ChronoUnit.WEEKS。

【讨论】:

以上是关于为啥 Instant 不支持 ChronoUnit.YEARS 的操作?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Instant 在彼此之后打印时显示不同的值? [关闭]

Spring Boot 2.5.0 和 InvalidDefinitionException:默认不支持 Java 8 日期/时间类型`java.time.Instant`

为啥我无法在 Android Instant App 中访问相机?

Java 8的Time包常用API

调用需要 API 级别 26(当前最低为 23):java.time.Instant#now

前端每周清单:Instant App将至,WebAssembly将获默认支持,PWA实践渐增