[原创]Java项目统一UTC时间方案
Java项目统一UTC时间方案
作者:Gods_巨蚁
引言
近期团队的个别项目在进行框架升级后,部分时间值存在8小时误差,原因是错误的将数据库中的时间数据理解成了UTC时间(旧版本认为是北京时间)
考虑到未来项目对于时间理解的一致性,我决定将项目统一为使用UTC时间,经调研,形成本文
mysql数据库时区及时间时间类型说明
数据库时区
mysql数据库拥有时区设置,默认使用系统时区
可通过如下语句查询当前时区
show variables like '%time_zone%';
下图为我个人机器上mysql数据库时区设置:

项目线上数据库时区设置如下:

可见数据库使用系统时间CST——China Standard Time UTC+8:00 中国沿海时间(北京时间)
时间类型说明
datetime
实际格式储存(Just stores what you have stored and retrieves the same thing which you have stored.)
与时区无关(It has nothing to deal with the TIMEZONE and Conversion.)
timestamp
值以UTC毫秒数保存( it stores the number of milliseconds)
存储及检索时根据当前时区设置,对时间数值做转换
由于timestamp与时区相关,且线上数据库时区设置为北京时间(即UTC+8:00)。因此,当数据库中使用了timestamp列,若使用不当,统一UTC格式时间改造将很可能会引入错误! 后面详述理由
统一UTC时间改造方案简述
统一时区设定
项目新框架中通过UTCTimeZoneConfiguration类型,在项目初始化时设置当前进程的默认时区
@Configuration
public class UTCTimeZoneConfiguration implements ServletContextListener{
public void contextInitialized(ServletContextEvent event) {
System.setProperty("user.timezone", "UTC");
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
} public void contextDestroyed(ServletContextEvent event) {}
}
时间类型Joda DateTime的使用方式
日期时间类型可以使用 java.util.Date,但推荐使用更为方便的joda DateTime,本节介绍joda DateTime 序列化/反序列化使用方式
Joda DateTime 类型用于定义接口输入输出参数,需进行序列化/反序列化操作。与原生的Date类型不同,DateTime需要做一点额外处理
1、Model类型的日期字段使用类型DateTime替代Date
实例代码如下
public class Entity {
@JsonSerialize(using = UTCDateTimeSerializer.class)
@JsonDeserialize(using = UTCDateTimeDeserializer.class)
private DateTime dateTime;
public DateTime getDateTime() {
return dateTime;
}
public void setDateTime(DateTime dateTime) {
this.dateTime = dateTime;
}
}
其中UTCDateTimeSerializer与UTCDateTimeDeserializer类的实现见附录
2、Get请求接受时间参数
此时,一种有效的处理方式是使用字符串接受日期参数,如下:
@RequestMapping(value = "/xxx", method = RequestMethod.GET)
public CommonResponse getXxx(@RequestParam(value = "beginTime") String beginTimeText,
@RequestParam(value = "endTime") String endTimeText) {
DateTime beginTime = DateTime.parse(beginTimeText).withZone(DateTimeZone.UTC);
DateTime endTime = DateTime.parse(endTimeText).withZone(DateTimeZone.UTC);
...
}
Dao时间操作——针对数据库列为datetime的场景
以Joda DateTime类型举例说明使用方法,某Dao类型中存在的两个方法如下:
public void update(int id, DateTime dateTime) {
String sql = "UPDATE " + TABLE_NAME + " SET datetime = ? WHERE id = ?";
jdbcTemplate.update(sql, new Timestamp(dateTime.getMillis()), id);
}
public DateTime getDateTime(int id) {
String sql = "SELECT datetime FROM " + TABLE_NAME + " WHERE id = ?";
List<DateTime> dateTimeList = jdbcTemplate.query(sql, new Object[] {id}, new RowMapper<DateTime>() {
@Override
public DateTime mapRow(ResultSet rs, int rowNum) throws SQLException {
return new DateTime(rs.getTimestamp("datetime").getTime());
}
});
return dateTimeList.size() > 0 ? dateTimeList.get(0) : null;
}
插入或更新数据,传递的时间参数请使用 new Timestamp(dateTime.getMillis())
读取时间参数,使用new DateTime(rs.getTimestamp("datetime").getTime())
Dao时间操作——针对数据库列为timestamp的场景
数据库timestamp类型适合用来记录数据的最后修改时间
其他场景建议使用datetime或者int
方案一 更改会话时区为UTC时间
对timestamp列的操作与datetime列的操作不做区分,此时需要设置数据连接会话的时区,默认为北京时间,需要设置为UTC时间,通过如下语句设置
set time_zone = '+0:00';
实际项目中使用数据库连接池,创建datasource后使用如下方式设置时区,将对所有连接生效
dataSource.setInitSQL("set time_zone = '+0:00'");
经此操作后,时区统一为UTC时间,Dao中时间操作,无需对timestamp做特殊处理
方案二 不更改会话时区
由于不更改时区,timestamp类型数据的使用存在一定限制
1、 如何更新timestamp数据
对于数据库表中的timestamp列,其值的更新应当由数据库自行维护,在create table时设置,如下:
CREATE TABLE t1 (
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
可简写如下
CREATE TABLE t1 (
ts TIMESTAMP
);
不允许程序自主更新timstamp列数据
线上数据库时区为北京时间,其接受到的日期数据被视为北京时间,而上层程序业务逻辑统一使用UTC时间,时区不统一。因此避免数据库记录的日期数据理解不一致,不允许程序通过写操作sql语句更新timestamp列
下图数据为本人实测数据,timestamp列由程序进行更新,update_time列则由数据库自动更新

前者显示的是UTC时间,看似合理,实则错误,数据库内部存储时间为UTC-8:00
update_time符合数据库时区设置,返回北京时间,内部实际存储UTC时间
2、 如何读取timestamp数据
为避免从数据库中获取时区相关时间(北京时间),强制使用UTC时间,使用函数UNIX_TIMESTAMP获取1970年至今秒数,转换成DateTime时乘以1000转变为毫秒
public DateTime getTimestamp(int id) {
String sql = "SELECT UNIX_TIMESTAMP(update_time) as unix_timestamp FROM " + TABLE_NAME + " WHERE id = ?";
List<DateTime> dateTimeList = jdbcTemplate.query(sql, new Object[] {id}, new RowMapper<DateTime>() {
@Override
public DateTime mapRow(ResultSet rs, int rowNum) throws SQLException {
return new DateTime(rs.getLong("unix_timestamp") * 1000);
}
});
return dateTimeList.size() > 0 ? dateTimeList.get(0) : null;
}
附录
Mysql时区设置
设置全局时区,需要管理员权限
使用本机系统时区
SET GLOBAL time_zone = SYSTEM;
使用UTC时间
SET GLOBAL time_zone = '+0:00';
使用北京时间
SET GLOBAL time_zone = '+8:00';
设置当前连接会话时区
set time_zone = '+0:00';
UTCDateTimeSerializer与UTCDateTimeDeserializer
UTCDateTimeSerializer 完成DateTime对象到UTC时间字符串的转换,格式为:yyyy-MM-ddTHH:mm:ssZ
UTCDateTimeDeserializer 完成时间字符串到DateTime对象的转换,转换为UTC时区
具体实现如下:
public class UTCDateTimeSerializer extends JsonSerializer<DateTime> {
@Override
public void serialize(DateTime dateTime,
JsonGenerator jsonGenerator,
SerializerProvider provider) throws IOException {
String dateTimeAsString = dateTime.withZone(DateTimeZone.UTC).toString(BecConstant.DATETIME_FORMAT);
jsonGenerator.writeString(dateTimeAsString);
}
}
public class UTCDateTimeDeserializer extends JsonDeserializer<DateTime> {
@Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
JsonToken currentToken = jsonParser.getCurrentToken();
if (currentToken == JsonToken.VALUE_STRING) {
String dateTimeAsString = jsonParser.getText().trim();
return DateTime.parse(dateTimeAsString).withZone(DateTimeZone.UTC);
}
return null;
}
}
[原创]Java项目统一UTC时间方案的更多相关文章
- Java springmvc 统一异常处理的方案
前言:为什么要统一异常处理?经常在项目中需要统一处理异常,将异常封装转给前端.也有时需要在项目中统一处理异常后,记录异常日志,做一下统一处理. Springmvc 异常统一处理的方式有三种. 一.使用 ...
- Java local 转UTC时间
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; impor ...
- 全网最全!彻底弄透Java处理GMT/UTC日期时间
目录 前言 本文提纲 版本约定 正文 Date类型实现 时区/偏移量TimeZone 设置默认时区 让人恼火的夏令时 Date时区无关性 读取字符串为Date类型 SimpleDateFormat格式 ...
- Java UTC时间与本地时间互相转换
协调世界时,又称世界统一时间.世界标准时间.国际协调时间.由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC. 这套时间系统被应用于许多互联网和万维网的标准中,例如,网络时间协议就是协 ...
- Java 项目创建 -- 统一结果处理、统一异常处理、统一日志处理
一.IDEA 插件使用 1.说明 此处使用 SpringBoot 2.2.6 .JDK 1.8 .mysql 8.0.18 作为演示. 使用 IDEA 作为开发工具. 2.IDEA 插件 -- Lom ...
- Java中转UTC时间字符串(含有T Z)为local时间
在Java中我们需要转换相应格式的字符串,很多时候我们想到用SimpleDateFormat类来解析.但是最近我在调用一个第三方的接口时返回的 JSON字符串中有个expires字段的值是2014-0 ...
- Java获得UTC时间
在Java语言中,您可以通过java.util.Calendar类取得一个本地时间或者指定时区的时间实例,如下: 取得本地时间: java.util.Calendar cal = java.util. ...
- Java微服务对UTC时间格式的处理
一.背景 先说一下为什么要使用UTC时间.开发一个全球化的系统,服务端(Java微服务)集中部署在同一个地方,用户在全球通过浏览器.手机客户端访问.不同地区的时区是不一样的,同一个时间戳,不同的用户看 ...
- 传统Java Web(非Spring Boot)、非Java语言项目接入Spring Cloud方案
技术架构在向spring Cloud转型时,一定会有一些年代较久远的项目,代码已变成天书,这时就希望能在不大规模重构的前提下将这些传统应用接入到Spring Cloud架构体系中作为一个服务以供其它项 ...
随机推荐
- 【zzulioj-2115】乘积最大(区间dp)
题目描述 今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰90周年.在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友XZ也有幸得 ...
- docker-web管理工具实验
工具名称 共有功能 备注 UCP 官方.收费 portainer 镜像库 容器管理 rancher shipyard kubernetes (上诉部署都基于linux) UCP ...
- 条款3:尽可能的使用const
const成员函数的一般好处有: 它使得class接口比较容易理解. 它使得操纵const对象成为可能. 使用的过程中应该在const与non const成员函数之间避免代码重复: class Tex ...
- Android 进阶14:源码解读 Android 消息机制( Message MessageQueue Handler Looper)
不要心急,一点一点的进步才是最靠谱的. 读完本文你将了解: 前言 Message 如何获取一个消息 Messageobtain 消息的回收利用 MessageQueue MessageQueue 的属 ...
- ng 自定义服务
服务的本质是对象. 创建服务的常见方式:factory(返回对象) service (方法.属性)constant(常量服务) value(变量服务) 1.factoryapp.factory('服务 ...
- Unity的Update() 和 FixedUpdate()的区别
Update() 和 FixedUpdate()在游戏中都会在更新的时候自动循环调用. 但是Update是在每次渲染新的一帧的时候才会调用,也就是说,这个函数的更新频率和设备的性能有关以及被渲染的物体 ...
- Compile For Cydia Submission Author: BigBoss Updated September 23, 2011
Compile For Cydia Submission Author: BigBoss Updated September 23, 2011: In order to submit your app ...
- PS基础教程[3]如何去除照片上的水印
网络上的照片大部分都有很多的水印,要嘛就是网站的地址,要嘛就是一些煽情的文字,我们看图片想要的可不是这些东西,那么我们怎样去掉图片上的水印呢?本次我们就来分享一下仿制图章工具的使用. 方法 1.打开P ...
- PS抠图之单色背景图片
PS一直大家比较喜欢的一款图像处理软件,很多朋友对使用基本的功能.最近很多的朋友都在问我关于PS抠图的方法,这些方法也不是一句两句就能说清楚,并且每天都重复的叫他们,不如直接写出来刚刚接触到的朋友一起 ...
- 洛谷 P3144 [USACO16OPEN]关闭农场Closing the Farm_Silver
传送门 题目大意: n个谷仓 ,每次关闭一个谷仓,问剩下没被关闭的谷仓是 否联通. 题解:并查集+倒序处理 代码: #include<iostream> #include<cstdi ...