互聯網上的日期和時間

最近參加公司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時間。

為什麼我們會踩坑

了解了本地時間和標準時間之後,很容易想到為什麼我們會在這裡出問題。

很自然的,開發者編寫代碼處理用戶輸入的時間類型數據,處理的都是本地時間的格式。而計算機系統處理邏輯,依賴標準時間。從用戶輸入的本地時間,轉化成系統能處理的標準時間,這個過程是系統隱式的自動轉換。當系統的時區設置和用戶所處的時區不一致的時候,時間經過一次輸入,處理之後再輸出,就有了差異。

為了避免在時間處理上出現問題,核心就是統一輸入輸出時間的轉化。因此解決的思路也是兩個:

  1. 強制用戶輸入的時間類型是標準的,使用時間戳/標準時間。如果你的系統是給全球用戶使用的,應該採用這種功能方法。這是最準確,最不容易出錯的。
  2. 系統的時區和用戶的時區嚴格一致。項目規模小,全部都是一個時區的用戶。採用這種方案,一旦用戶跨時區了,還是會存在問題。不是很推薦。

時區的名稱可以查維基百科,也可以查官方的資料庫 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捷聯慣導?

TAG:互联网 | 计算机技术 | Python |