Java 当前日期与过去日期之间的年、月、日、小时、分钟、秒之差

Java Difference between current date and past date in Years, Months, Days, Hours, Minutes, Seconds

我知道这个问题之前已经在这里被问过很多次了,但是我没能找到任何关于我的案例的具体信息。我正在尝试查找当前日期时间和之前日期时间之间的差异,每个日期时间的格式均为 yyyy-MM-dd HH:mm:ss.s。根据 here 给出的答案,我想出了以下代码:

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.s");
String earliestRunTime = "2017-12-16 01:30:08.0";
Date currentDate = new Date();
log.info("Current Date: {}", format.format(currentDate));

try {
    Date earliestDate = format.parse(earliestRunTime);

    long diff = currentDate.getTime() - earliestDate.getTime();

    long diffSeconds = diff / 1000 % 60;
    long diffMinutes = diff / (60 * 1000) % 60;
    long diffHours = diff / (60 * 60 * 1000) % 24;
    long diffDays = diff / (24 * 60 * 60 * 1000) % 30;
    long diffMonths = diff / (30 * 24 * 60 * 60 * 1000) % 12;
    long diffYears = diff / (12 * 30 * 24 * 60 * 60 * 1000);

    return String.format("%s years, %s months, %s days, %s hours, %s minutes, %s seconds",
            diffYears, diffMonths, diffDays, diffHours, diffMinutes, diffSeconds);


    }
catch (Exception e) {
    e.printStackTrace();
    return e.getMessage();
}

当我运行代码时,JSONreturns结果如下:

lifetime: "41 years, -1 months, 14 days, 9 hours, 42 minutes, 37 seconds"

我这里有两个问题:

  1. 我计算 41 年和负数哪里出错了?
  2. 我有更好的方法吗?我当前的设置不考虑闰年或 365 天的年份,我需要考虑这些。

41年是负数我哪里算错了?

因为分母会溢出。您需要使用 Long:

long diffMonths = diff / (30 * 24 * 60 * 60 * 1000L) % 12; //Notice the L at the end
long diffYears = diff / (12 * 30 * 24 * 60 * 60 * 1000L); //Notice the L at the end

另请注意,12 * 30 是一年中天数的非常糟糕的近似值。

我有更好的方法吗?

是的。使用 Java 的持续时间 api 8. https://www.mkyong.com/java8/java-8-period-and-duration-examples/

很难给出准确的答案,因为问题有点模糊。例如 - 如果一年中的某一年是闰年,而您正在比较日期 2020/03/28 和 2021/03/28,结果应该是什么? 1年还是1年1天? (2020年是闰年所以03/28之后还有03/29)

使用与您相同的方法,您需要将分母明确标识为长值。目前,它假定它们是整数,这会导致数字溢出——这意味着计算的值对于整数来说太大了。这可以解释为什么你得到 negative/arbitrary 值。修复很简单:

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.s");
String earliestRunTime = "2017-12-16 01:30:08.0";
Date currentDate = new Date();
log.info("Current Date: {}" + format.format(currentDate));

try {
    Date earliestDate = format.parse(earliestRunTime);

    long diff = currentDate.getTime() - earliestDate.getTime();

    long diffSeconds = diff / 1000L % 60L;
    long diffMinutes = diff / (60L * 1000L) % 60L;
    long diffHours = diff / (60L * 60L * 1000L) % 24L;
    long diffDays = diff / (24L * 60L * 60L * 1000L) % 30L;
    long diffMonths = diff / (30L * 24L * 60L * 60L * 1000L) % 12L;
    long diffYears = diff / (12L * 30L * 24L * 60L * 60L * 1000L);

    return String.format("%s years, %s months, %s days, %s hours, %s minutes, %s seconds",
            diffYears, diffMonths, diffDays, diffHours, diffMinutes, diffSeconds);


}
catch (Exception e) {
    e.printStackTrace();
    return e.getMessage();
}
  1. Where am I going wrong in my calculations 41 years and a negative number?

除了使用众所周知的麻烦和过时的 SimpleDateFormat class 和同样过时的 Date 之外,您的代码中还有以下错误:

  • 您正在将 08.0 解析为 8 秒 0 秒。在我的 JDK-11 SimpleDateFormat 上选择 0 秒并丢弃我认为正确的 8 秒。 SimpleDateFormat 无法解析秒的一位小数(只能精确地解析三位小数),因此解决此错误的方法是完全丢弃 SimpleDateFormat
  • 正如其他人所说,您的乘法运算出现 int 溢出。例如,30 * 24 * 60 * 60 * 1000 应该给出 2 592 000 000,但是由于 int 不能保存这个数字,所以你得到 -1 702 967 296。由于这是一个负数,下面的除法会给你一个负数的月数。
  • 正如 Solomon Slow 在评论中指出的那样,一个月可能是 28、29、30 或 31 天。将所有月份设置为 30 天时,您可能会面临天数和月份数不正确的风险,最后还有年数。当我今天 运行 你的代码时,正确答案应该是 1 年,4 个月,13 天,但我得到的是 19 天,6 天太多了。
  • 您没有考虑夏令时 (DST) 和其他时间异常。这些可能会导致一天为 23 或 25 小时,从而给出错误。

或者总结一下:你的错误是你试图“手工”进行计算。日期和时间数学太复杂且容易出错,无法执行此操作。您应该始终将它留给经过充分验证的库 classes。

  1. Is there a better way for me to do this? My current setup does not consider leap years or a 365 day year, and I need to take these into account.

是的,有更好的方法。最好的方法可能是使用 ThreeTen Extra 项目中的 PeriodDuration class,请参阅下面的 link。我现在不打算在我的电脑上安装那个库,所以我只会展示使用内置 classes:

的优秀现代解决方案
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.S");
    LocalDateTime currentDateTime = LocalDateTime.now(ZoneId.of("Australia/Sydney"));
    String earliestRunTime = "2017-12-16 01:30:08.0";
    LocalDateTime earliestDateTime = LocalDateTime.parse(earliestRunTime, dtf);

    // We want to find a period (years, months, days) and a duration (hours, minutes, seconds).
    // To do that we cut at the greatest possible whole number of days
    // and then measure the period before the cut and the duration after it.
    LocalDateTime cut = earliestDateTime.plusDays(
            ChronoUnit.DAYS.between(earliestDateTime, currentDateTime));

    Period p = Period.between(earliestDateTime.toLocalDate(), cut.toLocalDate());
    Duration d = Duration.between(cut, currentDateTime);

    String result = String.format("%s years, %s months, %s days, %s hours, %s minutes, %s seconds",
            p.getYears(), p.getMonths(), p.getDays(),
            d.toHours(), d.toMinutesPart(), d.toSecondsPart());

    System.out.println(result);

当我运行刚才的代码得到:

1 years, 4 months, 13 days, 19 hours, 26 minutes, 7 seconds

在java.time中,现代Java日期和时间API,Period是年月日的量,Duration 是小时、分钟、秒和秒的分数(精确到纳秒)。既然你两个都想要,我就用两个 classes.

我正在使用的 DurationtoXxxPart 方法在 Java 9 中介绍。如果您使用 Java 8(或 ThreeTen Backport)打印分钟秒有点复杂。搜索 java format duration 或类似内容以了解操作方法。

我还没有考虑夏令时。为此,我们需要知道最早的 运行 时间字符串的时区,然后使用 ZonedDateTime 而不是 LocalDateTime。否则代码会非常相似。

链接