最近在开发的时候遇到了一些跟时间相关的坑,这篇文章主要记录一下通过遇到的坑对时间属性的理解。

时间戳

引用百度百科的描述:

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

简单的理解下就是,从北京时间 1970年01月01日08时00分00秒 起一直到目前的秒数。这个秒数对于全世界的任何国家任何地方,在同一个时间点的值都是一样的。

举例来说:假如目前北京时间是 2017年11月11日凌晨0点0分0秒,此时的时间戳是 1510329600000 (毫秒)。这个时间戳代表了从北京时间 1970年01月01日08时00分00秒到北京时间 2017年11月11日凌晨0点0分0秒,地球一共转了 1510329600 秒。这是一个绝对的秒数,因为只要在地球上,所有国家都一起度过了这么多秒。

但是相同的时间戳,在不同的地区代表了不同的时间,我们都知道日本比中国快了一个小时,所以时间戳 1510329600 (秒)在中国代表的是 2017年11月11日凌晨0点0分0秒,但是在日本代表的是 2017年11月11日凌晨1点0分0秒。

所以就引伸出了时区的概念。

时区

引用百度百科的一段描述:

地球是自西向东自转,东边比西边先看到太阳,东边的时间也比西边的早。东边时刻与西边时刻的差值不仅要以时计,而且还要以分和秒来计算,这给人们带来不便。

为了克服时间上的混乱,1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议[1] )上,规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东1-12区,西1-12区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时,相邻两个时区的时间相差*1*小时。例如,中国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间晚1小时。因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致。凡向西走,每过一个时区,就要把表拨慢1小时(比如2点拨到1点);凡向东走,每过一个时区,就要把表拨快1小时(比如1点拨到2点)。并且规定英国(格林尼治天文台旧址)为本初子午线,即零度经线

那么如何使用时间戳确定在某一地区代表了什么时间呢?这就需要了时间戳+时区来确定。如果有了时间戳并且有了时区这样的话就可推算出来,在当前时区的用户此时此刻是什么时间了。

Java 时间

JDK1.8 之前

大家比较常用的类就是 java.util.Date 类:

1
2
3
4
5
public static void main(String[] args) {
Date date = new Date();
System.out.println(date);
}
//输出结果:Sat Nov 11 10:48:46 CST 2017

当然 Date 类设计的有多烂的例子我就不举了。Date 在 java 当中的含义是代表了一个瞬时的状态(时间线上的一个点),也就是对时间戳的一个封装。只是从 toString 方法我们就可以看到 CST ,它代表了什么呢,是 China Standard Time 即北京时间。因为在 toString 的时候取了用户当前的时区来进行输出。

JDK1.8 之后

jdk1.8 当中新增了 java.time 包,这个包里面的一些类和概念基本上覆盖了 joda-time 的实现概念。并且在 jdk1.8 之后的 joda-time 已经声明不会继续维护了。

Java 日期和时间 API 规范邀请 Java 使用如下时标:

  • 每天都有 86400 秒
  • 每天正午于官方时间(UTC)准确匹配
  • 其他时间也要以一种精确定义的方式于其紧密匹配

具体的使用说明以后再讨论。

如何设计好跟时间有关的功能

最近项目当中遇到了一些跟时区相关的坑。

举个例子详细描述下:

  1. 设计一个活动 A,全球的用户都必须在各自时区的一个日期才可以参加这个。例如 2017年11月11日可以参加双11下单,只有你的时区的日期变成了 2017年11月11日 才可以参与下单。
  2. 此时我们库里面存的是 Date 类型,那么服务器是在北京的,所以默认时区也就是北京时区了,即 UTC+8 时区。
  3. 入库的时候会转成相应的 UTC 时间,即存的是 2017-11-10 16:00:00 这个时间,因为北京时区 +8,所以 +8 小时后正好是 11 号零点。
  4. 用户参加活动的时候,需要来判断,这个时区的用户是不是可以参加活动。

上面例子的判断时候转换是比较麻烦的,因为我们存的北京时间所以需要先把开始时间转成 yyyyMMdd 字符串。然后在根据用户时区转换成 yyyyMMdd 的零点的那个 Date,例如是日本的用户,那么转换后的开始时间就是 2017-11-10 17:00:00 UTC时间,因为日本是 UTC+9 时区。也就是在北京服务器当中处理的时候是 2017年11月11日凌晨1点。

比较麻烦的是判断用户当前时区的时间点,与开始时间相差了几天的计算。这个相当于再次把转换后的 2017年11月11日凌晨1点。又转换成了 yyyyMMdd 然后再次转成服务器当前时区的时间进行比较处理。

如果使用了 yyyyMMdd 来处理的话就简单了很多,只需要把 yyyyMMdd 转换成对应的时区的凌晨就好了。

如果想要设计的功能,在不同地区都是相同的日期开始的话。比较建议的方式是把开始日期设计成字符串类型,例如 yyyyMMdd 这样的。这样的话就不需要存一个时间类型到库当中。

—EOF—