代码之家  ›  专栏  ›  技术社区  ›  Deepboy

java 8-chronounit.months.between(fromdate,todate)不能按预期工作

  •  3
  • Deepboy  · 技术社区  · 6 年前

    我需要找出两个日期之间的月数,包括两个月。我正在使用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 回复  |  直到 6 年前
        1
  •  2
  •   Basil Bourque    6 年前

    半开

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

    我想补充一个在日期时间工作中常用的定义时间跨度的替代方法:半开放。在半开放的方法中,开始是 包容的 当结局是 排他性 . 因此,上半年的月份(1月、2月、3月、4月和6月)从1月1日开始,一直到,但不包括, 七月 1。

    有时我们在日常生活中会直觉地使用这种方法。例如,如果一个教室的孩子中午12:00到下午1:00之间休息吃午饭,学生就应该坐在他们的座位上 之前 下午一点钟响了。午休时间为下午1点,但不包括在内。

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

    现代的 Java.时间 类在定义时间跨度时使用此方法。

    LocalDateRange

    奥列夫的回答明智地建议你看看 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.
    ) ;
    

    但我不推荐,如果你坚持使用完全封闭的方法,开始和结束都是 包容的 , the 本地日期范围 报盘 LocalDateRange.ofClosed() .

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

    你可以从 本地日期范围 通过 时期 班级。

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

    关于 Java.时间

    这个 java.time 框架是在Java 8和之后构建的。这些类取代了麻烦的旧类 legacy 日期时间类,如 java.util.Date , Calendar 和; SimpleDateFormat .

    这个 Joda-Time 项目,现在在 maintenance mode ,建议迁移到 java.time 类。

    要了解更多信息,请参阅 Oracle Tutorial . 和搜索堆栈溢出的许多例子和解释。规格是 JSR 310 .

    你可以交换 Java.时间 直接使用数据库的对象。使用A JDBC driver 符合 JDBC 4.2 或以后。不需要弦,不需要 java.sql.* 类。

    在哪里获得java.time类?

    这个 ThreeTen-Extra project使用其他类扩展java.time。这个项目是将来可能添加java.time的一个试验场。你可以在这里找到一些有用的类,比如 Interval , YearWeek , YearQuarter more .

        2
  •  3
  •   davidxxx    6 年前

    就日历而言,您的问题是可以理解的:这两个比较代表一个完整的月。
    但是 java.time.temporal.ChronoUnit.between 不是以这种方式推理,而是以 成套设备 .
    根据它的javadoc,结果是预期的:

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

    这个 LocalDate.until javadoc,由 ChronoUnit.between() 对您的案例来说更为明确,并给出了一个有趣的例子 between() 用于 MONTHS 作为 TemporalUnit :

    计算返回一个整数,表示 两个日期之间的完整单位。例如,中的金额 2012年6月15日至2012年8月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 = & gt; 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
    = & gt; 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 = & gt; 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);
    }
    
        3
  •  2
  •   Anonymous    6 年前

    中后卫 the other answer 已经解释了为什么您的代码给出了意外的结果。请允许我补充一点,如果你对几个月而不是日期感兴趣, 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 一个月。一月份是有效的,但都不行 08 也不 09 会在8月或9月工作,所以养成这个坏习惯。Java(和许多其他语言)理解以 0 作为 octal numbers .