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

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

处理方法

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

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

针对后台定时任务比如过期时间处理或者特定时间发布的场景,后台最好维护国家和时区的映射关系,按照国家依次取到特定时间来一一处理

java中的时间表示

jdk8之前

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

  1. 时间表示:

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

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

相关举例如下: 使用java.util.Datejava.text.SimpleDateFormat进行格式化和解析

java 复制代码
        // 格式化日期为字符串
        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进行日期和时间计算

java 复制代码
        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进行时区转换

java 复制代码
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进行日期和时间操作

java 复制代码
         // 获取当前日期
        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进行时区转换:

java 复制代码
        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中常见的时间类型及其简要介绍:

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

参考

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

相关推荐
Asthenia041239 分钟前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom1 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide1 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04122 小时前
Spring 启动流程:比喻表达
后端
Asthenia04123 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫