Java 8 - ChronoUnit.MONTHS.between(fromDate,toDate) 未按预期工作

Java 8 - ChronoUnit.MONTHS.between(fromDate,toDate) not working as expected

我需要找到号码。两个日期之间的月份,包括两个月份。我正在使用 ChronoUnit.Months.between,但得到了意想不到的结果。

代码适用于:1 月 31 日 - 7 月 31 日

失败时间:1 月 31 日 - 6 月 30 日

失败的代码:

import java.time.LocalDate;
import static java.time.temporal.ChronoUnit.MONTHS;
public class HelloWorld{

     public static void main(String []args){
        LocalDate startDate=LocalDate.of(2018,01,31);
        LocalDate endDate=LocalDate.of(2018,6,30);
        //Both dates inclusive, hence adding 1
        long noOfMonths = (MONTHS.between(startDate,endDate)) + 1L;
        System.out.println(" ********* No. of months between the two dates : " + noOfMonths);
        }
}

预计:6(一月、二月、三月、四月、五月、六月) 实际:5

有效代码:

import java.time.LocalDate;
import static java.time.temporal.ChronoUnit.MONTHS;
public class HelloWorld{

     public static void main(String []args){
        LocalDate startDate=LocalDate.of(2018,01,31);
        LocalDate endDate=LocalDate.of(2018,7,31);
        //Both dates inclusive, hence adding 1
        long noOfMonths = (MONTHS.between(startDate,endDate)) + 1L;
        System.out.println(" ********* No. of months between the two dates : " + noOfMonths);
        }
}

预计:7(一月、二月、三月、四月、五月、六月、七月) 实际:7

你看到同样的行为了吗?有没有其他方法可以做到这一点?

谢谢

就日历而言,您的问题是可以理解的:两次比较代表 1 个完整的月份。
但是java.time.temporal.ChronoUnit.between不是这样推理的,而是完整单位.
并且根据其 javadoc 预期结果:

The calculation returns a whole number, representing the number of complete units between the two temporals. For example, the amount in hours between the times 11:30 and 13:29 will only be one hour as it is one minute short of two hours.

ChronoUnit.between() 在幕后使用的 LocalDate.until javadoc 对您的情况更加明确,并且还给出了一个关于 between()MONTHS 一起使用的有趣示例TemporalUnit:

The calculation returns a whole number, representing the number of complete units between the two dates. For example, the amount in months between 2012-06-15 and 2012-08-14 will only be one month as it is one day short of two months.

在您的工作示例中:

LocalDate startDate=LocalDate.of(2018,01,31);
LocalDate endDate=LocalDate.of(2018,7,31);
long noOfMonths = MONTHS.between(startDate,endDate);

你有 31 days of month / 31 days of month, on 6 months => 6 months.

在你失败的例子中:

LocalDate startDate=LocalDate.of(2018,01,31);
LocalDate endDate=LocalDate.of(2018,6,30);
long noOfMonths = MONTHS.between(startDate,endDate);

你有30 days of month / 31 days of month, on 5 months
=> 4 months + 1 month short of 1 day <=> 4 months(四舍五入)。

如果你会这样写:

LocalDate startDate=LocalDate.of(2018,01,30);
LocalDate endDate=LocalDate.of(2018,6,30);
long noOfMonths = MONTHS.between(startDate,endDate);

你会 30 days of month / 30 days of month, on 5 months => 5 months .

关于您的问题:

Is there any alternate way of doing this?

最简单的方法:将月中的第几天设置为 1 或两个日期中的任何相同数字。
如果不符合您的要求,您可以检查这两个日期是否是他们当月的最后一天,如果是这样,请将其设置为相同的月份值。
你可以这样写:

int lastMonthStart = startDate.getMonth()
                              .length(startDate.isLeapYear());
int lastMonthEnd = endDate.getMonth()
                          .length(endDate.isLeapYear());

if (startDate.getDayOfMonth() == lastMonthStart && endDate.getDayOfMonth() == lastMonthEnd) {
    startDate = startDate.withDayOfMonth(1);
    endDate = endDate.withDayOfMonth(1);
}

davidxxx in 已经解释了为什么你的代码给出了意想不到的结果。请允许我补充一点,如果您对月份而不是日期感兴趣,YearMonth class 是适合您使用的正确格式:

        YearMonth startMonth = YearMonth.of(2018, Month.JANUARY);
        YearMonth endMonth = YearMonth.of(2018, Month.JUNE);
        // Both months inclusive, hence adding 1
        long noOfMonths = ChronoUnit.MONTHS.between(startMonth, endMonth) + 1;
        System.out.println(" ********* No. of months between the two months (inclusive): " + noOfMonths);

输出:

********* No. of months between the two months (inclusive): 6

这显然也消除了月份长度不等带来的问题。

如果您已经有 LocalDate 个对象,可能值得转换:

        LocalDate startDate = LocalDate.of(2018, Month.JANUARY, 31);
        YearMonth startMonth = YearMonth.from(startDate);

顺便说一句,一个月内不要使用两位数 01。对于 1 月,它有效,但 0809 都不适用于 8 月或 9 月,所以这是一个坏习惯。 Java(以及许多其他语言)将以 0 开头的数字理解为 octal numbers

Half-Open

另外两个答案 and 都是正确的,而且内容丰富。

我想补充一下 date-time 工作中常用的另一种定义时间跨度的方法:Half-Open。在Half-Open方法中,开始是包含,而结尾是不包含。因此,上半年的月份(一月、二月、三月、四月和六月)被跟踪为从 1 月 1 日开始到 运行 直到但不包括 7 月 1.

有时我们会在日常生活中直觉地使用这种方法。例如,如果 children 的 class 房间从 12:00 中午到下午 1 点吃午饭,学生应该在 [=111= 之前就座] 钟声在下午 1 点敲响。午休时间一直持续到但不包括下午 1 点。

我相信您会发现始终如一地使用 Half-Open 方法会使您的代码更易于阅读、调试和维护。

现代 java.time classes 使用这种方法来定义时间跨度。

LocalDateRange

Ole 的回答 V.V。明智地建议您查看 YearMonth class 以帮助您的工作。

另一个有用的 class 来自 ThreeTen-Extra project: org.threeten.extra.LocalDateRange。您可以在单个 object.

中表示整个日期范围

您可以使用开始日期和 Period 六个月来实例化。

LocalDateRange ldr = LocalDateRange.of( 
    LocalDate.of( 2018 , Month.JANUARY , 1 ) ,
    Period.ofMonths( 6 ) 
) ;

或指定开始和结束日期。

LocalDateRange ldr = LocalDateRange.of( 
    LocalDate.of( 2018 , Month.JANUARY , 1 ) ,
    LocalDate.of( 2018 , Month.JULY , 1 ) // Half-open.
) ;

虽然我不推荐它,但如果您坚持使用开始和结束都 包含 的 Fully-Closed 方法,LocalDateRange 可以报价 LocalDateRange.ofClosed().

LocalDateRange ldr = LocalDateRange.ofClosed(     // `ofClosed` vs `of`.
    LocalDate.of( 2018 , Month.JANUARY , 1 ) ,
    YearMonth( 2018 , Month.JUNE ).atEndOfMonth() // Fully-closed.
) ;

您可以通过 Period class.

LocalDateRange 中获取月数
LocalDateRange ldr = LocalDateRange.of( 
    LocalDate.of( 2018 , Month.JANUARY , 1 ) ,
    Period.ofMonths( 6 ) 
) ;
Period p = ldr.toPeriod() ; 
long totalMonths = p.toTotalMonths() ;  // 6

关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

您可以直接与您的数据库交换 java.time object。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* classes.

在哪里获取java.time classes?

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

其他解决方案:

int getInclusiveMonthsDiff(LocalDate startDate, LocalDate endDate) {
        return (endDate.monthValue + endDate.year * 12) - (startDate.monthValue + startDate.year * 12) + 1
}