记录一次夏令时和冬令时导致的项目BUG

现象

项目中计算证书开始和结束两个时间点之间的时间差(天数),同样的代码在国内正常运行,计算的天数和预期一致;但是在欧洲运行时计算的天数比国内的天数少了一天,导致校验失败。

代码逻辑如下:

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.");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值