程序员应该如何处理时间

 

每天早上9:00到公司,迟到要罚款;中午12:00去吃饭,慢了好吃的就没有了;晚上18:00要回家,晚了老婆不开心。 ​ 我们熟练的使用着时间,毕竟小学二年级课本上就开始讲时间了。

可时间究竟是什么?

  • 哲学家认为过去、现在、未来等时间概念只不过是人的幻觉。

  • 牛顿说:绝对钟的读数就是时间。

  • 爱因斯坦说,时间和空间一起,构成了被称为时空的实体。

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

地球上的时间

大神考虑的内容要么是心理的,要么是宇宙的,要么就是微观的。其实我们了解地球上的时间就够了。 ​ 一般人都知道时区:为了克服时间上的混乱,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天了。

  • 问题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接口中继续使用原始类型。

  6. 查询2019-01-1到2019-01-02两天的数据,这个查询条件应该如何表示?

    有人说应该写time>=2019-01-01 00:00:00 && time < 2019-01-03 00:00:00(后面将这个表示法称为方法A)

    有人说方法A太不人性了,明明结束时间是2019-01-02,条件里面确实2019-01-03,非常的反人类,应该写成 time >= 2019-01-01 00:00:00 and time <= 2019-01-02 23:59:59 999(后面将这个方法称为方法B)

    到底用哪种方式比较好呢?我来补充一个信息吧,这个世界上有一种东西叫“闰秒”,参见百科https://baike.baidu.com/item/%E9%97%B0%E7%A7%92/696742

    是不是被吓到了,我们用下面程序来验证一下2017-01-01 07:59:59之后到底是不是2017-01-01 07:59:60

    ZonedDateTime time = LocalDateTime.of(2017, 1, 1, 7, 59, 59)
            .atZone(ZoneId.of("Asia/Shanghai"));
    ZonedDateTime newTime = time.plus(1, ChronoUnit.SECONDS);
    ​
    System.out.println(time);
    System.out.println(newTime);
    2017-01-01T07:59:59+08:00[Asia/Shanghai]
    2017-01-01T08:00+08:00[Asia/Shanghai]

    居然答案不是07:59:60,到底谁错了呢?原来Java的时间处理依赖操作系统,不同操作系统上结果可能不同。闰秒也导致了不少软件问题,所以好多国家都在主张废除闰秒。方案B在使用的时候存在描述不准确的可能性,所以建议还是使用方案A确保结果准确。

    得到的结果是

发布了12 篇原创文章 · 获赞 11 · 访问量 503

猜你喜欢

转载自blog.csdn.net/boroborome/article/details/103794795