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

Java期间和持续时间之间的微妙之处,java,time,duration,Java,Time,Duration,我不确定我是否理解了JavaPeriod和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(); ZonedDateTime nowNormalized=n

我不确定我是否理解了Java
Period
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();
ZonedDateTime nowNormalized=now.withZoneSameInstant(born.getZone());
Period preciseBirthdayPeriod = Period.between(born.toLocalDate(), nowNormalized.toLocalDate());
int preciseDaysOld = preciseBirthdayPeriod.getDays();
当我读到甲骨文的时候,它说我可以知道像这样的生日已经过了多少天(使用他们使用的示例日期):

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

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等

那么,我该怎么做才能根据我的时区得到准确的答案呢?我唯一能想到的是回到使用
LocalDate
,但是首先从
ZonedDateTime
值规范化时区,然后使用
持续时间

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();
但是,要得到一个精确的答案,这似乎真的很复杂。

JavaDoc的状态是,它建模:

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

我知道它没有提到时间点

您可能希望从project中检查以下模型:

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

该项目网站声明该项目“[…]由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();

您对Java-8类
期间
持续时间
的分析或多或少是正确的

  • java.time.Period
    仅限于日历日期精度
  • java.time.Duration
    仅处理秒(和纳秒)精度,但将天始终视为等同于24小时=86400秒
通常,在计算一个人的年龄时,忽略时钟精度或时区就足够了,因为像护照这样的个人文件不能记录一个人出生的确切时间。如果是这样的话,
Period
-类将完成它的工作(但请小心处理它的方法,如
getDays()
,请参见下文)

但您需要更高的精度,并根据本地字段描述结果,同时考虑时区。好的,第一部分(精度)由
持续时间支持,但第二部分不支持

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

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

显然,您需要一种方法来确定持续时间,包括特定时区(在您的示例中:某人出生的时区)中的日历单位和时钟单位。Java-8-time-library的缺点是:它不支持
Period
Duration
的任何组合。导入外部库Threen额外类
Interval
也不会有帮助,因为
long daysPassed=Interval.toDuration().toDays()仍将忽略时区影响(1天==24小时),并且也无法以其他单位(如月份等)打印增量

摘要:

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

旁注:

我自己已经在我的时间库中实现了您所寻找的内容,因此它可能对您自己的实现很有用。例如:

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
Timezone bornZone=Timezone.of(AMERICA.NEW_YORK);
临终时刻=
PlainTimestamp.of(1960,net.time4j.Month.janur.getValue(),1,22,34,56)(
冰片
);
矩currentTime=矩.nowInSystemTime();
MomentInterval interval=MomentInterval.between(bornTime,currentTime);
MachineTime mt=interval.getSimpleDuration();
系统输出打印项次(mt);//1797324427.356000000s[POSIX]
net.time4j.Duration持续时间=
interval.getNominalDuration(
bornZone,//如果力矩穿过DST边界,则相关
日历单位。年,
日历单位。个月,
日历单位:天,
时钟单位。小时,
时钟单位。分钟
);
//P56Y11M12DT12H52M(如果一天的出生时间在当前时钟时间之后,则为12天)
//如果上面只指定了天数,则输出为:P20801D
系统输出打印项次(持续时间);
System.out.println(duration.getPartialAmount(CalendarUnit.DAYS));//12

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

这两类之间的主要区别是:

  • java.time.Period
    使用基于日期的值(2018年5月31日)
  • java.time.Duration
    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