Unix 時間戳表示的是從世界標準時間(UTC,Coordinated Universal Time)的 1970 年 1 月 1 日 0 時 0 分 0 秒開始的偏移量。mysql
全球共有 24 個時區,分爲東西各 12 時區。全部地區在使用同一個時間戳的基礎上,根據當地時區調整時間的表示。git
如今比較常見的日期和時間的表示標準是 ISO8601,或者在其基礎上更加標準化的 RFC3339。程序員
舉個例子,北京時間 2021 年 1 月 28 日 0 時 0 分 0 秒用 RFC3339 表示爲:2021-01-28T00:00:00+08:00
。github
+08:00
表示東 8 區,2021-01-28T00:00:00
表示這個時區的人所看到的時間。加號若是改成減號,則表示西時區。golang
比較特殊的是 UTC 時區,能夠表示爲 2006-01-02T15:04:05+00:00
,但一般簡化爲 2006-01-02T15:04:05Z
。sql
在使用的時候,應當根據時區調整時間的展現。例如 1611792000
能夠表示爲 2021-01-28T00:00:00Z
或者 2021-01-28T08:00:00+08:00
。mongodb
不一樣的數據來源極可能使用不一樣的時間表示方法。根據是否可讀分紅兩類:數據庫
數字類型就不詳細說明。函數
字符串又根據是否有時區分爲兩類:工具
2021-01-28 00:00:00
沒有包含時區信息2021-01-28T08:00:00+08:00
包含了時區信息在解析沒有包含時區信息的字符串時,一般要由程序員指定時區,不然默認爲 UTC 時區。若是附帶時區,那就能夠不用另外指定。
例如 Golang 的時間庫,就有兩個方法:
Parse(layout, value string)
ParseInLocation(layout, value string, loc *Location)
在解析的時候,會先根據年月日時分秒計算出一個整數。接着看 value 是否包含時區信息。
若是 value 包含時區,那麼就會給解析後的整數加一個偏移量,這個偏移量由時區與 UTC 時區之間的位置關係決定。
若是 value 不包含時區信息,Parse 會將其設置爲 UTC 時區,ParseInLocation 會根據傳入的時區調整解析出來的整數,並將時區設置爲傳入的時區。
和解析時同樣,保存日期和時間的方式有多種。
例如 Golang 的 Time :
type Time struct { wall uint64 ext int64 loc *Location // 位置。用於調整時間的表示。 }
Golang 存儲的不是 Unix 時間戳,可是會根據狀況將其轉換爲時間戳。對於 loc 的修改不會對 Unix 時間戳產生影響,只會影響時間的展現形式。
MongoDB 使用的 bson.Date 使用 int64 存儲從 1970 年 1 月 1 日以來的毫秒數。
MySQL 使用 DATETIME 類型存儲不包含時區的年月日時分秒,查詢時以 YYYY-MM-DD HH:MM:SS
的形式展現。也能夠用四個字節的 TIMESTAMP 類型存儲 Unix 時間戳。
之前在保存時間戳的時候,一般都使用四個字節,也就是 32 位的有符號整數存儲。
把二進制的 01111111 11111111 11111111 11111111
轉化爲十進制後獲得 2147483647
,再轉化爲北京時間獲得 2038-01-19 11:14:07
。
這就表示 32 位整數最多隻能存儲到 2038 年的時間,所以被稱爲 「2038 年問題」。
比較新的一些項目會經過各類方式解決這個問題,一般是使用 64 位整數來存儲時間戳。但使用方式各有不一樣。
例如 Golang 使用了兩個 64 位整數來存儲。其中沒法符號整數 wall,第一位表示是否有單調時間。
MongoDB 則是使用 int64 存儲從 1970 年 1 月 1 日以來的 UTC 毫秒數。
MySQL 沒有解決 TIMESTAMP 類型的問題,它始終是四個字節。所以若是要解決這個問題,最好使用 DATETIME。可是 DATETIME 也有問題,它無法存儲時區。不過大多數應用都無需考慮時區問題,無需擔憂。
數據庫都默認使用 UTC。若是不加以處理,存儲到數據庫的時間就會展現爲與本地實際展現的時間不一致的形式。
例如 MongoDB 存儲的是從 1970 年 1 月 1 日以來的 UTC 毫秒數,像 Navicat 這種工具,會用 UTC 的形式展現時間。這樣其餘時區的人看起來就會不習慣。
而 MySQL 就更難處理了,DATETIME 不帶時區。
解決這個問題有三種思路:
0000-00-00 00:00:00
的狀況產生影響。db.createView("view_name","collection_name",[ { $addFields: { date: { $dateToString: { date: "$date", format: "%Y-%m-%dT%H:%M:%S+08:00", timezone: "+08:00" } } } } ]);addFields 會覆蓋同名的字段。上面的語句會將原先的 date 字段的值以新的格式展現。
MongoDB 的官方庫在存儲的時候,會使用 UTC 的時間戳。但在查詢的時候,會判斷是否設置了使用本地時間展現。若是沒有設置按本地時間展現,則會將 Time 設置爲 UTC 時區。
if !tc.UseLocalTimeZone { timeVal = timeVal.UTC() }
如何事先配置好?
builder := bsoncodec.NewRegistryBuilder() // 註冊默認的編碼和解碼器 bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(builder) bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(builder) // 註冊時間解碼器 tTime := reflect.TypeOf(time.Time{}) tCodec := bsoncodec.NewTimeCodec(bsonoptions.TimeCodec().SetUseLocalTimeZone(true)) registry := builder.RegisterTypeDecoder(tTime, tCodec).Build() client, err := mongo.NewClient(options.Client().ApplyURI(uri), options.Client().SetRegistry(registry))
MongoDB 使用的 bson.Date 使用 int64 存儲 1970 年 1 月 1 日以來的毫秒數。從 MongoDB 查出來的也是這個數據。
若是 decode 的時候指定了存儲結果的結構體的時間字段的類型,如 time.Time。則會將 int64 轉化爲 time.Time。若是不指定,則返回 int64。
可見 MongoDB 官方庫使用的是第二種思路。
https://github.com/go-sql-driver/mysql#loc
須要在鏈接的時候設置。dsn 裏面帶上 loc 參數。
在解析查詢結果中的 DateTime 類型的時候,會將字節轉換爲字符串形式。這個字符串形式最長的狀況是 0000-00-00 00:00:00.0000000
。驅動會根據實際長度解析。
MySQL 驅動的作法是,若是 dsn 有帶 loc 參數,那麼在解析年月日時分秒和毫秒後,以這些數據和時區建立 time.Time。即 time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc)
。
而在 insert 操做時,會將 time.Time 設置爲指定的時區。v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
,這裏的 v 就是咱們 Insert 的類型爲 time.Time 的值。
可見 MySQL 驅動使用的是第三種思路。