现象
项目中计算证书开始和结束两个时间点之间的时间差(天数),同样的代码在国内正常运行,计算的天数和预期一致;但是在欧洲运行时计算的天数比国内的天数少了一天,导致校验失败。
代码逻辑如下:
private static void calcDaysWithDate(String startTime, String endTime) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date start = format.parse(startTime);
Date end = format.parse(endTime);
long oneDayTime = 86_400_000L;
long between = (end.getTime() - start.getTime()) / oneDayTime;
System.out.println("Date Api: " + startTime + " to " + endTime + " : " + between + " days.");
}
将证书的开始和结束时间字符串转为Date类型,然后获取相应的时间戳,最后时间戳相减除以一天毫秒数,得到两个时间点之前相差的天数。
该代码之前在国内和欧洲运行都没问题,直到最近的一次修改,修改原因是证书即将到期,导致结束时间不能向后延,在新证书申请下来之前,暂时将时间间隔缩短了三个月,之前都是整年的间隔。刚开始考虑是否是时差导致的,但是转念一想这里计算的是时间差,不是时间点;如果有时差,开始时间和结束时间都有时差,结果也应该是正常的。
分析了好久也没往夏令时和冬令时上想,但是问题还是得解决,看着上面的代码使用的是老的Date类,本着死马当活马医的想法,就用1.8的LocalDate试试,结果还真有效果。
代码如下:
private static void calcDaysWithLocalDate(String startTime, String endTime){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d");
LocalDate start = LocalDate.parse(startTime, formatter);
LocalDate end = LocalDate.parse(endTime, formatter);
long between = ChronoUnit.DAYS.between(start, end);
System.out.println("LocalDate Api: " + startTime + " to " + endTime + " : " + between + " days.");
}
受此启发,然后就将代码中计算的时间粒度变小一些,计算两个时间点相差的小时数,原因就在这里找到了,同样的代码在欧洲比国内少了一个小时,因为代码中按天计算并使用long来接收结果,导致结果取整就少了一天。
到这里就明了了,就是代码逻辑不严谨以及夏令时和冬令时的问题导致的,但是为什么之前没有遇到这个问题呢?
分析发现,因为之前证书的有效期都是整年,开始时间在冬令时,结束时间也在冬令时,就正好不会出现这个问题。而本次修改将有效期减少了三个月,导致存在开始时间可能在冬令时,而结束时间在夏令时的情况,这就导致了两个时间点之间的时间差少了一个小时。实际情况也是在11月才开始在生产出现该问题。
至此,问题也得到了解决,修改代码,使用1.8新的api – LocalDate实现。
完整代码如下:
public class DateDemo {
public static void main(String[] args) throws ParseException {
String startDate = "2021-11-10";
String endDate = "2041-08-07";
calcDaysWithDate(startDate, endDate);
calcDaysWithLocalDate(startDate, endDate);
String startDateTime = "2021-11-10 00:00:00";
String endDateTime = "2041-08-07 00:00:00";
calcDaysWithLocalDateTime(startDateTime, endDateTime);
calcDaysWithDateTime(startDateTime, endDateTime);
}
private static void calcDaysWithDate(String startTime, String endTime) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date start = format.parse(startTime);
Date end = format.parse(endTime);
long oneDayTime = 86_400_000L;
long between = (end.getTime() - start.getTime()) / oneDayTime;
System.out.println("Date Api: " + startTime + " to " + endTime + " : " + between + " days.");
}
private static void calcDaysWithDateTime(String startTime, String endTime) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date start = format.parse(startTime);
Date end = format.parse(endTime);
long oneDayTime = 3_600_000L;
long between = (end.getTime() - start.getTime()) / oneDayTime;
System.out.println("DateTime Api: " + startTime + " to " + endTime + " : " + between + " hours.");
}
private static void calcDaysWithLocalDate(String startTime, String endTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d");
LocalDate start = LocalDate.parse(startTime, formatter);
LocalDate end = LocalDate.parse(endTime, formatter);
long between = ChronoUnit.DAYS.between(start, end);
System.out.println("LocalDate Api: " + startTime + " to " + endTime + " : " + between + " days.");
}
private static void calcDaysWithLocalDateTime(String startTime, String endTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime start = LocalDateTime.parse(startTime, formatter);
LocalDateTime end = LocalDateTime.parse(endTime, formatter);
long between = ChronoUnit.HOURS.between(start, end);
System.out.println("LocalDateTime Api: " + startTime + " to " + endTime + " : " + between + " hours.");
}
}