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