互聯網上的日期和時間
最近參加公司Hackthon,自己寫的項目,同事寫的項目,都踩了時間的坑。以前升級Celery到4.1.0之後定時任務同樣出了時區的BUG。時間/時區這塊,大家經常用,但是卻沒有系統的整理總結,遂寫此文。
日期(Date)、時間(Time)和時間戳(Timestamp)
互聯網這麼大,覆蓋了全球這麼多時區。如果沒有統一的標準來規定怎麼表示時間,那麼一定是混亂的,無法正常運行的。我們需要一個統一的標準,來規範統一不同語言、不同時區對同一個時刻的表示。
國際標準組織(ISO)制定了ISO 8601,我們的中華人民共和國國家標準GB/T 7408-2005《數據元和交換格式 信息交換 日期和時間表示法》與ISO 8601:2000等效採用。
上面兩個標準,對開發者而言,太過於嚴肅。其實有一份更加簡單易讀的文件 RFC3339 Date and Time on the Internet: Timestamps。本文之後的討論,都基於 RFC3339。
RFC3339 比 ISO 8601 有一個很一個明顯的限制,這裡提一下:ISO允許24點,而 RFC3339 為了減少混淆,限制小時必須在0至23之間。23:59過1分鐘,是第二天的0:00。
閱讀RFC3339,我主要明確下面三個定義:
- 時間戳
- 本地時間
- 標準時間
時間戳
時間戳是一個數字,定義為格林威治時間1970年01月01日00時00分00秒(北京時間1970年01月01日08時00分00秒)起至現在的總秒數。注意,同一時刻,不同時區獲得的時間戳是相同的。以前很多用來記錄時間的欄位,在資料庫中往往不會存儲為Datetime類型,而是直接存儲為無符號整形,存放時間戳的值。
Python獲取時間戳的代碼為
import timenint(time.time())n
本地時間
當前時區的本地時間
import datetimendatetime.datetime.now()n
上面的輸出值為
2017-12-08 11:50:03.537506
標準時間
本地時間只包括當前的時間,不包含任何時區信息。同一時刻,東八區的本地時間比零時區的本地時間快了8個小時。在不同時區之間交換時間數據,除了用純數字的時間戳,還有一種更方便人類閱讀的表示方式:標準時間的偏移量表示方法。
RFC3339詳細定義了互聯網上日期/時間的偏移量表示:
2017-12-08T00:00:00.00Z
這個代表了UTC時間的2017年12月08日零時
2017-12-08T00:08:00.00+08:00
這個代表了同一時刻的,東八區北京時間(CST)表示的方法
上面兩個時間的時間戳是等價的。兩個的區別,就是在本地時間後面增加了時區信息。Z表示零時區。+08:00表示UTC時間增加8小時。
這種表示方式容易讓人疑惑的點是從標準時間換算UTC時間。以CST轉換UTC為例,沒有看文檔的情況下,根據 +08:00 的結尾,很容易根據直覺在本地時間再加上8小時。正確的計算方法是本地時間減去多增加的8小時。+08:00減去8小時才是UTC時間,-08:00加上8小時才是UTC時間。
為什麼我們會踩坑
了解了本地時間和標準時間之後,很容易想到為什麼我們會在這裡出問題。
很自然的,開發者編寫代碼處理用戶輸入的時間類型數據,處理的都是本地時間的格式。而計算機系統處理邏輯,依賴標準時間。從用戶輸入的本地時間,轉化成系統能處理的標準時間,這個過程是系統隱式的自動轉換。當系統的時區設置和用戶所處的時區不一致的時候,時間經過一次輸入,處理之後再輸出,就有了差異。
為了避免在時間處理上出現問題,核心就是統一輸入輸出時間的轉化。因此解決的思路也是兩個:
- 強制用戶輸入的時間類型是標準的,使用時間戳/標準時間。如果你的系統是給全球用戶使用的,應該採用這種功能方法。這是最準確,最不容易出錯的。
- 系統的時區和用戶的時區嚴格一致。項目規模小,全部都是一個時區的用戶。採用這種方案,一旦用戶跨時區了,還是會存在問題。不是很推薦。
時區的名稱可以查維基百科,也可以查官方的資料庫 Time Zone Database。
踩過的坑和解決方法
避免踩時間坑,總的改進方式:
- 系統最好都配好NTP,保證時間準確。
- 所有的輸入數據,最好轉換成標準時間,再交給業務邏輯處理。
- 在天朝,還是把系統時區設置成北京時間比較好。
下面是一些踩過的坑:
Docker 設置時區
很多Docker鏡像都是UTC時區,在上海使用鏡像啟用容器,容器裡面獲取的本地時間都會慢8個小時。可以在Dockerfile裡面設置時區。參考這個問題的回答
ENV TZ=Asia/ShanghainRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezonen
Ubuntu 設置時區
Docker裡面大部分鏡像是基於Debian的,Docker這麼解決,Ubuntu上思路類似,直接執行命令就好了。
TZ=Asia/Shanghainln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezonen
可以執行date
命令查看時間,CST是中國標準時間。
? ~ date
Fri Dec 8 14:12:46 CST 2017
Linux 和 Windows 雙系統時間差
以前寫過修復Ubuntu和Win10雙系統時間差。
Celery 4.1.0 時區計算錯誤
這個PR Correct calculation of application current time with timezone 已經修復了,但是現在還沒發布新版本。要麼回退4.0.2,要麼等下個版本。
Python 怎麼從本地時間轉換成標準時間
Python 裡面的 datetime.datetime.now() 拿到的是本地時間。如果用這個保存時間,請務必確保系統的時區設置正確,統一。
Python中間的時間戳、datetime之間的轉換,最好強制寫清楚時區。這是個例子
# 時間戳轉換到CST時間ntimestamp = int(time.time())ndatetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone(datetime.timedelta(hours=8)))n
原文鏈接 作者 @柳純
本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。
推薦閱讀:
※MySQL(0x1)
※黑客自學者工具集成系統推薦與選擇自己喜歡的集成系統
※零基礎入行圖像演算法工程師需要學習哪些課程?
※如何看待谷歌deeplearning團隊新開發的DNC(可微分神經計算機)?
※如何實現Pixhawk與matlab捷聯慣導?