您可能認為處理日期和時間很容易。我們有一分鐘持續 60 秒,一小時持續 60 分鐘,一天持續 24 小時,一週持續 7 天,一個月持續 28 到 31 天,依此類推。
這裡當然不需要火箭科學......
好吧,事實並非如此!
我們將展示您在應用程式和後端開發過程中可能遇到的與日期和時間相關的陷阱和陷阱。
讓我們從測量單位開始,從最小到最大。
每天使用的最小單位是秒。它也是 Unix 時間的基礎。
但是,在某些程式語言(例如 Java)中,最常見的單位是毫秒(秒的 1/1000),例如 System.currentTimeMillis() 方法。
這種分歧可能會導致許多錯誤。
如果您從系統外部收到數值,即使在閱讀文件之後,乍一看也可能不清楚它使用的測量單位!
查看簡訊和彩信內容提供者(資料庫)欄位中的日期欄位。兩份文件都只說:
收到訊息的日期。
型態:INTEGER(長整型)
但是,簡訊使用毫秒,而彩信使用秒。驚訝嗎?好吧,這種情況可能會發生,特別是如果此類 API 是由不同的人設計的。
遇到這樣的情況會如何處理?那麼如何才能避免它們呢?
幸運的是,我們可以製定幾個通用規則:
始終確保傳入資料的格式。
請在野外檢查它,因為文件可能不正確和/或過時。當您在開發過程中尋找錯誤時,通常很容易發現錯誤,例如未來 5 萬年太遠的日期。例如,當毫秒被視為秒時,就會出現此結果。
如果您對發送值的一方有任何影響(例如,系統正在設計但尚未有人使用),請考慮標準化文字格式。
在這裡,我強調標準化(例如 ISO 8601)而不是某些自訂格式(稍後我們將擴大這個主題)。幾年後,原來團隊中的任何人都可能不再為該專案工作,並且文字格式對於新開發人員來說很容易理解,因為他們不需要查看文件。
然而,在性能關鍵型設備中,數位格式可能會更好。
**使用專用類別來處理日期/時間/持續時間值,而不是原始整數。
**在 Java 世界中,我們有一個 java.time 包,其中包含許多有用的類,例如 Instant 或 Duration。如果有一個整數叫做eg。 eventDuration,不知道它儲存的是秒還是毫秒,甚至可能是天?
如果您必須處理無法完全重構的原始值(整數、長整數等),例如遺留程式碼,在變數/欄位名稱中包含測量單位 .
例如,eventDurationSeconds 是明確的。
您可能聽過閏年。它們與「正常」的不同之處在於多了一天。我們還有閏秒!
它們比非閏秒長嗎?
嗯,這要看情況!
首先,讓我們先從一些理論開始。地球正在減速;實際上,這不是一個哲學陳述,而是一個經過科學證明的事實。它被稱為 delta-T。
好的,那麼這在實踐中對我們意味著什麼?嗯,我們的時間測量單位有一個標準化的長度。基本單位是秒,定義為:
9 192 631 770 個輻射週期的持續時間,對應於銫 133 原子基本上未擾動基態的兩個超精細能階之間的躍遷。 (資料來源:bipm.org)
該持續時間是恆定的,並告知所有其他從秒導出的單位,例如。一分鐘由 60 秒組成,一小時由 60 分鐘(3,600 秒)組成,依此類推。
但是,如果地球減速(白天變長),我們必須以某種方式適應這種減速,以確保我們的單位測量的時間與現實一致。這是透過插入額外的秒數——閏秒來完成的。
一年中只有 2 個可用的閏秒時段:六月底和 12 月。最結束意味著最後一天(分別為 30 日或 31 日),即 UTC 23:59:59 之後(因此當地時間有所不同)。
在通常的最後幾秒之後,會在進入第二天之前插入額外的閏秒。因此,我們可以得到 23:59:60(一分鐘 61 秒——從 0 開始計數)。
由於減速不是恆定的,所以閏秒的插入是不規則的。最近一次(2021 年 4 月撰寫本文時)發生在 2016 年 12 月,距今已經 4 年多了。不過,倒數第二次是在2015年6月,兩者只相差1.5年。
在一些我們可以設定時間的地方-例如實體掛鐘或一些框架API,可能無法觀察和/或設定閏秒時間。
例如,老式的 Date Java 類別甚至支援雙閏秒 - 範圍從 0 到 61,因此 62 秒是可能的!
但是,java.time 套件中的現代 Instant 類別不會向程式設計師公開閏秒。閏秒在一天的最後 1,000 秒內均勻拉伸(這些秒更長)。
請注意,理論上,閏秒也可以是負數。但目前還沒有發生。
這意味著一分鐘可能有 59 秒,這可能會導致巨大的問題。例如,如果某個操作計劃在 23:59:59.001 發生,但結果發現所需的時間不存在......
幸運的是,與擴充可見秒類似,也可以縮小,對程式設計師完全透明。
我們知道第61秒可以存在,但第62秒呢?嗯,文檔說:
同一分鐘內發生兩個閏秒的可能性極小,但此規範遵循 ISO C 的日期和時間約定。
確實,在 C 規範中我們有一個 [0, 61] 範圍。但為什麼?同樣的問題也困擾著 1992 年 tzdata(時區資料庫)的作者 Paul Eggert。我們可以在檔案中讀到:
*「因為一年中確實有兩個閏秒(在不同的日子),而 ANSI C 委員會的某人認為它們是在同一天出現的。」*— 來源 groups.google.com。
幾十年前發生的輕微解釋錯誤現在仍然可見,因為新的實作需要向後相容這些標準。
讓我們看看應用程式和後端開發中的其他邊緣情況。
分鐘和小時不涉及任何意外情況,所以讓我們跳到日子。
一天是什麼?這取決於!你可以說它從00:00:00到23:59:60持續24小時(包括閏秒?)。好吧,無論後者是否普遍正確,一天並不總是持續 24 小時,因為它可能是 23、25 甚至 21 和 27!為什麼有這些價值觀?答案是…
所謂的 DST 或「夏令時」會在溫暖的月份提前時鐘,以減少功耗。黑暗開始的時間比「正常」(非 DST)時間晚。如今,夏令時的必要性存在爭議,因為它有許多缺點。由於這些原因,一些國家最近甚至停止了 DST(例如俄羅斯在 2014 年)。
夏令時有哪些問題?來看看吧!
我不會討論諸如忘記手動調整掛鐘或延遲公共交通之類的事情,而是,我將重點放在與後端和應用程式開發相關的方面。
您可能會認為,在夏季,我們將時鐘提前 1 小時。但情況並非總是如此!世界上有些地方的時差為 3 小時(如凱西)或 30 分鐘(如豪勳爵島)。
夏季,並不完全像名字所暗示的那樣,也可以覆蓋春季或秋季的部分時間,但一般來說,它與溫暖的月份有關。反過來,這取決於半球。
歐洲有夏天,澳洲有冬天,反之亦然。因此,從長遠來看,澳洲的夏令時發生在歐洲的冬季!
此外,夏季的時間定義取決於司法管轄區。在歐盟的所有國家中,時間都會在同一時刻更改,因此例如柏林和倫敦之間的時差始終為 1 小時,無論我們是在夏季還是冬季。
但是讓我們考慮一下雪梨和倫敦之間的時差。在這種情況下,這取決於一年中的哪一天!這是因為悉尼開始和停止遵守 DST 的日期與倫敦不同。
靈感:timeanddate.com
例如,從一月開始,我們有 10 小時的時差。悉尼當時遵守夏令時,但倫敦不遵守。 3月底,倫敦開始實施夏令時,而雪梨州則維持不變,因此時差縮小至9小時。然後到了四月份,雪梨停止遵守夏令時,所以我們有 8 小時。我們有 3 種不同的偏移量。所以關於雪梨和倫敦時差的問題有3個答案!
美國、歐盟成員國或澳洲等國家在夏令時方面擁有穩定的管轄權。轉變何時發生是預先眾所周知的。然而,情況並非總是如此,因為一些國家可能會意外地改變法律。例如,2020 年斐濟的規則發生了變化,公告僅在幾個月前發布。
在一些正式慶祝齋戒月的伊斯蘭國家,情況更為複雜。如果他們也遵守夏令時,後者可能會…暫停(在齋月期間)。
這意味著夏令時轉換可能會發生多次(來回),甚至一年內發生幾次。此外,齋戒月是根據伊斯蘭(農曆)日曆計算的。預測用於計算齋戒月邊界日期,它可能並不總是準確的。例如,2020 年巴勒斯坦的預測結果短了約一週。更改僅提前幾天應用。
大多數系統使用 IANA 時區資料庫作為 DST 轉換時刻的來源。該資料庫通常每年都會有一些更新。
請注意,處理 DST 可能會對演算法產生影響。讓我們考慮幾個典型場景。
如果我們將鬧鐘安排在特定的牆上時間(例如02:30),則可能會發現這樣的時刻不存在(當DST 轉換期間時間從02:00 更改為03:00 時)或當時間向後改變時,它會出現兩次。例如如果不做備份,不給藥或給兩次藥,那後果可能就很可怕了。
如果使用者位於觀察 DST 的時區,另一個常見的影響包括週期性觸發時間變化。例如,如果您安排在 bitrise.io 上的建置在 10:00 觸發,它可能會突然在 11:00 開始觸發。
發生這種情況是因為底層邏輯不知道 DST,且使用者可見的時間僅在渲染 UI 時計算。絕對時刻始終相同,但用戶可見的時間會根據夏令時而變化。通常,這不是顧客所期望的。
一週的第一天是哪一天?如果您住在歐洲,您可能會說星期一。在美國,這一天是星期日;在阿拉伯國家,則是星期六。這些事實可能會影響演算法建構和/或渲染日曆。
一年中的第幾週呢?你可能認為一切都是從1月1日開始的。
正如您可能已經猜到的,情況並非總是如此!
根據 ISO 標準,一年的第一週必須包含星期四。例如,2021 年 1 月 1 日是星期五,因此屬於上一年的最後一週。 2021年的第一周從1月4日開始!然而,並非所有語言環境都使用與 ISO 標準相同的規則。
世界上大多數人使用的公曆有 12 個月,從一月到十二月。然而,其他類型的日曆可能有更多的月份。例如,希伯來曆可能有 13 個月。第 13 個(在 IT 領域)稱為「十一月」。
通常,一年有 365 天。然而,每一個能被 4 整除的年份(例如 2020、2024 等)都是閏年,有 366 天(二月有 29 天,而不是正常的 28 天)。但是,如果它能被 100 整除(2100、2200 等),則它不是閏年。但 ?若能被 400 整除(2000、2400 等),則為閏年。
幸運的是,您不必(也不應該!)嘗試自己實現此類區別。您應該使用給定程式語言的眾所周知的日期類別/函數庫。
知名服務中存在許多與不正確的閏年計算相關的嚴重錯誤。甚至還有一個名詞:閏年問題。
當地時間取決於經度。雖然在某個特定地點是中午,但在地球的另一邊也是午夜。
旅行時連續調整時鐘是不切實際的,因此地球被劃分為多個區域,覆蓋具有相同當地官方時間的區域。
對於小國或最大國家(如澳洲、美國或俄羅斯)的某些地理區域,區域邊界通常等於國家邊界。
來源:timeanddate.com
由於某些地方的政治和經濟原因,官方時間與「合法」時間有很大差異(僅考慮經度/太陽時間)。例如,在西班牙,該區域與中歐大陸大部分地區相同,而不是英國,根據經度更接近。
大多數時區都有積分偏移(與 UTC — 標準時間的差異),例如。柏林 +1 小時(夏令時 +2)或布宜諾斯艾利斯 -3 小時(阿根廷,無夏令時)。然而,偏移量可以包括半小時,例如。在孟買(印度),我們的時間為 +5:30(無夏令時)。
如果這還不夠,也可以住宿,就像在加德滿都(尼泊爾)我們有 +5:45h!
幸運的是,在撰寫本文時,還沒有更細微的偏移。然而,它們過去曾存在過,例如荷蘭的+0:20。
由於我們的時鐘有 24 小時這一事實,人們可能會認為這也是一系列可能的偏移量。 +12:00 和 -12:00 加起來就是 24。
然而,時區之間最遠的時間距離是26小時!
這是可能的,因為我們有 +14:00 偏移量。大洋洲的幾個國家都採用了這種方式,因為它們與澳大利亞關係密切,所以相差幾個小時比超過 20 個小時更實際,這在大多數情況下會導致另一個日期。
讓我們考慮一個航班轉機查找器。對於往返航班,用戶可以選擇出發和回程日期/時間。可能很明顯回程時間一定是出發後。
當然,這與事實相差甚遠!
請記住,這些時間是在當地時區。
看一下這個例子:
00:30 從東京(出發時間+09:00)出發前往舊金山(出發時間-07:00)
當地時間前一天18:00抵達舊金山!
19:50 從舊金山返回(相對於最初出發時間仍早)
所以,你可以去某個地方,昨天就回來。看這個例子:
您可能面臨的應用程式/後端開發中的另一個潛在邊緣情況與時區命名有關。
您可能會注意到,我們可以透過偏移量來呼叫時區,例如。 +01:00 或其標識符,例如。歐洲/柏林。請注意,後一種表示法具有多種好處:它包含有關 DST 轉換的資訊(柏林在夏季的偏移量為 +02:00),並且還保存歷史資料。
例如,歐洲/華沙和歐洲/柏林區域現在似乎是相同的。它們全年都有相同的偏移量,夏令時轉換也總是在同一時刻發生。
然而,過去並非總是如此!例如,1977年柏林根本沒有夏令時,但華沙從4月3日到9月25日觀察夏令時(與我們今天的時間完全不同)。
請注意,時區識別碼是基於城市名稱建構的。這是故意的,因為國家邊界比城市名稱更容易改變。
例如,克里米亞實際上已從烏克蘭改為俄羅斯,但城市名稱保持不變。無論我們需要當前數據還是歷史數據,都可以使用歐洲/辛菲羅波爾區域。
假設某件事持續一天。因此,如果它從 09:15:00 AM 開始,那麼我們可以添加 24 小時並獲得結束時間(在本例中,僅是第二天的 09:15:00 AM)。
嗯,這並不總是那麼容易!請記住,當時鐘被人為地向前或向後調整時,我們可以進行夏令時轉換。這意味著一天通常持續 24 小時,但偶爾也可能是 23、25 甚至更奇怪的值,例如 27 或 23 個半小時(你還記得凱西和豪勳爵嗎?)。
在實現業務邏輯時,不要盲目地將天視為 24 小時,這一點很重要。您應該為此使用適當的日期/時間 API。例如,在 Java 中,我們有不同的類別:
期間,代表日曆天 — 非常適合訂閱有效性(以天、月或年為單位)
持續時間,代表連續時間,例如。 24 小時,無論日曆上的日期是什麼。它非常適合測量操作的跨度,例如行程或自設備/程序啟動以來的時間。
某些語言有不同的字詞來表示陽光明媚的狀態(英文 - day)和連續 24 小時(英文 - nychthemeron,但並不常用)。
日期/時間表示:
來源:xkcd.com
與其他系統通訊時(例如透過 REST API),就資料格式達成一致非常重要。如果您看到日期顯示為 10/12/2021,則它可能採用美國格式(10 月 12 日)或英國格式(12 月 10 日)。
最好使用一些明確的表示法!
對於日期和時間,我們有 ISO 8601 標準。請注意,不僅絕對日期/時間是標準化的,而且句點也是標準化的。使用正確的類型也很重要 - 本地(用於表示數據,如鬧鐘觸發時間或出生日期)或分區(用於表示時間時刻,如事件開始或拍照)。
自訂的非標準化格式通常會導致混亂和錯誤,因此請避免使用它們。
在 UI 中顯示日期和時間時,必須考慮使用者的區域設定。顯示格式取決於語言和區域。請看以下範例,它們都引用同一日期:
21/4/18 — 美國英文
18/04/2021 — 英國英語
2021 年 4 月 18 日 — 羅馬尼亞語
١٨/٤/٢٠٢١ — 沙烏地阿拉伯,阿拉伯語,採用東阿拉伯數字
日期/時間庫提供了預先定義的格式類型,例如java.time中的FormatStyle。如果可能的話使用它們。否則,您可以使用標準化模式符號建立更多自訂格式。有關更多詳細信息,請參閱 CLDR 文件。
值得注意的是,在月份、季度和星期幾的情況下,也有獨立的變體。它們僅在某些語言中有意義(在英語中無效)。例如,在波蘭語中,四月被稱為 kwiecień — 這是一個獨立版本。但是,如果它與某一天(例如 4 月 18 日)相關,則文字將變為 18 kwietnia.
處理日期和時間並不簡單。但是,如果您遵循上述標準並使用正確的類型,則可以避免巨大的錯誤。
我希望我對日期和時間邊緣情況的見解對您的專案有用。祝你好運!
最初於 2021 年 4 月 26 日發佈於 https://www.thedroidsonroids.com。
以上是應用程式和後端開發中的邊緣案例 - 日期和時間的詳細內容。更多資訊請關注PHP中文網其他相關文章!