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 驱动使用的是第三种思路。