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"
我这里有两个问题:
- 我计算 41 年和负数哪里出错了?
- 我有更好的方法吗?我当前的设置不考虑闰年或 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();
}
- 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。
- 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.
我正在使用的 Duration
的 toXxxPart
方法在 Java 9 中介绍。如果您使用 Java 8(或 ThreeTen Backport)打印分钟秒有点复杂。搜索 java format duration
或类似内容以了解操作方法。
我还没有考虑夏令时。为此,我们需要知道最早的 运行 时间字符串的时区,然后使用 ZonedDateTime
而不是 LocalDateTime
。否则代码会非常相似。
链接
- ThreeTen Extra
- Oracle Tutorial: Date Time 解释如何使用 java.time
我知道这个问题之前已经在这里被问过很多次了,但是我没能找到任何关于我的案例的具体信息。我正在尝试查找当前日期时间和之前日期时间之间的差异,每个日期时间的格式均为 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"
我这里有两个问题:
- 我计算 41 年和负数哪里出错了?
- 我有更好的方法吗?我当前的设置不考虑闰年或 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();
}
- Where am I going wrong in my calculations 41 years and a negative number?
除了使用众所周知的麻烦和过时的 SimpleDateFormat
class 和同样过时的 Date
之外,您的代码中还有以下错误:
- 您正在将
08.0
解析为 8 秒 0 秒。在我的 JDK-11SimpleDateFormat
上选择 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。
- 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.
我正在使用的 Duration
的 toXxxPart
方法在 Java 9 中介绍。如果您使用 Java 8(或 ThreeTen Backport)打印分钟秒有点复杂。搜索 java format duration
或类似内容以了解操作方法。
我还没有考虑夏令时。为此,我们需要知道最早的 运行 时间字符串的时区,然后使用 ZonedDateTime
而不是 LocalDateTime
。否则代码会非常相似。
链接
- ThreeTen Extra
- Oracle Tutorial: Date Time 解释如何使用 java.time