落地实践之全球化系统多时区日期时间处理问题

2023年 11月 30日 41.7k 0

背景:项目中电商系统需要出海,在亚太和欧洲站点部署,开放给多个国家访问。每个国家的时区不一样,需要在各个国家前端展示不一样的时间。
比如现在中国是东8区,日本是东9区,同一时刻两地上网浏览时间就会相差一个小时。

处理方法

总结成一句话,所有时间的处理需要带上时区或者转换成绝对时间UTC时间来处理
具体如下:

  • 后端系统存储统一用 UTC 时间(包括DB落盘、内部逻辑处理),不应当受用户时区或服务器时区的影响
  • 系统间交互(包括rpc接口以及rest接口)有关时间的处理,统一带上时区。我们是用String类型 yyyyMMddHHmmssz
  • 前端输入、展示的时间,根据具体业务场景进行时区调整(时区从浏览器获取,或者通过国家与时区映射来取),以及精度调整
  • 面对不带时间的日期,要明确区分「纪念日」与「精度不高的绝对时间」两种用途,大部分时候你看到的日期是后者,它也应当用“确定时区的 DateTime”来实现
  • 针对后台定时任务比如过期时间处理或者特定时间发布的场景,后台最好维护国家和时区的映射关系,按照国家依次取到特定时间来一一处理

    java中的时间表示

    jdk8之前

    在Java中,在JDK 8之前,时间表示和时区转换主要使用java.util.Datejava.util.Calendarjava.text.SimpleDateFormat等类进行操作。

  • 时间表示:

    • java.util.DateDate类表示特定的时间点,精确到毫秒级别。它使用自UTC(协调世界时)的纪元时间以来的毫秒数来表示时间。但是,Date类在设计上存在一些问题,因此在JDK 8中引入了新的日期和时间API(java.time包)来替代它。
    • java.util.CalendarCalendar类是用来进行日期和时间计算的抽象类。它提供了处理日期和时间的各种方法,例如获取年、月、日、时、分、秒等。但是,Calendar类的使用不太方便,而且在多线程环境下也存在线程安全问题。
  • 时区转换:

    • java.util.TimeZoneTimeZone类用于表示特定的时区。它提供了静态方法来获取系统默认时区或指定时区的实例,以及将日期和时间转换到指定时区的功能。
    • java.text.SimpleDateFormatSimpleDateFormat类用于将日期和时间格式化为指定模式的字符串,或者将字符串解析为日期和时间。它可以指定时区来进行转换操作。
  • 相关举例如下:
    使用java.util.Datejava.text.SimpleDateFormat进行格式化和解析

            // 格式化日期为字符串
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String formattedDate = sdf.format(date);
            System.out.println("Formatted Date: " + formattedDate);
    
            // 解析字符串为日期
            String dateString = "2021-09-30 15:30:45";
            Date parsedDate = sdf.parse(dateString);
            System.out.println("Parsed Date: " + parsedDate);
    

    使用java.util.Calendar进行日期和时间计算

            Calendar calendar = Calendar.getInstance();
    
            // 获取当前年份
            int year = calendar.get(Calendar.YEAR);
            System.out.println("Current Year: " + year);
    
            // 增加一个月
            calendar.add(Calendar.MONTH, 1);
            int newMonth = calendar.get(Calendar.MONTH);
            System.out.println("New Month: " + newMonth);
    
            // 设置特定日期
            calendar.set(2022, Calendar.MARCH, 15);
            int day = calendar.get(Calendar.DAY_OF_MONTH);
            System.out.println("Day: " + day);
    

    使用java.util.TimeZone进行时区转换

    Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
            // 设置时区为纽约
            TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York");
            sdf.setTimeZone(newYorkTimeZone);
            String newYorkTime = sdf.format(date);
            System.out.println("New York Time: " + newYorkTime);
    
            // 设置时区为东京
            TimeZone tokyoTimeZone = TimeZone.getTimeZone("Asia/Tokyo");
            sdf.setTimeZone(tokyoTimeZone);
            String tokyoTime = sdf.format(date);
            System.out.println("Tokyo Time: " + tokyoTime);
    

    jdk8及以后

    在JDK 8及其之后,Java引入了新的日期和时间API,即java.time包,以解决旧的日期和时间类(如java.util.Datejava.util.Calendar)存在的问题。新的API提供了更加简洁、易用和线程安全的方式来处理日期、时间和时区的表示和转换。

    使用LocalDateLocalTimeLocalDateTime进行日期和时间操作

             // 获取当前日期
            LocalDate currentDate = LocalDate.now();
            System.out.println("Current Date: " + currentDate);
    
            // 获取当前时间
            LocalTime currentTime = LocalTime.now();
            System.out.println("Current Time: " + currentTime);
    
            // 创建特定日期和时间
            LocalDate specificDate = LocalDate.of(2022, 3, 15);
            LocalTime specificTime = LocalTime.of(12, 30, 0);
            LocalDateTime specificDateTime = LocalDateTime.of(specificDate, specificTime);
            System.out.println("Specific Date and Time: " + specificDateTime);
    

    使用ZoneIdZonedDateTime进行时区转换:

            LocalDateTime localDateTime = LocalDateTime.now();
            ZoneId newYorkZone = ZoneId.of("America/New_York");
            ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
    
            // 转换到纽约时区
            ZonedDateTime newYorkTime = ZonedDateTime.of(localDateTime, newYorkZone);
            System.out.println("New York Time: " + newYorkTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
    
            // 转换到东京时区
            ZonedDateTime tokyoTime = newYorkTime.withZoneSameInstant(tokyoZone);
            System.out.println("Tokyo Time: " + tokyoTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
    

    mysql中的时间表示

    在MySQL中,有几种不同的时间类型可用于存储和操作日期和时间数据。下面是MySQL中常见的时间类型及其简要介绍:

  • DATE:DATE类型用于存储日期值,格式为'YYYY-MM-DD'。它可以表示从'1000-01-01'到'9999-12-31'之间的日期。
  • TIME:TIME类型用于存储时间值,格式为'HH:MM:SS'。它可以表示从'-838:59:59'到'838:59:59'之间的时间。
  • DATETIME:DATETIME类型用于存储日期和时间值,格式为'YYYY-MM-DD HH:MM:SS'。它可以表示从'1000-01-01 00:00:00'到'9999-12-31 23:59:59'之间的日期和时间。
  • TIMESTAMP:TIMESTAMP类型用于存储日期和时间值,格式为'YYYY-MM-DD HH:MM:SS'。它可以表示从'1970-01-01 00:00:01' UTC到'2038-01-19 03:14:07' UTC之间的日期和时间。注意,TIMESTAMP类型还可以自动更新为当前时间戳,例如在插入或更新行时。
  • YEAR:YEAR类型用于存储年份值,格式为'YYYY'。它可以表示从'1901'到'2155'之间的年份。
  • 参考

    Java服务器时区时间转换为中心,实现简单高效的时间转换方案
    一文读懂全球化系统中的日期时间处理问题

    相关文章

    Oracle如何使用授予和撤销权限的语法和示例
    Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
    下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
    社区版oceanbase安装
    Oracle 导出CSV工具-sqluldr2
    ETL数据集成丨快速将MySQL数据迁移至Doris数据库

    发布评论