全球化时区处理

概念

1.相对时间和绝对时间

  • 相对时间: "yyyy-MM-dd HH:mm:ss" 或 "HH:mm";
  • 绝对时间: "yyyy-MM-dd HH:mm:ss" + timezone 或 timestamp;

怎么做

1. 数据库字段类型

  • 相对时间:datetime,date,time;
  • 绝对时间:timestamp;

2. java【实体类】时间字段类型

  • 相对时间:java.lang.String;
  • 绝对时间:java.time.Instant;

3. instant使用规则

  • 时间戳转换统一指定UTC;
  • 当请求通过url拼接参数的格式传输时,后端用Instant对象接收时,只能用世界标准时间格式2020-04-16T16:45:44Z;
  • 当请求通过body参数的格式传输时,后端用Instant对象接收时,jackson有3种方式将前端传的值解析为Instant:
    1.(默认)世界标准时间2020-04-16T16:45:44Z
    2.时间戳1587055544(以UTC标准)
    3.后端指定注解指定日期格式和时区=UTC,前端传2020-04-16 16:45:44;
  • 其它方式:
    1.后端用Long来接收,前端传的时间戳以UTC为标准
    2.后端用String和时区接收,前端传日期字符串格式和时区;

为什么

1.相对时间,【实体对象】从mysql或者前端接受为什么用String而不是LocalDate、LocalTime?

  • LocalDate、LocalTime存储读取时,会转成jvm的系统时区时间,导致读取值不准确,因为mysql的datetime字段是不分时区的(不推荐使用)
  • String 存的就是字符串,不管什么时区都不会变,使用时只需指定ZoneId转成时间对象即可(推荐使用)

2.绝对时间,【实体对象】从mysql或者前端接受为什么用Instant而不是Date、LocalDateTime、ZonedDateTime、OffSetDateTime?

  • Date已经比较旧了,不推荐使用;
  • ZonedDateTime、OffSetDateTimezoneDateTime之类的不是高版本的mybatis不支持,是mybatis对数据库提出了更高的要求,目前mysql不支持,原因:https://github.com/mybatis/mybatis-3/issues/1750 (不推荐使用);
  • LocalDateTime绝对时间,我们希望无论是在数据库中存储还是在jvm取值的时候,都是UTC时间标准的时间类型,而 LocalDateTime在取值的时候会默认将时间戳按照服务器时区转换成【int类型的年/月/日/时/分/秒 + zoneId】的形式存储,不利于:
  1. 不同时区的时间比较
  2. 服务部署在不同时区
    而Instant的实现是基于UCT的时间戳,本身不具备时区信息,可以确保在业务使用时按照预期给出转换结果,规避时区不同引起的bug(不建议使用);
  • Instant 时间戳对象,存的就是时间戳,与时区无关,只需根据ZoneId转成时间对象即可(推荐使用);

还需注意什么

  1. 时间戳的值,所有系统统一以UTC时间为标准,因此不会受jvm系统时区、mysql系统时区、jdbc连接配置时区影响,但这三个时区会决定在对应环境中展示的时间字符串时间;

  2. Instant的toString(),采用UTC的00:00时区为准;

  3. Instant的compareTo(Instant otherInstant)、isBefore(Instant otherInstant)、isAfter(Instant otherInstant)比较的是时间戳;

使用Instant示例

CREATE TABLE `zone_time` (
`id` INT NOT NULL AUTO_INCREMENT,
`created_at` datetime NULL COMMENT '创建时间',
`updated_at` TIMESTAMP NULL COMMENT '更新时间',
PRIMARY KEY ( `id` ) USING BTREE );

实体对象

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ZoneTime implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
 
    private String createdAt;
 
    private Instant updatedAt;
 
}

|

使用instant接收参数

  • eg1: 传值:世界标准时间格式,@PathVariable

    {url}/{updatedAt},举例:url/2020-04-16T16:45:44Z

    后端参数:
    @PathVariable("updatedAt") Instant updatedAt
##### java取出的值:
image.png
  • eg2:传值:世界标准时间格式(body) yyyy-MM-ddTHH:mm:ssZ

    RequestBody对象
  @Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ZoneTime implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
 
    private String createdAt;
 
    private Instant updatedAt;
 
}
#####   前端请求的body
    {
    "createdAt":"2020-04-16 16:45:44",
    "updatedAt":"2020-04-16T16:45:44Z"
  }
##### mysql存入的值
image.png
##### java取出的值
image.png
  • eg3: 传值:时间戳(body)

RequestBody对象

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ZoneTime implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
 
    private String createdAt;
 
    private Instant updatedAt;
 
}
前端请求的body
{
    "createdAt":"2020-04-16 16:45:44",
    "updatedAt":"1587055544"
}
mysql存入的值
image.png
java取出的值
image.png
  • eg4: 传日期时间格式(body)

RequestBody对象
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ZoneTime implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
 
    private String createdAt;
 
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "UTC")//必须制定timezone="UTC"
    private Instant updatedAt;
 
}
前端请求的body
{
    "createdAt":"2020-04-16 16:45:44",
    "updatedAt":"2020-04-16 16:45:44"
}
mysql存入的值
image.png
java取出的值
image.png

java8的时间互转示例

Instant->其他时间类
  • Instant→ZonedDateTime
    Instant instant = Instant.now();
     ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
  • Instant→OffsetDateTime
     Instant instant = Instant.now();
     ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
  • Instant→LocalDateTime
     Instant instant = Instant.now();
     LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime localDateTime = instant.atOffset(ZoneOffset.UTC).toLocalDateTime();
  • Instant→LocalDate
    LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
     LocalDate localDate = instant.atOffset(ZoneOffset.UTC).toLocalDate();
  • Instant→LocalTime
    LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
     LocalDate localDate = instant.atOffset(ZoneOffset.UTC).toLocalDate();
  • 汇总instant转为其他时间对象:

展开源码

public static void main(String[] args) {
    Instant instant = Instant.now();
    ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
    System.out.println("Instant→ZonedDateTime:" + zonedDateTime);

    OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.UTC);
    System.out.println(" Instant→OffsetDateTime:" + offsetDateTime);

    LocalDateTime zoneToLocalDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
    LocalDateTime offSetLocalDateTime = instant.atOffset(ZoneOffset.UTC).toLocalDateTime();
    System.out.println(" Instant→LocalDateTime:" + zoneToLocalDateTime);
    System.out.println(" Instant→LocalDateTime:" + offSetLocalDateTime);

    LocalDate zoneToLocalDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
    LocalDate offSetLocalDate = instant.atOffset(ZoneOffset.UTC).toLocalDate();
    System.out.println(" Instant→LocalDate:" + zoneToLocalDate);
    System.out.println(" Instant→LocalDate:" + offSetLocalDate);

    LocalTime zoneToLocalTime = instant.atOffset(ZoneOffset.UTC).toLocalTime();
    LocalTime offSetLocalTime = instant.atZone(ZoneId.systemDefault()).toLocalTime();

    System.out.println(" Instant→LocalTime:" + zoneToLocalTime);
    System.out.println(" Instant→LocalTime:" + offSetLocalTime);
}
String->...->Instant
  • String→instant(String格式必须为如下格式)默认时区UTC
 String date = "2007-12-03T10:15:30.00Z";
    Instant instant=Instant.parse(date);
  • String->LocalDateTime→instant
 String date = "2020-04-16 16:45:44";
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Instant instant = LocalDateTime.parse(date, formatter).toInstant(ZoneOffset.UTC);
  • String->ZonedDateTime→instant
String date = "2020-04-16 16:45:44";
     DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
     ZonedDateTime zonedDateTime=ZonedDateTime.parse(date,formatter);
     Instant instant=zonedDateTime.toInstant();
使用UTC时间转换对应时区

对象实现

@Slf4j
public class Test {
    public static void main(String[] args) {
        String date = "2020-04-16 16:45:44";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        Instant instant = LocalDateTime.parse(date, formatter).toInstant(ZoneOffset.UTC);
        log.info("UTC时间:{}", instant);
        log.info("时间戳:{}", instant.getEpochSecond());
        ZonedDateTime mskZonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("Europe/Moscow"));
        log.info("莫斯科时间:{}", mskZonedDateTime);
        log.info("莫斯科时间偏移量:{}", mskZonedDateTime.getOffset().getId());
        log.info("莫斯科时间偏移量总秒:{}", mskZonedDateTime.getOffset().getTotalSeconds());
        ZonedDateTime shanghaiZonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("Asia/Shanghai"));
        log.info("上海时间:{}", shanghaiZonedDateTime);
        log.info("上海时间偏移量:{}", shanghaiZonedDateTime.getOffset().getId());
        log.info("上海时间偏移量总秒:{}", shanghaiZonedDateTime.getOffset().getTotalSeconds());
    }
}

输出结果

image.png

本文为原创,转载需要指明出处

你可能感兴趣的:(全球化时区处理)