Java 周期和持续时间之间的微妙之处

2022-09-03 06:32:56

我不确定我是否理解Java和.PeriodDuration

当我阅读Oracle的解释时,它说我可以找出自这样的生日以来多少天(使用他们使用的示例日期):

LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1);
Period birthdayPeriod = Period.between(birthday, today);
int daysOld = birthdayPeriod.getDays();

但正如他们所指出的那样,这并没有考虑到你出生的时区和你现在所在的时区。但这是一台计算机,我们可以精确,对吧?那么我会使用?Duration

ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
Duration duration = Duration.between(born, now);
long daysPassed = duration.toDays();

现在实际时间是准确的,但是如果我理解正确,天可能不能正确表示日历日,例如DST等。

那么,我该怎么做才能根据我的时区获得准确的答案呢?我唯一能想到的就是回到 使用 ,但首先从值中规范化时区,然后使用 .LocalDateZonedDateTimeDuration

ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime nowNormalized=now.withZoneSameInstant(born.getZone());
Period preciseBirthdayPeriod = Period.between(born.toLocalDate(), nowNormalized.toLocalDate());
int preciseDaysOld = preciseBirthdayPeriod.getDays();

但这似乎真的很复杂,只是为了得到一个准确的答案。


答案 1

您对Java-8类的分析或多或少是正确的。PeriodDuration

  • 该类仅限于日历日期精度。java.time.Period
  • 该类仅处理秒(和纳秒)精度,但始终将天视为等效于 24 小时 = 86400 秒。java.time.Duration

通常,在计算一个人的年龄时,忽略时钟精度或时区是完全足够的,因为护照等个人文件没有记录某人出生的确切时间。如果是这样,那么 -class 就会完成它的工作(但请小心处理它的方法 - 见下文)。PeriodgetDays()

但是,您希望更精确,并根据本地字段描述结果,同时考虑时区。好吧,第一部分(精度)由 支持,但不支持第二部分。Duration

使用它也没有帮助,因为确切的时间差(被忽略)可以以天为单位影响增量。此外(只需打印代码的输出):PeriodPeriod

    Period preciseBirthdayPeriod =
        Period.between(born.toLocalDate(), nowNormalized.toLocalDate());
    int preciseDaysOld = preciseBirthdayPeriod.getDays();
    System.out.println(preciseDaysOld); // 13
    System.out.println(preciseBirthdayPeriod); // P56Y11M13D

如您所见,使用 preciseBirthdayPeriod.getDays() 方法以天为单位获得总增量是相当危险的。不,它只是总增量的一部分。还有11个月和56年。我认为不仅在几天内打印delta也是明智的,因为这样人们就更容易想象delta有多大(请参阅社交媒体中经常看到的打印持续时间的用例,如“3年,2个月和4天”)。

显然,您更需要一种方法来确定持续时间,包括日历单位以及特殊时区中的时钟单位(在您的示例中:某人出生的时区)。Java-8-time-library的坏处是:它不支持AND的任意组合。导入外部库Threttleen-Extra-class也无济于事,因为仍将忽略时区效应(1天== 24小时),并且也无法以其他单位(如月等)打印delta。PeriodDurationIntervallong daysPassed = interval.toDuration().toDays();

总结:

您已经尝试了 -解决方案。@swiedsw给出的答案尝试了基于-的解决方案。这两种方法在精度方面都有缺点。您可以尝试将这两个类组合到一个新类中,该类自己实现并实现必要的时间算术(不是那么微不足道)。PeriodDurationTemporalAmount

附注:

我自己已经在我的时间库Time4J中实现了你想要的东西,所以它可能是你自己实现的灵感。例:

Timezone bornZone = Timezone.of(AMERICA.NEW_YORK);
Moment bornTime =
    PlainTimestamp.of(1960, net.time4j.Month.JANUARY.getValue(), 1, 22, 34, 56).in(
        bornZone
    );
Moment currentTime = Moment.nowInSystemTime();
MomentInterval interval = MomentInterval.between(bornTime, currentTime);

MachineTime<TimeUnit> mt = interval.getSimpleDuration();
System.out.println(mt); // 1797324427.356000000s [POSIX]

net.time4j.Duration<?> duration =
    interval.getNominalDuration(
        bornZone, // relevant if the moments are crossing a DST-boundary
        CalendarUnit.YEARS,
        CalendarUnit.MONTHS,
        CalendarUnit.DAYS,
        ClockUnit.HOURS,
        ClockUnit.MINUTES
    );

// P56Y11M12DT12H52M (12 days if the birth-time-of-day is after current clock time)
// If only days were specified above then the output would be: P20801D
System.out.println(duration);
System.out.println(duration.getPartialAmount(CalendarUnit.DAYS)); // 12

这个例子也证明了我的一般态度,即使用月份,天,小时等单位在严格意义上并不精确。唯一严格精确的方法(从科学的角度来看)是使用以十进制秒为单位的机器时间(最好以SI秒为单位,在1972年之后的Time4J中也可以)。


答案 2

JavaDoc of Period指出,它建模:

ISO-8601 日历系统中基于日期的时间量,例如“2 年 3 个月和 4 天”。

我知道它没有提到时间点。

您可能希望从项目 ThreeTen-Extra 中检查哪些模型的间隔

两个瞬间之间的不可变时间间隔。

项目网站指出该项目“[...]由 Java 8 日期和时间库的主要作者 Stephen Colebourne 策划。

您可以通过调用“间隔”从“时间间隔”中检索“持续时间”。toDuration()

我将转换您的代码以给出一个示例:

ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
Interval interval = Interval.of(born.toInstant(), now.toInstant());
long daysPassed = interval.toDuration().toDays();

推荐