C#中DateTime的缺陷与代替品DateTimeOffset

C#中的DateTime在逻辑上有个非常严重的缺陷:

> var d = DateTime.Now;
> var d2 = d.ToUniversalTime();
> d == d2
false
> d.Equals(d2);
false

在C#交互模式中输入以上代码,可以发现尽管一个是本地时间(d),一个是UTC时间(d2),只是时区不一样,但在这个世界上,应该属于同一个时刻。然而两个时间却不相等。。。

原因在于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 = 8 - 8 = 0, d2 = t0 - z0 = 0 - 0 = 0 。

然而 d != d2。这才是它怪异的地方。

以东八区为例,在C#交互模式中输入以下代码:

> 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时间的怪物。

当d3转成本地时间时,会把d3作为UTC时间来加8小时。

当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时间戳,都能胜任。空间代价也非常小,除非你的存储空间真的是硬伤。。

原文地址:https://www.cnblogs.com/zlmdy/p/8560396.html

时间: 03-13

C#中DateTime的缺陷与代替品DateTimeOffset的相关文章

easyui datagrid中datetime字段的显示和增删改查问题

datagrid中datetime字段的异常显示: 使用过easyui datagrid的应该都知道,如果数据库中的字段是datetime类型,绑定在datagrid显式的时候会不正常显示,一般需要借助于formatter来格式化时间格式 { title: '活动开始时间', field: 'BeginTime', width: 300, editor: { type: 'datetimebox', options: { required: true }, formatter: function

webservice中DateTime类型参数的传入问题

This step-by-step article describes how to format DateTime and Date values in the XML that is extracted from an ADO.NETDataSet object. In ADO.NET, the DateTime and Date values of DataTable columns are written in the XSD DateTime and Dateformats when

C#中DateTime格式转换

在C#中DateTime是一个包含日期.时间的类型,此类型通过ToString()转换为字符串时,可根据传入给Tostring()的参数转换为多种字符串格式. 常用的函数: DateTime.Now.ToString("yyyyMMdd")显示为:20160501:                     DateTime.Now.ToString("yyyyMMddHHmmss")显示为:20160501210635: 目录 1. 分类 2. 制式类型 3. 自定

python中datetime模块

Python提供了多个内置模块用于操作日期时间,像calendar,time,datetime.time模块我在之前的文章已经有所介绍,它提供 的接口与C标准库time.h基本一致.相比于time模块,datetime模块的接口则更直观.更容易调用.今天就来讲讲datetime模块. datetime模块定义了两个常量:datetime.MINYEAR和datetime.MAXYEAR,分别表示datetime所能表示的最 小.最大年份.其中,MINYEAR = 1,MAXYEAR = 9999

解决SQL Server 2005数据库中datetime时间字段在前端显示时分秒的问题

SQL Server 2005中时间类型datetime的格式是"年月日时分秒",直接读出来该字段,为了不让它在前端显示"时分秒"若是显示在dataGridView中,可以修改控件的某一列格式,如: dataGridView1.Columns[10].DefaultCellStyle.Format = "yyyy-MM-dd"; 但是要在listview控件中的话,就有点困难了,貌似没有类似的属性,这样的话,考虑从数据源入手. 1.获取当前年月日

SQL Server中DateTime与DateTime2的区别

DateTime字段类型对应的时间格式是yyyy-MM-dd HH:mm:ss.fff,3个f,精确到1毫秒(ms),示例2014-12-0317:06:15.433. DateTime2字段类型对应的时间格式是yyyy-MM-dd HH:mm:ss.fffffff,7个f,精确到0.1微秒(μs),示例2014-12-0317:23:19.2880929. 如果用SQL的日期函数进行赋值,DateTime字段类型要用GETDATE(),DateTime2字段类型要用SYSDATETIME().

C# 中DateTime的各种用法

获得当前系统时间: DateTime dt = DateTime.Now; Environment.TickCount可以得到“系统启动到现在”的毫秒值 DateTime now = DateTime.Now; Console.WriteLine(now.ToString("yyyy-MM-dd")); //按yyyy-MM-dd格式输出s Console.WriteLine(dt.ToString()); // 26/11/2009 AM 11:21:30 Console.Write

php中DateTime、diff

手册地址:http://php.net/manual/en/dateinterval.format.php 1 $january = new DateTime('2010-01-01'); 2 $february = new DateTime('2010-02-01'); 3 $interval = $january->diff($february); 4 5 // %a will output the total number of days. 6 echo $interval->forma

python 中datetime 和 string 转换

dt = datetime.datetime.strptime(string_date, fmt) fmt 的格式说明如下: https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior

js转换数据库中DateTime字段类型

在程序中,从数据库中读取到的日期时间类型数据一般是这种格式:"/Date(1355109408000+0800)/" 要经过js函数处理变为格式:'2012-12-10 11:05:21' 用此js函数即可搞定: function timeFormatter(value) { var da = new Date(parseInt(value.replace("/Date(", "").replace(")/" , "