C#中的DateTime在逻辑上有个很是严重的缺陷:html
> var d = DateTime.Now; > var d2 = d.ToUniversalTime(); > d == d2 false > d.Equals(d2); false
在C#交互模式中输入以上代码,能够发现尽管一个是本地时间(d),一个是UTC时间(d2),只是时区不同,但在这个世界上,应该属于同一个时刻。然而两个时间却不相等。。。mongodb
缘由在于DateTime不存储时区,或者说,只存储了一个模糊的关于时区的字段Kind,它是DateTimeKind枚举类型的,有三种取值:Utc/Local/Unspecified,当取值为Unspecified时,则会有歧义。服务器
但我仍是要吐槽,若是d.Kind或d2.Kind中任意一个是Unspecified,那么d != d2我能够理解。可是上面的d.Kind是Local,d2.Kind是Utc,若是按照DateTime不存储时区的逻辑,那么这两个统一转换Utc或者Local时,那么它们应该相等,事实上也是如此:网络
> d == d2.ToLocalTime() true
若是把d的本地时间t1当作9,本地时间所处时区z1当作+8,相应的UTC时间t0当作1,UTC时间所处时区z0当作0,对它们作规范化处理:函数
那么 d = t1-z1 = 9 - 8 = 1, d2 = t0 - z0 = 1 - 0 = 1 。post
然而 d != d2。这才是它怪异的地方。this
以东八区为例,在C#交互模式中输入如下代码:spa
> var d3 = new DateTime(2018, 1, 1); > d3 [2018/1/1 0:00:00] > d3.ToLocalTime() [2018/1/1 8:00:00] > d3.ToUniversalTime() [2017/12/31 16:00:00]
能够发现,一个简单的构造函数,开发者心中默认通常都是本地时间,然而它却容许直接建立出一个既非本地时间、也非UTC时间的怪物。code
当d3转成本地时间时,会把d3做为UTC时间来加8小时。htm
当d3转成UTC时间时,却会把d3做为本地时间来减8小时。
那么d3究竟是本地时间仍是UTC时间呢?没人清楚,除非它存在于一个很是小的局部做用域中,而且生命周期极短,这时候咱们也许能够假设它为本地时间。
然而这个本地时间也依赖于它的运行环境,若是是有几台时区不一致的计算机,阉割了时区信息的DateTime转成字符串在网络中传输到另外一个时区(好比隔壁的十一区)的另外一台服务器中,解析出来后,所谓的东八区本地时间8点,到了日本,变成了既非本地时间、也非UTC时间的怪物。
DateTime在官方文档中已经不推荐使用,而是推荐使用它的代替品DateTimeOffset,后者保存时区信息。
在交互模式中验证一下:
> var dto = DateTimeOffset.Now; > var dto2 = dto.ToUniversalTime(); > dto == dto2 true
能够发现,DateTimeoffset判断两个时间是否等价的标准,是以世界时间轴的时刻来判断的,与时区无关,甚至能够与UTC时间无关。只要它们都在同一个时间体系里、能互相变换便可。
在实际项目中,建议你们:
- 若是有使用DateTime的,统一换成DateTimeOffset。
- 若是有用到32比特的UNIX时间戳的,统一换成64比特的long来存储UtcTicks。
即便项目自己不跨时区,仍然有可能遇到时区问题,好比若是使用了mongodb的,mongodb存储的时候都是统一存成UTC时间的(好像是,忘了),并且通常来讲会带有时区信息。可是有一种状况比较糟糕,若是你的DateTime的Kind是Unspecified的,隐含的时区的信息就会丢失。再取出来以后,就会有8小时的时差。有一些第三方的或者本身公司的类库之类的,若是没有处理好这个问题,也有潜在的时区丢失问题。UNIX时间戳存成Utc Ticks也有好处,不管是精度仍是时间跨度,都远超UNIX时间戳。只须要64比特,便可得到下至100纳秒的精度,上超万年的时间跨度,一劳永逸,不管是转回UNIX时间戳仍是JS时间戳,都能胜任。空间代价也很是小,除非你的存储空间真的是硬伤。。