美文网首页
网页应用的多时区日期格式的处理

网页应用的多时区日期格式的处理

作者: Lyudmilalala | 来源:发表于2023-06-06 17:39 被阅读0次
    1. 前端js的Date类型和后端SpringBoot的Date类型在网络传输中是自动基于UNIX时间戳封装的,因此只要对应使用这两种数据类型,就不会带来时区差异

    2. 无论js,java,python,在未指定时区的情况下,将Date类型的对象格式化为string时,均会使用机器本地的时区,可以通过修改电脑的时区,再格式化同一个时间戳的Date对象来测试

    修改电脑时区前后对比.png

    在将一个字符串转为Date对象时,也会默认使用当前电脑的时区。但需要注意的是,转换成的Date对象本身是不带时区属性的,只是将字符串解释成时间戳的过程中使用了本地时区

    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    Date d = formatter.parse("2023-06-06 10:48:24.476");
    log.info("d timestamp = {}", d.getTime());   
    # 东八区  d timestamp = 1686019704476
    
    1. mysql的datetime类型保存格式为yyyy-MM-dd HH:mm:ss(如使用datetime(3)则为yyyy-MM-dd HH:mm:ss.SSS)的日期时间字符串,不带时区信息,保存值以mysql server的时区信息为准。因此如果有多台不同的mysql server,最好统一所有的时区为UTC
    mysql datetime.png
    1. 通过ORM将mysql数据库里的datetime列转为java中Date对象时,会使用字符串解析的方法封装成Date类,当java向mysql数据库传递Date类时,会将Date类转为字符串datetime格式传递

    当java应用与mysql数据库之间的连接有serverTimezone参数时,此处的server指的是mysql数据库服务器,而连接到数据库的应用,虽然有可能是个SpringBoot服务器,但从ORM的角度依然是客户端。serverTimezone参数会让java应用认为mysql数据库的时区为该指定的时区,故在根据yyyy-MM-dd HH:mm:ss字符串计算出时间戳后,会额外加减运行java应用的当前电脑与此时区的时间差。

    如数据库在一台东八区的电脑上,其中有·datetime·数据

    mysql> SELECT * FROM date_test;
    +----+-------------------------+
    | id | create_time             | 
    +----+-------------------------+
    |  1 | 2023-06-06 10:48:24.476 | 
    +----+-------------------------+
    

    用同一台东八区的电脑上的SpringBoot应用去连接它,连接配置为

    spring.datasource.url = jdbc:mysql://127.0.0.1:3306/spinq_cloud_computing?characterEncoding=utf-8&serverTimezone=UTC
    

    数据库映射类如下

    package cn.spinq.cloud.mini.entity;
    
    import lombok.Data;
    import javax.persistence.*;
    import java.util.Date;
    
    @Entity
    @Data
    @Table(name = "date_test")
    public class DateTest {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id", nullable = false)
        private long id;
    
        @Column(name = "create_time", nullable = false, columnDefinition = "datetime(3)")
        private Date createTime;
    
        public DateTest() {
            Date d = new Date();
            createTime = d;
        }
    
        public DateTest(Date d) {
            createTime = d;
        }
    

    运行如下测试

        @Test
        void DateDiffTest() throws ParseException {
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            Optional<DateTest> d1Op = dateTestRepository.findById(new Long(1));
            Date d1 = d1Op.get().getCreateTime();
            log.info("d1 is = {}, shown by str = {}", d1.getTime(), formatter.format(d1));
    
            Date d2 = formatter.parse("2023-06-06 10:48:24.476");
            log.info("unwritten d2 is = {}, shown by str = {}", d2.getTime(), formatter.format(d2));
    
            DateTest d2Test = dateTestRepository.save(new DateTest(d2));
            log.info("written d2 is = {}, shown by str = {}", d2Test.getCreateTime().getTime(), formatter.format(d2Test.getCreateTime()));
    
            log.info("d2 is {} hours more than d1.", (d2.getTime() - d1.getTime())/(3600*1000));
        }
    

    获取到结果

    INFO  main Method: DateDiffTest - d1 is = 1686048504476, shown by str = 2023-06-06 18:48:24.476
    INFO  main Method: DateDiffTest - unwritten d2 is = 1686019704476, shown by str = 2023-06-06 10:48:24.476
    INFO  main Method: DateDiffTest - written d2 is = 1686019704476, shown by str = 2023-06-06 10:48:24.476
    INFO  main Method: DateDiffTest - d1 is 8 hours more than d2.
    

    可知此处SpringBoot应用将mysql数据库中id=1的2023-06-06 10:48:24.476当做了UTC时区的时间来处理,生成Date对象时为其多加了8个小时

    查看数据库,发现插入的第二条数据如下

    mysql> SELECT * FROM date_test;
    +----+-------------------------+
    | id | create_time             | 
    +----+-------------------------+
    |  1 | 2023-06-06 10:48:24.476 | 
    |  2 | 2023-06-06 02:48:24.476 | 
    +----+-------------------------+
    

    可知在插入d2的时候,因为java判断mysql需要UTC的时间,因此将程序中当前时区的时间减去了8个小时,再存入了数据库

    1. 当mysql数据库时区和serverTimezone固定,改变java应用的时区,发现均可向mysql server写入正确的东八区时间字符串,读出后,Date对象的时间戳值保持一致

    固定mysql server时区为GMT+8,serverTimezone=Asia/Shanghai时,mysql中放人时间2023-01-01 00:00:00.000,当前电脑运行如下测试用例

        @Test
        void DateDiffTest() {
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            Optional<DateTest> d1Op = dateTestRepository.findById(new Long(1));
            Date d1 = d1Op.get().getCreateTime();
            log.info("d1 is = {}, shown by str = {}", d1.getTime(), formatter.format(d1));
        }
    

    可以发现,字符串格式的时间不同,但时间戳保持一致

    // 电脑时区设置为东八区
    INFO  main Method: DateDiffTest - d1 is = 1672502400000, shown by str = 2023-01-01 00:00:00.000
    // 电脑时区设置为西五区,包含夏令时
    INFO  main Method: DateDiffTest - d1 is = 1672502400000, shown by str = 2022-12-31 11:00:00.000
    

    总结

    1. javascriptjava中Date类的默认封装拆箱都可以获取到正确的时间,无需做特殊处理【因为用json传递数据,因此并不一定传递的真的是时间戳,也可能是带了时区信息的字符串,此处还未抓包求证,但总之可能正常传输】
    2. 多地区提供服务时,应该控制所有数据库为同一时区,并将jdbc连接的serverTimezone参数设置为与数据库时区一致,则不同时区的服务器都可以获取正确的日期数据

    参考资料1

    相关文章

      网友评论

          本文标题:网页应用的多时区日期格式的处理

          本文链接:https://www.haomeiwen.com/subject/fvniedtx.html