这几年经手了很多面向全球用户的Web项目,每个项目对时区处理都不大一样,Date和Time的类型也不太一样,所以来做个总结。
本文将介绍一套最简单的实现,能满足绝大多数项目的时区需求:后端统一用UTC时区(0时区)来落库、计算、触发器,前端根据用户浏览器时区来渲染。
后端
实体
Java8之后,就不要再使用java.sql.*
和java.util.Date
了,而是使用java.time.*
包下的类型:LocalTime、 LocalDate和LocalDateTime:
java 代码解读复制代码@Data
@Entity
@Table(name = "date_time_record")
public class DateTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDate localDate; // map with pg date type
private LocalTime localTime; // map with pg time type
private LocalDateTime localDateTime; // map with pg timestamp type
}
数据库
以PostgreSQL为例,可以将类型TIME、DATE和TIMESTAMP映射到java.time类型
sql 代码解读复制代码create table public.date_time_record
(
id bigserial primary key,
local_date date, -- 无时区的日期类型(2024-10-01)
local_time time(6), -- 无时区的日期类型(12:34:56)
local_date_time timestamp(6) -- 无时区的日期+时间类型(2024-09-13 06:30:05.971952)
);
Jackson序列化和反序列化
通过@JsonFormat
注解来指定日期格式的转换规则
java 代码解读复制代码@Data
public class DateTimeDTO {
@Schema(example = "2024-10-01") // swagger的包
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate localDate;
@Schema(example = "12:34:56")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss")
private LocalTime localTime;
@Schema(example = "2024-10-01T12:34:56", type = "string")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-d'T'HH:mm:ss")
private LocalDateTime localDateTime;
}
Controller入参绑定
java 代码解读复制代码@RestController
@RequestMapping("/api")
public class DateTimeController {
@Resource
private DateTimeRepository dateTimeRepository;
@PostMapping("/datetime")
public ResponseEntity save(@RequestBody DateTimeDTO dto) {
DateTimeEntity entity = new DateTimeEntity();
BeanUtils.copyProperties(dto, entity);
dateTimeRepository.save(entity);
return ResponseEntity.ok(dto);
}
}
全局UTC时区
我们在代码中需要使用LocalDate.now()
或LocalDateTime.now()
等API时,会使用本地时区如UTC+8,所以需要全局配置为UTC时区,以避免计算和落库时发生时区问题。
java 代码解读复制代码@SpringBootApplication
public class DateTimeBindingDemoApplication {
public static void main(String[] args) {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
SpringApplication.run(DateTimeBindingDemoApplication.class, args);
}
}
至此,API的入参绑定、系统内计算、落库、出参Json格式都已得到适配
前端
LocalDateTime在前端做转换是没问题的,但由于LocalDate和LocalTime缺少用来offset的部分,则需要补全为一个完整的LocalDateTime才可以做转换(解释:例如2024-10-01,在中国时间7:59时,UTC还是2024-09-30;所以为了信息完整,最好用LocalDateTime类型)
前端如果有针对不同时区进行渲染的需求,有一系列很好用的的API:
- Date.prototype.toLocaleString()
- Date.prototype.toLocaleDateString()
- Date.prototype.toLocaleTimeString()
示例:
js 代码解读复制代码// 从后端获取的日期时间字符串+Z表示UTC时区,也可以直接让后端返回TZ格式
const localDateTimeString = "2024-10-01T12:34:56" + "Z";
// 将TZ格式的字符串转换为Date对象
const utcDateTime = new Date(localDateTimeString);
// toLocaleString默认会使用浏览器时区,输出为:2024/10/1 20:34:56
console.log(utcDateTime.toLocaleString());
// 也可以添加不同的locale,这个参数并不影响timezone
// 输出为:10/1/2024, 8:34:56 PM
console.log(utcDateTime.toLocaleString('en-US'));
// 输出为:2024/10/1 20:34:56
console.log(utcDateTime.toLocaleString('zh-CN'));
// 也可以添加参数,比如timezone和format格式,更详细的参考MDN文档
// 输出为:October 1, 2024 at 08:34:56 AM
console.log(utcDateTime.toLocaleString('en-US', { timeZone: 'America/New_York', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }));
评论记录:
回复评论: