计算从闰年之间的年份

Eva*_*les 9 java leap-year java-8 java-time

在计算两个日期之间的年份时,第二个日期是从第一个日期开始计算的(这是我正在研究的一个简化示例),LocalDate并且Period似乎计算一年略有不同.

例如,

LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plusYears(1);
System.out.println(Period.between(date, plusYear).getYears());
Run Code Online (Sandbox Code Playgroud)

LocalDate date = LocalDate.of(1996, 3, 29);
LocalDate plusYear = date.plusYears(1);
System.out.println(Period.between(date, plusYear).getYears());
Run Code Online (Sandbox Code Playgroud)

尽管明确增加了一年,但第一次Period返回的年份为0第二种情况1.

这有什么好的方法吗?

dig*_*ise 3

这个问题具有哲学性质,涵盖了一些问题,例如时间测量和日期格式约定。

LocalDate是ISO 8601数据交换标准的实施。Java Doc 明确指出该类不表示时间,而仅提供标准日期表示法。

API 仅提供对符号本身的简单操作,所有计算都是通过递增给定日期的YearMonthDay来完成的。

换句话说,打电话时LocalDate.plusYears()您添加的是每年 365 天的概念年,而不是一年内的确切时间量。

这使得成为可以添加到由 表示的日期的最小时间单位LocalDate

在人类的理解中,日期不是时间中的一个时刻,而是一个时期。

它以 00h 00m 00s (...) 开始,以 23h 59m 59s (...) 结束。

LocalDate然而,避免了时间测量和人类时间单位模糊的问题(小时都可以有不同的长度),并将日期符号简单地建模为元组:

(years, months within a year, days within a month )

从纪元开始计算。

在这种解释中,日是影响日期的最小单位是有道理的。

如下所示:

LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusSecond = date.plus(1, ChronoUnit.SECONDS);
Run Code Online (Sandbox Code Playgroud)

回报

java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
Run Code Online (Sandbox Code Playgroud)

...这表明,使用LocalDate并添加秒数(或更小的单位来驱动精度),您无法克服问题中列出的限制。

查看实现,您会发现LocalDate.plusYears()在添加年份后,调用resolvePreviousValid(). 然后,此方法检查闰年并按以下方式修改日期字段:

day = Math.min(day, IsoChronology.INSTANCE.isLeapYear((long)year)?29:28);

换句话说,它通过有效扣除 1 天来纠正它。

您可以使用Year.length()which 返回给定年份的天数,对于闰年返回366 。所以你可以这样做:

LocalDate plusYear = date.plus(Year.of(date.getYear()).length(), ChronoUnit.DAYS);
Run Code Online (Sandbox Code Playgroud)

您仍然会遇到以下奇怪的情况(为Year.length()简洁起见,请用天数替换):

LocalDate date = LocalDate.of(1996, 2, 29); 
LocalDate plusYear = date.plus(365, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " + 
                    between.getMonths() + "m " + 
                    between.getDays() + "d");
Run Code Online (Sandbox Code Playgroud)

回报

1997-02-28
0y 11m 30d
Run Code Online (Sandbox Code Playgroud)

然后

LocalDate date = LocalDate.of(1996, 3, 29);
LocalDate plusYear = date.plus(365, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
                    between.getMonths() + "m " +
                    between.getDays() + "d");
Run Code Online (Sandbox Code Playgroud)

回报

1997-03-29
1y 0m 0d
Run Code Online (Sandbox Code Playgroud)

最后:

LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plus(366, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
                    between.getMonths() + "m " +
                    between.getDays() + "d");
Run Code Online (Sandbox Code Playgroud)

返回:

1997-03-01
1y 0m 1d
Run Code Online (Sandbox Code Playgroud)

请注意,将日期移动366 天(而不是365天)会将期限从11 个月零 30 天增加到1 年零 1 天(增加 2 天!)。