每天早上9:00到公司,迟到要罚款;中午12:00去吃饭,慢了好吃的就没有了;晚上18:00要回家,晚了老婆不开心。

我们熟练的使用着时间,毕竟小学二年级课本上就开始讲时间了。

可时间究竟是什么?

  • 哲学家认为过去、现在、未来等时间概念只不过是人的幻觉。
  • 牛顿说:绝对钟的读数就是时间。
  • 爱因斯坦说,时间和空间一起,构成了被称为时空的实体。

看看大神的答案,本来可以理解的时间,一下子就糊涂了。人类文明探索了几千年,微观上看到了夸克交子,宏观上找到了黑洞,但是面对时间我们仍然不能给出确定的答案。


地球上的时间

大神考虑的内容要么是心理的,要么是宇宙的,要么就是微观的。其实我们了解地球上的时间就够了。

拨开时间的迷雾_java

一般人都知道时区:为了克服时间上的混乱,1884年在华盛顿召开的一次国际经度会议上,规定将全球划分为24个时区(东、西各12个时区)。


客户的疑问

有了时区,已经可以解决地球上一般问题了。但是面对全球业务的客户,他们还是会提出很多问题:

  1. 为什么要问我使用哪个时区显示时间?上游发给我们什么就显示什么呀?
  2. 计算两个时间差几天,为什么需要选择使用哪个时区?(客户的规则2019-01-01 23:59:59到2019-01-02 00:00:00,算一天)
  3. 上游给了时间和时区两个字段,我们存下来后,数据库里面显示也是正常的,为什么就没有时区了?

DDD中最重要的一个过程就是统一语言。和客户沟通时间问题的时候,可以先把一些时间的概念统一一下。给客户建立这样的时间概念:本地时间、时区、时区时间、绝对时间 。

  • 本地时间:一般人讲的时间都是本地时间,它只在当地有效。类似2019-12-24 10:22,北京的这个时间和伦敦的这个时间可不是一个时间。

  • 时区:使用相同本地时间的区域。类似Asia/Shanghai,America/Havana,为了便于理解我经常标记为UTC+8,UTC-8,这两种标记方法并不等效,不是一个概念,客户一般不关心这个,不用说,程序员自己心里明白就好,后面讲差别。

  • 时区时间:带有时区的时间,可以认为是可读的绝对时间。类似2019-12-24 10:22 (Asia/Shanghai),同样我也会写成2019-12-24 10:22 UTC+8,因为带有了时区信息,这些时间就可以在不同时区间转换了。

  • 绝对时间:也可以叫时间戳,是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。尽量少讲绝对时间,有些客户不太理解这个。

有一次我这样和客户讲:我们在北京看到的12点是北京时间12点,在伦敦看到的12点是伦敦的12点,那么如果我们在月球,看到的12点是什么呢?所以脱离了地球的24个时区,时间还是存在的,这种不因时区而变化的时间就是绝对时间。有了这些基本概念后,围绕着这些基本概念解答问题,而且主要以举例子的形式讲解。

  • 问题1:不同时区显示的时间不同,比如如果一个货物是在英国中午12:00发货,此时是中国的20:00,那么一个中国用户想要看到的是12:00还是20:00呢?

    如果客户选择12:00,说明用户关心本地时间,系统应该使用事件发生地时区显示时间;

    如果客户选择20:00,说明用户关心绝对时间,但是绝对时间没法显示,还是要选择一个时区,所以使用用户最舒服的时区,他自己的时区。
  • 问题2客户问这个问题,十有八九是以为时区只影响时间,忘记了“日期变更线”,可以举一个极端的例子,举例子的时候时间一定要带上时区。

2019-01-01 23:59:59 UTC+8到2019-01-02 00:00:00 UTC+8,中间差一天;如果转换时区到UTC+7,就变成了2019-01-01 22:59:59 UTC+7到2019-01-01 23:00:00 UTC+7,中间差0天了。

拨开时间的迷雾_java_02

  • 问题3:这个问题非常有挑战,用户都说到“数据库”了。看起来不把时间戳讲一讲是搞不定了,实际上客户真的不太理解时间戳。时间戳、绝对时间都非常的技术,客户接受不了。当需要表达时间戳的时候,我一般说成是格林尼治时间,我们把所有时间都转换成0时区的时间保存了,这样比较方便比较。客户追问:把本地时间和时区放在一起得到的数据,这个数据里面一定有时区呀?答:这个过程就像2+8=10,但是通过10,无法找到2和8。计算机在存储绝对时间时做了类似的事情。

总结下来和客户沟通的主要手段就是:统一语言加举例子


程序员的时间

常见问题
1. java.util.TimeZone和java.time.ZoneId,这两个东西干什么的?有什么区别?

  • TimeZone是JDK7以前的原生时区,ZoneId是JDK8以后的原生时区。他们功能是一样的,ZoneId是从joda-time到jdk里面来踢场子的。
  • TimeZone提供了toZoneId(),ZoneId没有提供toTimeZone(),但是TimeZone提供了getTimeZone(ZoneId),看来ZoneId比TimeZone更为基础,推荐使用ZoneId。

2. Australia/Canberra 和 UTC+11:00有什么区别?

  • 在提到时区的时候,我们会想到Australia/Canberra或者UTC+11:00,但是这两个东西并不等价。UTC+11:00其实是偏移量,与任何国家不相干,对应固定的经度区间,157度30分~172度30分;Australia/Canberra是行政时区,采用相同时区的地区,在地理位置上的偏移量可能不同,中国跨越了5个时区,但是全国还是统一使用UTC+8;有些国家的政策也可能调整,具体的偏移量也会变,采用夏令时的地区每年都会变,具体什么时间调整也是政策决定的。

    OffsetDateTime对应UTC+11:00,固定偏移量;ZonedDateTime对应。Australia/Canberra,偏移量不一定是固定的,对于Australia/Canberra一般是UTC+11,有时也会变成UTC+10。
  • 下面demo中同一个Zone的两个时间2015-10-04 01:00和2015-10-04 03:00,使用了不同的时区,看起来相差两小时,实际上仅仅相差1小时。
ZoneId zoneId = ZoneId.of("Australia/Canberra");ZonedDateTime start = LocalDateTime.of(2015, 10, 4, 1, 0)      .atZone(zoneId);ZonedDateTime end = LocalDateTime.of(2015, 10, 4, 3, 0)      .atZone(zoneId);
System.out.println(MessageFormat.format("Start:\t{0}\nEnd:\t{1}\nDuration:\t{2}",       start, end, Duration.between(start, end)));

输出结果为:

Start:2015-10-04T01:00+10:00[Australia/Canberra]End:2015-10-04T03:00+11:00[Australia/Canberra]Duration:PT1H

所以使用类似Australia/Canberra的这种ZoneRegion才能得到真正可靠的本地时间。
3. ZonedDateTime vs OffsetDateTime

  • ZonedDateTime提供了toOffsetDateTime(),OffsetDateTime也提供了toZonedDateTime(),他们互惠互利,互通有无,和睦相处。但是,一个ZonedDateTime在经历了toOffsetDateTime()、toZonedDateTime()再回到ZonedDateTime的时候已经不是原来的ZonedDateTime了,它把它原来的Australia/Canberra弄丢了。所以不要随便toOffsetDateTime()。

4. GMT vs UTC 

GMT,格林尼治标准时间(旧译“格林威治平均时间”或“格林威治标准时间”)是指位于伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。

协调世界时(UTC) 英文:Coordinated Universal Time ,别称:世界统一时间,世界标准时间,国际协调时间, 协调世界时,又称世界统一时间,世界标准时间,国际协调时间,简称UTC。

GMT的历史比UTC悠久,UTC出现后GMT就开始参考UTC时间,基本可以认为GMT=UTC。5. Restful接口中建议使用Date、String、long来保存时间。

com.alibaba.fastjson.JSON可以正常的将java.time中内容正确的保存和读取到json中;com.fasterxml.jackson.databind.ObjectMapper保存的结果不理想。因此建议在restful接口中继续使用原始类型。