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

时间:2018-06-16 18:19:25

标签: datetime java-8 duration

我需要找到不。两个月之间的月份,包括两个月。我正在使用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月(1月,2月,3月,4月,5月,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月(1月,2月,3月,4月,5月,6月,7月) 实际:7

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

由于

3 个答案:

答案 0 :(得分:3)

就日历而言,您的问题是可以理解的:两次比较代表完整的一个月 但是java.time.temporal.ChronoUnit.between并没有以这种方式推理,而是以完整单位
根据其javadoc预期结果:

  

计算返回一个整数,表示数字   两个时间之间的完整单位。例如,金额   11:30到13:29之间的时间只有一个小时   一分钟不到两个小时。

LocalDate.until引用的ChronoUnit.between() javadoc对您的案例更为明确,并且还提供了一个有关between()MONTHS一起用作TemporalUnit的有趣示例1}}:

  

计算返回一个整数,表示数字   两个日期之间的完整单位。例如,金额   2012-06-15和2012-08-14之间的月份仅为一个月   是短短两个月的一天。

在您的工作示例中:

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

关于您的问题:

  

有没有其他方法可以做到这一点?

最简单:将日期设置为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);
}

答案 1 :(得分:2)

<{3}}中的

davidxxx已经解释了为什么您的代码会产生意外结果。请允许我补充一点,如果您对数月而不是日期感兴趣,那么YearMonth课程对您来说是好的和正确的课程:

        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);

输出:

  

*********两个月之间的月数(含):6

这显然也消除了几个月长度不等的问题。

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

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

另外,请勿使用两位数的01一个月。对于1月它是有效的,但0809都不适用于8月或9月,所以这是一个习惯性的习惯。 Java(以及许多其他语言)将以0开头的数字理解为the other answer

答案 2 :(得分:2)

半开

另外两个答案by davidxxxby Ole V.V.都是正确且信息丰富的。

我想补充一下在日期工作中常用的另一种方法,用于定义一段时间:半开放。在半开放方法中,开头是包含,而结尾是独占。因此,今年上半年(1月,2月,3月,4月和6月)的月份将从1月1日开始跟踪,但不包括 7月 1。

有时我们在日常生活中直观地使用这种方法。例如,如果一个孩子的教室从中午12:00到下午1点吃午饭,那么学生应该在的座位之前下午1点钟敲响。午休时间最长,但不包括下午1点。

我相信您会发现半开放方法的一致使用使您的代码更易于阅读,调试和维护。

现代 java.time 类在定义时间范围时使用此方法。

LocalDateRange

Ole V.V.的回答明智地建议你看YearMonth课来帮助你的工作。

另一个有用的课程来自ThreeTen-Extra项目:org.threeten.extra.LocalDateRange。您可以在一个对象中表示整个日期范围。

您可以使用开始日期和六个月的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.
) ;

虽然我不推荐它,但如果你坚持使用完全封闭的方法,其中开头和结尾都是包含LocalDateRange确实提供了LocalDateRange.ofClosed()

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

您可以通过LocalDateRange课程从Period获得几个月的数量。

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框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和&amp; SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

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

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore