请记住:日历月和年并不代表固定的时间长度。例如,如果这段代码显示了1个月的差异,它可能意味着从28天到31天(其中1天= 24小时)。如果这对您的用例不合适,我强烈建议完全放弃月和年(这样您就可以只使用Duration类),或者最多为月和年设置一个固定的时间值(例如,一个月为30天,一年为365天)。
如果您仍然想继续(例如,因为您的用户已经期望月和年意味着日历月和年),您将注意到代码不像人们最初认为的那样简单(这也意味着它更容易出错;感谢@Alex注意到一个错误,现在已经修复了),这正是因为Java库设计者足够聪明,不会将日历周期(Period类)和确切的持续时间(Duration类)混合在一起。
博士TL;
前提条件:start <= end。
// Closest start datetime with same time-of-day as the end datetime.
// This is to make sure to only count full 24h days in the period.
LocalDateTime closestFullDaysStart = LocalDateTime.of(
start.toLocalDate()
.plusDays(end.toLocalTime().compareTo(start.toLocalTime()) < 0 ? 1 : 0),
end.toLocalTime()
);
// Get the calendar period between the dates (full years, months & days).
Period period = Period.between(closestFullDaysStart.toLocalDate(), end.toLocalDate());
// Get the remainder as a duration (hours, minutes, etc.).
Duration duration = Duration.between(start, closestFullDaysStart);
然后使用方法period.getYears(), period.getMonths(), period.getDays(), duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart()。
在网上试试!
扩大的答案
我将回答最初的问题,即如何以年、月、日、小时和分钟为单位获得两个LocalDateTimes之间的时间差,以便不同单位的所有值的“和”(见下面的注释)等于总时间差,并且每个单位中的值小于下一个较大的单位—即。分钟< 60,小时< 24,依此类推。
给定两个LocalDateTimes开始和结束,例如:
LocalDateTime start = LocalDateTime.of(2019, 11, 28, 17, 15);
LocalDateTime end = LocalDateTime.of(2020, 11, 30, 16, 44);
我们可以用Duration表示两者之间的绝对时间跨度——也许使用Duration。(开始、结束)之间。但是我们可以从Duration中提取的最大单位是天(作为时间单位相当于24小时)-请参阅下面的说明以了解解释。要使用更大的单位*(月,年),我们可以用一对(Period, Duration)来表示这个Duration,其中Period测量精确到天的差值,Duration表示余数。
We need to be careful here, because a Period is really a date difference, not an amount of time, and all its calculations are based on calendar dates (see the section below). For example, from 1st January 2000 at 23:59 to 2nd January 2000 at 00:01, a Period would say there is a difference of 1 day, because that's the difference between the two dates, even though the time delta is 2 minutes, much less than 24h. So, we need to start counting the calendar period at the next closest point in time which has the same time of day as the end point, so that any calendar days that we count actually correspond to full 24h durations:
LocalDateTime closestFullDaysStart = LocalDateTime.of(
start.toLocalDate()
// if the end time-of-day is earlier than the start time-of-day,
// the next point in time with that time-of-day is one calendar day ahead
// (the clock "wraps around" at midnight while advancing to it)
.plusDays(end.toLocalTime().compareTo(start.toLocalTime()) < 0 ? 1 : 0),
end.toLocalTime()
);
现在我们已经有效地将开始和结束之间的时间跨度分成了两部分:从开始到closefulldaysstart的时间跨度,它的构造将小于24h,因此我们可以使用Duration对象来测量它,不包含天数部分,
Duration duration = Duration.between(start, closestFullDaysStart);
以及从closefulldaysstart到end的时间跨度,我们知道我们现在可以可靠地用一个Period来测量。
Period period = Period.between(closestFullDaysStart.toLocalDate(), end.toLocalDate());
现在我们可以简单地使用Period和Duration中定义的方法来提取单个单元:
System.out.printf(
"%d years, %d months, %d days, %d hours, %d minutes, %d seconds",
period.getYears(), period.getMonths(), period.getDays(),
duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart()
);
1 years, 0 months, 1 days, 23 hours, 29 minutes, 0 seconds
或者,使用默认格式:
System.out.println(period + " + " + duration);
P1Y1D + PT23H29M
*注年、月、日
注意,在java中。时间的概念,像“月”或“年”这样的周期“单位”并不代表一个固定的、绝对的时间值——它们依赖于日期和日历,如下面的例子所示:
LocalDateTime
start1 = LocalDateTime.of(2020, 1, 1, 0, 0),
end1 = LocalDateTime.of(2021, 1, 1, 0, 0),
start2 = LocalDateTime.of(2021, 1, 1, 0, 0),
end2 = LocalDateTime.of(2022, 1, 1, 0, 0);
System.out.println(Period.between(start1.toLocalDate(), end1.toLocalDate()));
System.out.println(Duration.between(start1, end1).toDays());
System.out.println(Period.between(start2.toLocalDate(), end2.toLocalDate()));
System.out.println(Duration.between(start2, end2).toDays());
P1Y
366
P1Y
365
另一个例子,从2000年1月1日23:59到2000年1月2日00:01,一个Period会说有1天的差,因为这是两个日期之间的差,即使时间增量小于24小时。
消极的跨越
如果开始>结束,上面的代码生成的答案在技术上是正确的,如果我们把所有的单位相加,但以一种意想不到的方式呈现。
例如,对于
LocalDateTime start = LocalDateTime.of(2020, 1, 1, 1, 00);
LocalDateTime end = LocalDateTime.of(2020, 1, 1, 0, 0);
我们得到:
0 years, 0 months, -1 days, 23 hours, 0 minutes, 0 seconds
-1天加23小时等于-1小时,这是正确的。但我们期望答案是-1小时。
当前显示的持续时间总是正的,这是因为closefulldaysstart总是在未来开始。
如果开始>结束,但是,从开始到结束的方向是“回到时间”,所以我们需要从开始回溯,以找到与开始和结束之间的一天的结束时间最近的datetime。
此外,环绕条件也会发生变化:因为我们向后绕时钟,所以如果开始时间比结束时间早,我们就会环绕时钟(因此需要减去一个日历日)。
综合所有这些因素可以得出:
int endStartComparison = end.compareTo(start);
int endStartTimeComparison = end.toLocalTime().compareTo(start.toLocalTime());
LocalDateTime closestFullDaysStart = LocalDateTime.of(
start.toLocalDate()
.plusDays(
(
(endStartComparison > 0)
? (endStartTimeComparison < 0)
: (endStartTimeComparison > 0)
)
? (long) Math.signum(endStartComparison) // advance one unit in the direction start->end
: 0
),
end.toLocalTime()
);
我们可以用一种更简洁但更hack的方式重写丑陋的嵌套条件:
(endStartComparison * endStartTimeComparison < 0)
在网上试试!