Joda时间和Java8时差

Posted

技术标签:

【中文标题】Joda时间和Java8时差【英文标题】:Joda Time and Java8 Time difference 【发布时间】:2018-02-24 05:27:46 【问题描述】:

我正在寻找一种解决方案来计算两个日期之间的月份。我认为 joda 或 java8 time 可以做到。但是当我比较它们时,我发现了一些非常奇怪的东西。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import org.joda.time.Months;

public class DateMain 
    public static void main(String[] args) throws ParseException 

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    Date d1 = simpleDateFormat.parse("2017-01-28 00:00:00.000");
    Date d2 = simpleDateFormat.parse("2017-02-28 00:00:00.000");

    System.out.println("Test Cast 1");
    System.out.println("joda time api result: " + monthsBetweenJoda(d1, d2) + " month");
    System.out.println("java8 time api result: " + monthsBetweenJava8(d1, d2) + " month");

    Date dd1 = simpleDateFormat.parse("2017-01-29 00:00:00.000");
    Date dd2 = simpleDateFormat.parse("2017-02-28 00:00:00.000");

    System.out.println("Test Cast 2");
    System.out.println("joda time api result: " + monthsBetweenJoda(dd1, dd2) + " month");
    System.out.println("java8 time api result: " + monthsBetweenJava8(dd1, dd2) + " month");


public static int monthsBetweenJoda(Date fromDate, Date toDate) 
    if (fromDate == null || toDate == null) 
        throw new IllegalArgumentException();
    
    org.joda.time.LocalDateTime fromLocalDateTime = org.joda.time.LocalDateTime
        .fromDateFields(fromDate);
    org.joda.time.LocalDateTime toLocalDateTime = org.joda.time.LocalDateTime
        .fromDateFields(toDate);
    Months months = Months.monthsBetween(fromLocalDateTime, toLocalDateTime);
    return months.getMonths();


public static long monthsBetweenJava8(Date fromDate, Date toDate) 
    if (fromDate == null || toDate == null) 
        throw new IllegalArgumentException();
    
    LocalDateTime ldt1 = fromDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    LocalDateTime ldt2 = toDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    return ChronoUnit.MONTHS.between(ldt1, ldt2);



输出如下:

Test Cast 1
joda time api result: 1 month
java8 time api result: 1 month
Test Cast 2
joda time api result: 1 month
java8 time api result: 0 month

我对测试用例2感到很困惑,哪个是合理的?

抱歉,这是我的第一个问题。附上完整的代码。

【问题讨论】:

Joda 在我看来不对。它的文档说明了整月的数量,但您的测试与此不一致。 @JoeC:我怀疑它占用了最大的数字,以至于fromDate.plusMonths(x) <= toDate,在向 1 月 29 日添加 1 个月时仍然给出 2 月 28 日是合理的。基本上日历算术很奇怪。 您使用的是java.sql.Date 还是java.util.Date?请记录这一事实,并说明如何生成 Date 对象作为参数对传递。并展示您如何生成输出字符串。 您的输出与java.sql.Datejava.util.Date 上的toString 方法的输出不匹配。 向我们提供MCVE。您显示的代码不完整,因此我们无法复制您的方案。 嗯。 JavaDocs ofr ChonoUnit.between 没有明确提及该方法如何处理可变长度的单位(如 DAYSMONTHSYEARS 或实际上几乎所有这些)。 我的实现最终调用了LocalDateTime.until,后者(对于非基于时间的单位)又调用了LocalDate.until。但是,如果 end.time.isBefore(time) 可能会将结束日期缩短一天 - 也许您的测试就是这种情况(尽管从您的输出中看起来不像)? 【参考方案1】:

Joda-Time 和 Java 8 java.time.* 有不同的算法来确定之间的月数。

java.time.* 仅在当月的日期更大(或下个月)时才声明一个月已经过去。从 2017-01-292017-02-28 很明显,每月的第二天 (28) 小于第一天 (29),因此一个月还没有结束。

Joda-Time 声明一个月已经过去,因为将 1 个月添加到 2017-01-29 将产生 2017-02-28

两者都是合理的算法,但我相信java.time.* 算法更符合人们的期望(这就是为什么我在写java.time.* 时选择了不同的算法)。

【讨论】:

让我感到不安的是,这是 Java 8 时代的决定。因此,每次我们有 2 月 28 日,1 个月只会从 1 月 29 日到 3 月 1 日过去 - 对我来说似乎是错误的,特别是因为在 2 月 28 日在 1 月 29 日的结果中添加一个月。我该如何处理这个问题,所以我可以告诉自己——“放手”?

以上是关于Joda时间和Java8时差的主要内容,如果未能解决你的问题,请参考以下文章

JAVA8新特性随笔

Java 8的日期和时间API

joda-time的使用

Joda Time是否已被Java 8 Date and Time API弃用? (java.time)

计算机程序的思维逻辑 (95) - Java 8的日期和时间API

2020了你还不会Java8新特性?时间日期API&&Java8总结