夏令时踩坑记录

    记录一个数据上传下载夏令时踩坑经历。客户端将数据上传到平台,客户端再下载该数据,发现人的生日居然变成了前一天的23点,相差1小时

一、时区说明

    Asia/Shanghai: 兼容夏令时,下面简称 "上海"
    GMT+8: 不兼容夏令时,比夏令时时间慢一个小时, 下午简称"东八"

二、转换丢失一小时说明

    假设有个时间为1991-08-08 00:00:00 的date对象,当前jvm时区 为 "上海"
    (1)用"上海"时区来转换,其时间还是1991-08-08 00:00,因为时区一样
    (2)用"东八"来转换时,如果是基于时间戳或者是Date对象进行转换,那么时间变成了 1991-08-07 23:00
    (3)只要时区不一致,夏令时就存在 +1 或者 -1的问题
 
 

三、客户端上传分析

    代码分析:

        整理流程如下
            (1)获取客户端上传的加密的xml
            (2)xml解密
            (3)交给SpringMVC消息转换器将xml转换成对象,这里用自定义xml中Date类型适配器,底层的DateUtils用系统默认时区进行转换
            (4)将Date保存到数据库
            特别说明:客户端上传的生日是标准的 yyyy-MM-dd 
        通过分析可知: 上传过程没有发生过时区转换,且由于时间用的字符串传输,不可能出现时间发生改变的问题    
    

    生产环境参数:

        (1)springboot项目的application.yml中配置了GMT+8 但这个配置只对jackSon生效,上传过程没有用到jackson,所以这个GMT+8不影响
        (2)由于上传过程中客户端传的是字符串,而不是时间戳。字符串天然就屏蔽了这种时区问题
        (3)客户端时区GMT+8,通过客户端同事用代码获取一个夏令时的时间戳推导出时区为GMT+8
        (4)jvm默认用了上海
        虽然客户端用的GMT+8而jvm用的上海,但是传输用的字符串,客户端和服务端之间的传输和时区没关系
    

    小结:

        上传过程中: 由于时间使用字符串传输,客户端,服务端时区均不会影响上传结果
 

四、客户端下载代码分析

    代码分析:

        整体流程
            (1)Controller返回java对象,对象里面的生日用的系统默认时区
            (2)通过web过滤器,获取到Response对象从对象取出返回的json
            (3)上面的这个json,其实是java对象(Date)通过jackson消息转换器转换成了json字符串,这个jackson的转换用的是yml中配置的时区
            (4)json-->java 对象(Date)转换,用了fastjson,用的系统默认时区
            (5)java对象-->xml, 用了系统默认时区
            (6)加密返回给客户端
        特殊说明:返回给客户端时,时间用的是yyyy-MM-dd HH:mm:ss字符串格式
        
        分析:
            -- (3)中java对象转jackson 用的yml中配置的时区
            -- (4)(5)这个环节,没有发生过时区转换,这个环节时间不会变。
                意味着客户端看到的时间内容,就是(4)中java对象的时间内容
            -- (3)->(4)这个环节,用的是系统默认时区来转的,说明(4)中java对象的时间内容就是(3)中json字符串里面的时间内容
            -- 如果(1)->(3)的这个java对象转json时,存在时区转换,则会导致(3)中json串中的时间和(1)java对象时间不一致
            

    生产环境参数:

        (1)yml中配置了GMT
        (2)jvm没有配置,则默认用了上海
        (3)客户端用的东八区
        (4)时间用的字符串来传输
        

    下载时间不对原因及解决方案:

        原因:
            -- 由于系统用的上海,而yml中配置为GMT+8,导致代码中的(1)->(3)发生了 上海-->GMT+8的时区转换
            -- 上海转到GMT+8 夏令时会丢 1小时
        解决方案:
            yml配置为上海,且系统必须保证为上海
            

五、总结

    (1)上传过程中,时间用字符串传输,不存在时区转换,不会有问题
    (2)下载过程,yml时区如果和jvm系统时区不一致时,会发生时区转换。不是加1小时,就是减一小时。
    (3)yml时区和系统时区必须保持一致
    (4)建议yml中配置上海,然后用java代码在SpringBoot启动类中添加项目启动后把系统默认时区设置为上海,参考下面代码
    @PostConstruct
    public void init () {
        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("Asia/Shanghai")));
    }

六、后记

  由于一些特殊的夏今时日期(如1948-05-01) 在idk不的版本Date值不同,把系统和yml时区强制设置成GMT+8 解决这个夏令时的问题

posted @ 2021-12-11 16:32  zeng1994  阅读(414)  评论(1编辑  收藏  举报