掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
日期時間的處理,一直是計算機系統(tǒng)中看似簡單,實則經(jīng)常爆雷的問題。

焦作ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
例如,每隔幾年,都會爆出的「千年蟲問題」的各種變種,通常因為系統(tǒng)在設(shè)計之初,沒有設(shè)計好日期時間的數(shù)據(jù)存儲方式,或者低估了產(chǎn)品設(shè)計的生命周期,導(dǎo)致最初選型的數(shù)據(jù)結(jié)構(gòu)不夠用了。
千年蟲問題:
如果說,「千年蟲」是在時間維度上缺乏前瞻性的設(shè)計導(dǎo)致的,那么另一種缺乏前瞻性的問題,是空間維度的,即產(chǎn)品全球化、跨時區(qū)帶來的問題。
全球化的產(chǎn)品中,如果時間的處理沒有遵循統(tǒng)一的標(biāo)準(zhǔn),會讓整個系統(tǒng)充斥著難以理解和維護(hù)的時間轉(zhuǎn)換。各種接口的對接文檔,都不得不明確說明「這個接口的時間是什么時區(qū)的?需要如何處理?」后端服務(wù)如果需要跨國部署在多個大洲的機房時,因為服務(wù)器的時區(qū)不同,需要做大量的改造。
遺憾的是,大多情況下,產(chǎn)品不會一開始就有「全球化」屬性。所以在一開始,產(chǎn)研團(tuán)隊都不會重視全球化的設(shè)計問題,很容易留下缺乏前瞻性的設(shè)計問題。
通常情況下,我們都不鼓勵「過度設(shè)計」。然而,日期時間的設(shè)計,是最不怕「過度」的。這時因為,在技術(shù)上實現(xiàn)一個前瞻的時間日期方案,成本并不高;但如果一開始的設(shè)計不夠,后期的升級和數(shù)據(jù)遷移工作,卻是傷筋動骨的。
在微服務(wù)之間,以及在前后端之間,建議用字符串傳遞日期時間。字符串清晰易讀,易于人工調(diào)試,帶來的開銷通常也完全可以接受。(帶大量時間數(shù)據(jù)的接口,建議考慮用 Unix Timestamp。)
如果用字符串,格式就不要自己發(fā)明了。有個非常明確的國際標(biāo)準(zhǔn):ISO 8601(wikipedia: https://en.wikipedia.org/wiki/ISO_8601)
下面舉例是符合規(guī)范的常用格式:
注意,MySQL 中使用的字符串格式(如 2022-02-09 12:36:42)并不符合規(guī)范,不建議使用。
不同數(shù)據(jù)庫在時間日期相關(guān)對象的處理差異很大。這里單說 MySQL,因為坑不小。
MySQL 的 DateTime 數(shù)據(jù)在存儲時并不包含時區(qū)信息,因此,在讀取時也不會做任何時區(qū)的轉(zhuǎn)換。
同時,每個 MySQL 連接會話,都有「會話時區(qū)」的概念,但這個概念只影響 MySQL 的 NOW() 等有關(guān)當(dāng)前時間的函數(shù)的行為,對數(shù)據(jù)中已經(jīng)保存的 DateTime 沒有任何影響。
例如:
SET time_zone = '+00:00' ;
UPDATE tab SET datetime_colume = '2020-01-01 00:00:00';
SET time_zone = '+08:00' ; -- 換一個會話時區(qū)
SELECT datetime_colume FROM tab;
-- 返回值仍然是 '2020-01-01 00:00:00',和寫入的數(shù)據(jù)一致,和會話時間無關(guān)
---------
SET time_zone = '+00:00' ;
SELECT NOW(); -- 假設(shè)返回 '2022-01-01 00:00:00'
UPDATE tab SET datetime_colume = NOW(); -- 存入的是 '2022-01-01 00:00:00'
SET time_zone = '+08:00' ; -- 換一個會話時區(qū)
SELECT NOW(); -- '2022-01-01 08:00:00' 根據(jù)時區(qū)變化了
SELECT datetime_colume FROM tab; -- '2022-01-01 00:00:00' 已經(jīng)寫入的不會變
各語言一般都提供了原生的 DateTime 數(shù)據(jù)類型,以表達(dá)絕對的日期時間,并且都支持上面 ISO 8601 規(guī)范的解析和格式化。
處理相對時區(qū)時,各種語言通常都是使用操作系統(tǒng)的時區(qū)數(shù)據(jù)庫,來轉(zhuǎn)化為絕對時區(qū)。時區(qū)數(shù)據(jù)庫需要在聯(lián)網(wǎng)情況下,由操作系統(tǒng)負(fù)責(zé)定時更新。
Unix Timestamp 在存儲、計算、傳遞環(huán)節(jié)都可以使用,可謂萬能。它唯獨不適合表達(dá)紀(jì)念日日期。
它通過一個數(shù)值表示了一個絕對時間與 Unix Epoch 時間(定義為 1970-01-01T00:00:00Z)的差值秒數(shù)。Unix Timestamp 本身已經(jīng)表達(dá)了絕對時間,并不需要時區(qū)信息。
使用 Unix Timestamp 時,應(yīng)特別注意選用合適的數(shù)值類型,它會影響時間表示的范圍。稍不留神,你就可能種下一個新的千年蟲。
本著不重不漏的原則,我們可以按如下表格劃分產(chǎn)品中的所有日期時間對象:
指明時區(qū),需根據(jù)用戶所在時區(qū)做轉(zhuǎn)換② 表示全球唯一確定時間點④ 表示全球可理解的重復(fù)性時間 不存在的場景
下面逐一解釋這五種場景。
信息量包含「年月日-時分秒-時區(qū)」。這樣,就可以完全確定歷史長河中的一個無歧義的時間點。這個時間點是完全客觀的,和訪問的用戶地理位置無關(guān),和服務(wù)器的地理位置無關(guān),和什么都無關(guān)。
產(chǎn)品表現(xiàn)上,通常會根據(jù)查看者所在的時區(qū)來重新調(diào)整時間的顯示。
用途舉例:
包含「年月日-時分秒」,因為沒有時區(qū)信息,所以它本身并不能確定一個精確的時間點,而是只在特定的情境下才有意義。
所謂特定的情境,是因為業(yè)務(wù)場景中蘊含了時區(qū)的信息,并且是大家公認(rèn)的共識。因此,本質(zhì)上它仍然表示了一個絕對時間。在產(chǎn)品表現(xiàn)上,因為對時區(qū)的共識,所以不需要根據(jù)查看者的時區(qū)來調(diào)整時間的展示。
用途舉例:
和前兩類相比,去掉了「日期」這個信息,是為了描述重復(fù)性的日程。它可以是指明了時區(qū)的,也可以不指明時區(qū),而基于人們對時區(qū)的共識去理解。
用途舉例:
日期對象幾乎只有一個有意義的用途:表示紀(jì)念日/節(jié)日。它不會包含時區(qū)信息。
認(rèn)為「日期」只能用于「紀(jì)念日」,有些絕對了。但我確實查閱了很多資料,也沒有看到任何非「紀(jì)念日」用途的日期。
例如:
產(chǎn)品體現(xiàn)上,不需要根據(jù)時區(qū)調(diào)整日期的顯示。本質(zhì)上,「紀(jì)念日」的邏輯,其實是人腦的不嚴(yán)謹(jǐn)導(dǎo)致的一種習(xí)慣,是不嚴(yán)謹(jǐn)、不客觀的習(xí)慣。不包含時區(qū)信息,就是為了滿足這種不嚴(yán)謹(jǐn)?shù)牧?xí)慣。
上面說過,日期對象不能包含時區(qū)。你可能會問,我需要表示“北京時間 2022 年 3 月 22 日”呢?答案是:這不是一個日期,而是一個「精度不高的絕對時間」。
很多情況下,當(dāng)你想用日期時,其實很可能需要的是個「精度不高的絕對時間」。在飛書人力套件的業(yè)務(wù)中,經(jīng)常會遇到這種場景。
例如,一個在美國的同學(xué)與一個在日本的同學(xué),都在 2022 年 3 月 22 日這天從公司離職了,由同一個在北京的 HR 辦理離職事項。
可見,從我們用戶視角理解的「一個事件發(fā)生的日期」,其實是我們忽略了時間的精度。在產(chǎn)品全球化之前,我們通過一些默認(rèn)的簡化,忽略了時間精度的問題(例如把時間都填成 00:00:00)。一旦面臨產(chǎn)品的全球化,就需要補齊時間、提高精度。
而補齊時間、提高精度的方式,需要根據(jù)具體的產(chǎn)品形態(tài)具體考慮、明確定義。
例如,在上述離職場景下,就需要按照這個公司對離職的定義來補充,可以是當(dāng)?shù)貢r間當(dāng)天的 23:59:59,也可以是當(dāng)天下班時間,如 17:00:00。
又比如,對于跨團(tuán)隊的業(yè)務(wù),例如一個同學(xué)的上級匯報線從一個美國 Leader 轉(zhuǎn)到一個日本 Leader,那么為了避免歧義,通常會約定一個確定的生效時區(qū),如統(tǒng)一按照公司的總部所在地的時間來計算。
適用于上面的 ①②③④ 四種場景。
所有后端暴露的接口中的時間對象,全部以 UTC 時間表示。
同時,所有后端在存儲、計算、傳輸時間時,也統(tǒng)一使用 UTC 時間。由于 DB 存儲時間時,時區(qū)信息會被丟掉,因此應(yīng)保證丟掉的時區(qū),是大家明確約定清楚的無歧義的,即 UTC。這樣一來,DB 中的所有時間字段也都沒有歧義。
接口內(nèi)部產(chǎn)生的時間,例如 CreatedAt、UpdatedAt時間,都應(yīng)該轉(zhuǎn)換為 UTC 再落盤。如果直接使用了 MySQL 的NOW()函數(shù),應(yīng)確保 MySQL Session 的時區(qū)設(shè)置正確。
在前端或 BFF 負(fù)責(zé)處理用戶輸入的時間,以及展示給客戶看到的時間。包括兩個步驟:
處理“精度不高的時間”問題。比如:員工異動的生效時間,用戶只設(shè)置到“天”的精度。那么如果不跨國,可以補全用戶會話時區(qū)的 00:00:00 為精確生效時間;如果跨國,那就看客戶如何定義,以及產(chǎn)品給客戶怎樣的靈活性:例如,可以以客戶公司總部所在地的時區(qū)的 00:00:00 為精確生效的時間。
時區(qū)轉(zhuǎn)換。 注意,這里不一定是使用用戶的會話時區(qū)來轉(zhuǎn)換。如前面介紹的飛機火車賓館的預(yù)定時間,就要以預(yù)定當(dāng)?shù)氐臅r區(qū)來轉(zhuǎn)換。
上述兩點,是一定需要在產(chǎn)品設(shè)計中定義清晰的,切忌含糊不清。
不要較真兒抬杠的幾點:
適用于上面的 ⑤,即紀(jì)念日場景。
輸入或展示時,都不對日期做任何處理。日期對象直接保存在 DB 中。
只有真正的紀(jì)念日有必要用這種方式,應(yīng)當(dāng)非常謹(jǐn)慎。例如保存一個聯(lián)系人的生日時。
使用絕對的時差來表示時區(qū),例如:“東 8 區(qū)”表示比世界協(xié)調(diào)時間(UTC)早 8 個小時的時區(qū)。這是個客觀的時區(qū)。
很多時候,我們關(guān)注的是一個城市或地區(qū)的時區(qū)。例如:Asia/Shanghai 表示中國時間;三字母的縮寫 EST 表示美國東部標(biāo)準(zhǔn)時間。注意,這些根據(jù)地理位置定義的時區(qū)的時差是會發(fā)生變化的,變化因素包括:可能受到當(dāng)?shù)卣叩挠绊?,或夏令時影響。
對于歷史的時間,地理時區(qū)是可以確定客觀時區(qū)的,因為沒有人會重新定義已經(jīng)過去的時間。
對于未來的時間,地理時區(qū)并不能確定客觀時區(qū)。因此,如果一個未來的事件是按照非絕對時區(qū)約定的,那么它很可能變化。并且,我們的產(chǎn)品需要考慮到處理這種變化。
例如,中國員工發(fā)起一個“每天早 8 點”的跨國會議,那么在美國,由于夏令時的改變,冬天開會的時間和夏天是不一樣的。反之,美國員工發(fā)起的一個“每天早 8 點”的跨國會議,由于美國夏令時的變化,對中國員工的時間也是夏天和冬天不一樣的。
某些國家在夏天,會把時間調(diào)快一小時(提前一小時)。這表現(xiàn)為,同一個地區(qū),在冬天和夏天用不同的絕對時區(qū)。
這樣做,是因為夏天白天很長,調(diào)整后會在白天的更早的時段上班,從而下班后有更長的天亮的時間。注意,并不是把 10 點上班調(diào)整到 9 點上班,而是全社會重新定義了 10 點提前了一小時。
一個具體的例子,在美國:
Wikipedia: ISO8601 - 用字符串表達(dá)各種時間對象的標(biāo)準(zhǔn)??https://en.wikipedia.org/wiki/ISO_8601??
RFC3339 - 互聯(lián)網(wǎng)上關(guān)于時間和日期實現(xiàn)的通用建議
??https://www.rfc-editor.org/rfc/rfc3339??
RFC5545 - iCalendar 互聯(lián)網(wǎng)日歷應(yīng)用的規(guī)范
??https://datatracker.ietf.org/doc/html/rfc5545??
Stackoverflow: Daylight saving time and time zone best practices [closed] - 技術(shù)實現(xiàn)建議
https://stackoverflow.com/questions/2532729/daylight-saving-time-and-time-zone-best-practices
Stackoverflow: How to store repeating dates keeping in mind daylight saving time - 技術(shù)實現(xiàn)建議
https://medium.com/@vivekmadurai/how-to-deal-with-date-and-time-across-time-zones-39b1bd747f35
Medium: How to Deal with Date and Time across Time Zones - 技術(shù)實現(xiàn)建議
https://medium.com/@vivekmadurai/how-to-deal-with-date-and-time-across-time-zones-39b1bd747f35
Microsoft365: Behavior and format options of the Date and Time field - 微軟的時間和日期字段的文檔
https://docs.microsoft.com/en-us/dynamics365/customerengagement/on-premises/customize/behavior-format-date-time-field?view=op-9-1
Time Change 2021 in the United States - 美國 2021 年夏令時的調(diào)整方式
??https://www.timeanddate.com/time/change/usa?year=2021??

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流