背景
前言
java代码里对于日期、时间的使用与操作已经司空见惯,也有大量的工具包提供了对日期对象的各种操作的封装,但是一旦与国际接轨,需要处理的时区不再是单一的GMT+8,原本简单的日期对象就变得复杂起来了。
国际化服务简介
如上图:我们的java服务都是有一个默认时区的,以往在国内,都是GMT+8。其中java服务和数据库都是单一时区的,这个他们的时区都是系统级别的,但是既然是国际化的业务就需要考虑到业务层面是需要支持多时区的,比如一个做酒店业务的公司,同一套java服务和数据库对外提供服务,酒店是有很多的,并且由于是国际化酒店,不同的酒店所处的物理位置可能是不同时区的,那么每个酒店的业务逻辑以及数据都需要以这个酒店的时区为准,比如一个业务逻辑:每天凌晨2点需要启动一个定时任务对每个酒店做业务逻辑处理,显然不同时区的酒店的凌晨两点压根不是同一个时刻,就好比中国的早上2点和美国的早上2点是不同的时刻
综上所述:我们的java代码不能无脑在系统默认时区下操作日期对象了,反而需要去考虑当前的日期操作在对应业务时区下应该如何操作。
简单的案例分析
在业务代码中经常有一个操作就是对一个Date对象取它的这天的开始时间:2024-04-09 22:12:23 ---> 2024-04-09 00:00:00
也就是把时分秒置0了,加入系统时区就是GMT+8, 由于我们是在jvm内存里对日期进行操作,所以这个操作默认情况使用的系统时区,但是由于这个日期对象具有业务含义,比如是某个业务的创建时间,并且这个取一天的开始时间明显也是需要取该业务时区的第一天,因为这整个操作本质上就是业务的操作,所以简单的说就是这个对日期的操作需要在业务时区下进行,而不是在系统时区。
简单说一下换时区的原因,不仅仅是它是一个业务时区的操作,关键是不换时区可能导致错误,首先说原始的日期时间对象:2024-04-09 22:12:23, 这个本身是个业务时间,也是业务时区下,它是这个时间,假如业务时区是GMT+6,系统时区是GMT+8, 当你的java服务正确的拿到这个日期数据的时候,这个时间在系统时区下,时间变成了:2024-04-10 00:12:23。同一个时刻在不同时区下呈现出来的时间是不一样的,如果此时你在系统时区下,直接操作取它的一天开始时间,结果就变成了:2024-04-10 00:00:00(系统时区的), 这个时间对应的业务时区的时间:2024-04-09 22:00:00。得到的结果并不是业务时区下的0点,可能导致你的后续业务逻辑出错。
代码日期操作重新认知
时间日期基本认知
1. 时间戳
case:1712738764448
含义:代表一个绝对的物理时刻,是物理世界真实存在的某一个瞬间,是物理时间轴上的一个点
2. 时间字符串
case:2024-04-10 16:46:04
含义:时间戳+时区得到的一个用于人类沟通的时间字符串,在沟通中用来代码某一个时间戳或者代码某一个瞬间。
转换公式:
时间戳+时区->时间字符串
时间字符串+时区->时间戳
注意:按照上述公式,这个时间字符串必须知道是哪个时区的才有意义,如果不知道其时区,这个时间字符串没有任何实际的意义
3. 代码里的日期对象Date
其中fastTime是时间戳,cdate对象是时区信息
但是这里的cdate是不能修改的,它只能是系统默认的时区,所以我们可以认为Date对象本质上就是一个时间戳,时间戳是Date对象自己的属性,可以通过一些操作修改,在它需要用到时区的时候,使用系统默认时区即可。
官方还提供了带时区信息的日期对象:ZonedDateTime。
4. 时间戳与时间字符串的关系
结论:相同的时间戳在不同的时区下,年、月、日、时、分、秒可能都不一样
日期操作的重新认知
对日期对象的算术操作(加减法)本质上就是对时间戳的操作
常见的日期操作:加减一天、取一天的开始或者结束、设置年月日时分秒为具体值、求天数差。
理论上最简单的办法就是代码里只要涉及的日期的计算就转到目标时区下做,这样可以保证万无一失。
不过从技术上,我们可以做一下简单的区分,哪些操作是不需要切换时区的
原则上:只要一个操作对时间戳的偏移量在所有时区都相同,那么这个操作就是可以不用切换时区
不需要转换时区,只需要同时区下的运算
-
日期比大小
-
日、时、分、秒的加减
需要在目标时区的运算
-
时间字符串的展示
-
直接set具体的年、月、日、时、分、秒
-
获取一天的开始、结束时间
-
获取一年的最后一天
-
直接获取年月日
-
业务目前存在需要设置的具体小时数:0,6,12,14
-
-
年、月的加减