【问题标题】:Weird Value When Calculating Date Difference计算日期差异时的奇怪值
【发布时间】:2018-05-22 20:38:10
【问题描述】:

我一直在使用以下代码来计算两个日期之间的差异,但我遇到了一个不寻常的错误:

如果我通过以下日期:

d1 = 2017 年 8 月 12 日 d2 = 2022 年 7 月 31 日

它返回:24055..

结果不应该是月数吗?

 public static int CalcDateDiff( java.util.Date  date1,  java.util.Date  date2) {
    if(date1 == null || date2 ==null )
    {
        if(date2 == null)
        {
              Calendar d1 = Calendar.getInstance();
                d1.setTime(date1);
                final Calendar d2 = Calendar.getInstance();
                int diff = (d2.get(Calendar.YEAR) - d1.get(Calendar.YEAR)) * 12 + d2.get(Calendar.MONTH) - d1.get(Calendar.MONTH);

                return diff;
        }
        else
            return -1;
    }
    else
    {

            Calendar d1 = Calendar.getInstance();
            d1.setTime(date1);
            final Calendar d2 = Calendar.getInstance();
            d2.setTime(date2);
            int diff = (d2.get(Calendar.YEAR) - d1.get(Calendar.YEAR)) * 12 + d2.get(Calendar.MONTH) - d1.get(Calendar.MONTH);

            return diff;
        }
}

提前致谢!

【问题讨论】:

  • 请提供minimal reproducible example,并提供有关您所在时区的详细信息。这里可能会发生太多事情,无法在不查看更多详细信息的情况下为您提供帮助。跨度>
  • (例如,如果一个日期是公元 17 年,一个日期是公元 2022 年,你会大致得到这个答案。)
  • 我运行它并得到 55 作为输出...ideone.com/j06to9 我认为我们需要查看更多代码,或者您进行更多调试。
  • 我也得了 55。 - 年、月和日可能不是唯一的字段。如果打印 d1.getTimeInMillis() 和 d2.getTimeInMillis(),你会得到什么?
  • 展示你是如何创建date1date2

标签: java date date-difference java.util.date java.util.calendar


【解决方案1】:

最佳解决方案:

如果您已经有 java.sql.Date 实例,那么

final Date d1 = new java.sql.Date(2017 - 1900, 12, 8);
final Date d2 = new java.sql.Date(2022 - 1900, 07, 31);

我知道我在java.sql.Date 中使用了一个已弃用的构造函数,这只是为了方便以最少的代码行获得我需要的东西。 切勿在生产代码中使用此构造函数!

获得所需内容的最简单最直接的自我记录方式:

final long monthsBetween ChronoUnit.MONTHS.between(d1.toLocalDate(),d2.toLocalDate()) + 1;

因为使用这种方法,您不必纠结TimeZone 信息,因为对于以这种方式创建的LocalDate 实例,所有这些信息都保证是正确的。

由于某种原因,只有 java.sql.Date 只有 .toLocalDate(),这对您有好处,因为这是您从数据库中返回的内容。

public LocalDate toLocalDate() 将此 Date 对象转换为 LocalDate 转换创建一个 LocalDate 代表相同的 日期值作为本地时区中的此日期

返回:表示相同日期值的 LocalDate 对象 自从: 1.8


对代码更正的评论:

正确的公式是:

(y2 - y1) * 12 + (m2 - m1) + 1

此外,您的代码过于复杂并且使用了非常古老的类。

Java 8 解决方案和您的 Java 7 兼容更正解决方案:

public class Q47717075
{
    /*
    https://stackoverflow.com/questions/1086396/java-date-month-difference
     */
    public static void main(@Nonnull final String[] args)
    {
        final Date d1 = new java.sql.Date(2017 - 1900, 12, 8);
        final Date d2 = new java.sql.Date(2022 - 1900, 07, 31);
        System.out.println(CalcDateDiff(d1, d2));
        /* Obtains an instance of Instant from a text string such as 2007-12-03T10:15:30.00Z. */
        System.out.println(intervalInMonths(LocalDate.of(2017,12,8), LocalDate.of(2022,7,31)));
        System.out.println(ChronoUnit.MONTHS.between(d1.toLocalDate(),d2.toLocalDate()) + 1);
    }


    /*
       Alternate Java 8 version
     */
    public static int intervalInMonths(@Nonnull final LocalDate i1, @Nonnull final LocalDate i2)
    {
        final Period p = Period.between(i1, i2);
        return (p.getYears() * 12) + p.getMonths() + 1;
    }

    /**
     * Your versin corrected
     */
    public static int CalcDateDiff(@Nonnull final Date date1, @Nonnull final Date date2)
    {
        final Calendar d1 = Calendar.getInstance();
        d1.setTime(date1);
        final Calendar d2 = Calendar.getInstance();
        d2.setTime(date2);

        return ((d2.get(YEAR) - d1.get(YEAR)) * 12 + (d2.get(MONTH) - d1.get(MONTH)) + 1);
    }
}

正确的输出是:

56
56
56

【讨论】:

  • 从另一个答案继续讨论:我在美国/蒙得维的亚时区拍摄了 8 月 1 日和 12 月 1 日。它们具有一天的粒度并且位于同一时区。从美国/圣地亚哥时区的 JVM 调用更正后的 CalcDateDiff() 会产生 6 个月的时间。从美国/蒙克顿时区的 JVM 只需 4 个月。时区很重要。查看我的测试代码run live on ideone
  • 我在另一个答案上从一开始就一遍又一遍地说这个!只要两个实例的时区相同,答案就是正确的,你不明白,你不是为了理解而阅读,只是停下来。
  • 两个实例的时区相同是什么意思(当Date 不包含时区时)?我的测试代码中两个实例的时区是一样的,还是不同的,后一种情况,有什么区别?
【解决方案2】:

tl;博士

ChronoUnit.MONTHS.between(                                           // Use enum method to calculate elapsed time.
    date1.toInstant().atZone( ZoneId.of( "Africa/Casablanca " ) ) ,  // Convert from legacy `Date` class to modern `java.time.Instant` class. Adjust from UTC to a specific time zone. 
    date2.toInstant().atZone( ZoneId.of( "Africa/Casablanca " ) ) 
)                                                                    // Return a number of months elapsed. 

详情

Answer by Roberson 很好。这是另一种选择。

您使用的麻烦Date 类现在已经过时了。该遗留类被 java.time 包中的Instant 替换。两者都代表一个时刻,UTC 中时间轴上的一个点。现代类的分辨率为nanoseconds 而不是milliseconds

使用添加到旧类的新方法进行转换。

Instant instant = myUtilDate.toInstant() ;

要确定月份数,我们需要日期。确定日期需要时区。对于任何给定的时刻,日期在全球范围内因区域而异。

ZoneId z = ZoneId.of( "Pacific/Auckland " ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

ChronoUnit 枚举提供了between 计算经过时间的方法。

long months = ChronoUnit.MONTHS.between( zdt1 , zdt2 ) ;

【讨论】:

  • 时区信息只是增加了复杂性,与日期之间的月份无关。时区信息仅在处理小于一天的间隔时才相关。 LocalDate 适用于所有情况下,间隔大于 Hour
  • @OleV.V. - 如果您使用的计算的分辨率不低于天数,则时区无关紧要,只要两者具有相同的时区,在 java.util.Date 的情况下是可以保证的。这个答案只是一堆关于时区信息的噪音,与手头的计算完全无关。正如我的回答所示,当您使用 LocalDate 时,您会得到完全相同的输出,从而避免了所有这些噪音。
  • @JarrodRoberson,如果您愿意,您可以相信,但事实并非如此。如果一个时区有夏令时 (DST) 而另一个没有,或者交叉日期不同,那么您选择哪个时区(或者让 JVM 为您选择哪个时区)可能仍然会有所不同。我们处于不容易发现错误的极端情况。此外,如果您的方法仅适用于某个时区午夜的Dates,您至少应该说明这一点。
  • @OleV.V. - 只要它们都具有相同的时区,您不理解哪一部分?我的方法与任一实例的时间无关只要它们都具有相同的时区,您将获得相同的月数因为 month 比任何时间分量都多。默认情况下很简单java.util.Date,因此java.sql.DateLocalDate 都具有完全相同的时区,因为您无法设置它,它默认为本地时区。所以你的无知并不能衡量我的知识,这是基于文档的简单逻辑。
猜你喜欢
  • 2012-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-19
  • 2018-04-24
  • 1970-01-01
相关资源
最近更新 更多