[原创]Java项目统一UTC时间方案

摘要:
与原生的Date类型不同,DateTime需要做一点额外处理1、Model类型的日期字段使用类型DateTime替代Date实例代码如下publicclassEntity{@JsonSerialize@JsonDeserializeprivateDateTimedateTime;publicDateTimegetDateTime(){returndateTime;}publicvoidsetDateTime{this.dateTime=dateTime;}}其中UTCDateTimeSerializer与UTCDateTimeDeserializer类的实现见附录2、Get请求接受时间参数此时,一种有效的处理方式是使用字符串接受日期参数,如下:@RequestMappingpublicCommonResponsegetXxx{DateTimebeginTime=DateTime.parse.withZone;DateTimeendTime=DateTime.parse.withZone;...}Dao时间操作——针对数据库列为datetime的场景以JodaDateTime类型举例说明使用方法,某Dao类型中存在的两个方法如下:publicvoidupdate{Stringsql="UPDATE"+TABLE_NAME+"SETdatetime=?
Java项目统一UTC时间方案

作者:Gods_巨蚁

引言

近期团队的个别项目在进行框架升级后,部分时间值存在8小时误差,原因是错误的将数据库中的时间数据理解成了UTC时间(旧版本认为是北京时间)

考虑到未来项目对于时间理解的一致性,我决定将项目统一为使用UTC时间,经调研,形成本文

mysql数据库时区及时间时间类型说明

数据库时区

mysql数据库拥有时区设置,默认使用系统时区

可通过如下语句查询当前时区

show variables like '%time_zone%';

下图为我个人机器上mysql数据库时区设置:

[原创]Java项目统一UTC时间方案第1张

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

[原创]Java项目统一UTC时间方案第2张

可见数据库使用系统时间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 implementsServletContextListener{
    public voidcontextInitialized(ServletContextEvent event) {
        System.setProperty("user.timezone", "UTC");
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
    }

    public voidcontextDestroyed(ServletContextEvent event) {}
}

时间类型Joda DateTime的使用方式

日期时间类型可以使用 java.util.Date,但推荐使用更为方便的joda DateTime,本节介绍joda DateTime 序列化/反序列化使用方式

Joda DateTime 类型用于定义接口输入输出参数,需进行序列化/反序列化操作。与原生的Date类型不同,DateTime需要做一点额外处理

1Model类型的日期字段使用类型DateTime替代Date

实例代码如下

public classEntity {
    @JsonSerialize(using = UTCDateTimeSerializer.class)
    @JsonDeserialize(using = UTCDateTimeDeserializer.class)
    privateDateTime dateTime;

    publicDateTime getDateTime() {
        returndateTime;
    }

    public voidsetDateTime(DateTime dateTime) {
        this.dateTime =dateTime;
    }
}

其中UTCDateTimeSerializer与UTCDateTimeDeserializer类的实现见附录

2Get请求接受时间参数

此时,一种有效的处理方式是使用字符串接受日期参数,如下:

    @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(intid, DateTime dateTime) {
        String sql = "UPDATE " + TABLE_NAME + " SET datetime = ? WHERE id = ?";
        jdbcTemplate.update(sql, newTimestamp(dateTime.getMillis()), id);
    }

    public DateTime getDateTime(intid) {
        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) throwsSQLException {
                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 TABLEt1 (
  ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);

可简写如下

CREATE TABLEt1 (
  ts TIMESTAMP);

不允许程序自主更新timstamp列数据

线上数据库时区为北京时间,其接受到的日期数据被视为北京时间,而上层程序业务逻辑统一使用UTC时间,时区不统一。因此避免数据库记录的日期数据理解不一致,不允许程序通过写操作sql语句更新timestamp列

下图数据为本人实测数据,timestamp列由程序进行更新,update_time列则由数据库自动更新

[原创]Java项目统一UTC时间方案第3张

前者显示的是UTC时间,看似合理,实则错误,数据库内部存储时间为UTC-8:00

update_time符合数据库时区设置,返回北京时间,内部实际存储UTC时间

2、 如何读取timestamp数据

为避免从数据库中获取时区相关时间(北京时间),强制使用UTC时间,使用函数UNIX_TIMESTAMP获取1970年至今秒数,转换成DateTime时乘以1000转变为毫秒

    public DateTime getTimestamp(intid) {
        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) throwsSQLException {
                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 voidserialize(DateTime dateTime,
                          JsonGenerator jsonGenerator,
                          SerializerProvider provider) throwsIOException {
        String dateTimeAsString =dateTime.withZone(DateTimeZone.UTC).toString(BecConstant.DATETIME_FORMAT);
        jsonGenerator.writeString(dateTimeAsString);
    }
}

public class UTCDateTimeDeserializer extends JsonDeserializer<DateTime>{
    @Override
    publicDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
            throwsIOException {
        JsonToken currentToken =jsonParser.getCurrentToken();
        if (currentToken ==JsonToken.VALUE_STRING) {
            String dateTimeAsString =jsonParser.getText().trim();
            returnDateTime.parse(dateTimeAsString).withZone(DateTimeZone.UTC);
        }
        return null;
    }
}

免责声明:文章转载自《[原创]Java项目统一UTC时间方案》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇mysql 批处理文件出错后继续执行嵌入式Web服务器BOA移植下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

实用向—总结一些唯一ID生成方式

在日常的项目开发中,我们经常会遇到需要生成唯一ID的业务场景,不同的业务对唯一ID的生成方式与要求都会不尽相同,一是生成方式多种多样,如UUID、雪花算法、数据库递增等;其次业务要求上也各有不同,有的只要保证唯一性即可,有的需要加上时间戳,有的要保证按顺序递增等。以下是我结合实际业务中的使用总结了几种唯一ID的生成方式,  要求就是在一般的应用场景下一方面...

RTP协议之Header结构解析

实时传输协议 RTP,RTP 提供带有实时特性的端对端数据传输服务,传输的数据如:交互式的音频和视频。那些服务包括有效载荷类型定义,序列号,时间戳和传输监测控制。应用程序在 UDP 上运行 RTP 来使用它的多路技术和 checksum 服务。2 种协议都提供传输协议的部分功能。不过,RTP 可能被其他适当的下层网络和传输协议使用。如 果下层网络支持,R...

WebSocket详解

WebSocket 出现前 构建网络应用的过程中,我们经常需要与服务器进行持续的通讯以保持双方信息的同步。通常这种持久通讯在不刷新页面的情况下进行,消耗一定的内存资源常驻后台,并且对于用户不可见。在 WebSocket 出现之前,我们有以下解决方案: 传统轮询(Traditional Polling) 当前Web应用中较常见的一种持续通信方式,通常采取 s...

windows时间函数

介绍        我们在衡量一个函数运行时间,或者判断一个算法的时间效率,或者在程序中我们需要一个定时器,定时执 行一个特定的操作,比如在多媒体中,比如在游戏中等,都会用到时间函数。还比如我们通过记录函数或者算法开始和截至的时间,然后利用两者之差得出函数或者 算法的运行时间。编译器和操作系统为我们提供了很多时间函数,这些时间函数的精度也是各不相同的,所以...

PHP获取上周、本周、上月、本月、本季度、上季度时间

echo date("Y-m-d",strtotime("now")); echo "<br>"; echo date("Y-m-d",strtotime("-1 week Monday")); echo "<br>"; echo date("Y-m-d",strtotime("-1 week Sunday")); ech...

python 后台爆破工具

sys:使用sys模块获得脚本的参数 queue模块,创建一个“队列”对象 time 模块     Python time time() 返回当前时间的时间戳(1970纪元后经过的浮点秒数)。  find()函数找不到时返回为-1 #!/usr/bin/env python# -*- coding: utf-8 -*-import sysimport re...