程序員應該如何處理時間

 

每天早上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確保結果準確。

    得到的結果是

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章