35

你需要了解的有关.NET日期时间的必要信息 - Julian_酱

 4 years ago
source link: https://www.cnblogs.com/JulianHuang/p/10967289.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

你需要掌握的有关.NET DateTime类型的知识点和坑位 都在这里

    DateTime是一个时常让人复杂困惑的数据类型,开发人员编写【将日期从Web服务器返回到浏览器】类似代码有时结果与预期不符。

ASP.NET MVC 5和 Web API 2/ASP.NETCore 以不同方式序列化日期,这可能会给在一个Web应用程序中同时使用这两个序列化的开发人员带来更多混淆。

本文会尽量覆盖 ASP.NET / ASP.NETCore 中与 Date/Time有关的歧义、参数绑定、序列化相关的知识点和坑位。  

DateTime

DateTime最大问题在于有歧义:地球上各时区形成了一个万花筒,某一时刻在各个时区的本地定义值是不同的;同理给出某时间值,万花筒上的各个时区使用这个值标识了 不同的时刻。

正因为这个现状,在Internet以及无线电通信时,需要定义统一的时间标准。

UTC时间标准

协调世界时(Coordinated Universal Time,缩写为UTC)是全世界定义时钟和时间的主要时间标准。 以原子时秒长为基础在时刻上尽量接近世界时的一种计量系统,

格林威治GMT时间 是19 世纪中叶英帝国基准时间,他以格林威治天文台的0度经线,将世界分为24个时区。 为了方便在不需要精确到秒的情况下,通常将GMT和UTC视为等同。

  UTC能精确代表单一时间点,UTC时刻对于住在加利福尼亚和中国的人来说都是一样的。世界上所有时区的本地时间 都以相对于UTC正负偏移表示(以英国格林威治时区为0时区)

  如果把以上北京时间2019/06/04 09:45:29转化为UTC时间,可以使用以下公式: UTC + 时区差 = 当地时间。

电邮中若有

Sun,04,June 2019 9:45:29 +0800

说明信件发送第时间是2019年6月04日,星期日,上午9点45分29秒,该地区领先UTC8小时 (+800,就是东八区时间)。

发这封邮件的时间,北京时间是9点45,伦敦时间凌晨1点45 ......

中国大陆采用ISO 8601-1988的《数据元和交换格式信息交换日期和时间表示法》(GB/T 7408-1994)称之为国际协调时间,代替原来的GB/T 7408-1994;中国台湾采用CNS 7648的《资料元及交换格式–资讯交换–日期及时间的表示法》,称之为世界统一时间。

ISO 8601时间显示格式

  Web上著名且大规模采用的是ISO-8601 标准。https://www.cl.cam.ac.uk/~mgk25/iso-time.html

当没有更多信息的时候,只包含Date/Time 的写法被假定为当地时间,要指示该时间是UTC时间,可在Datetime值后面加上字母 z

在Datetime值后面增加 +hh:mm、 +hhmm, -hh 可指示该时间值相对于UTC时间的偏移

// Date: 2016-10-12 10:18:42 UTC time
ISO 8601: 2016-10-11T10:18:42z          == 2016-10-11T10:18:42+00:00

// Date: 2016-10-12 10:18:42 Pacific time 对应的UTC时间是 2016-10-11T16:18:42
 ISO 8601: 2016-10-11T10:18:42-06:00    # 本地时间上午10点左右, 英国伦敦时间下午4点左右: 该本地时间相对伦敦时间提前 6小时。

 也是就说我们现在常见的时间格式   2026-10-11T10:18:42+08:00 前面是本地北京时间,后面标记了时区。 

.Net中关于Date/Time的实现

  .Net 4.0+ 提供的各种结构已经全方位支持 date, timezone, timezone之间的转化,足以解决开发者遇到的Date/Time相关的问题。

DateTime

  DateTime 定义了一个特殊的date/time, 内置的Kind属性提供了受限的时区信息

  ① DateTimeKind.UTC 指定了UTC的DateTime,明确定义了单一时间点

  ② DateTimeKind.Local 指示了本地时间,这个值在具有相同时区的其他系统依然能够定义一个时间点,但是在其他时区以外,这个DateTime值会有不同解释。

  ③ DateTimeKind.Unspecified 更没有兼容性,仅表示时间值

  我们关注 DateTime.ToUniversalTime() 方法的表现,当DateTime被设定为Unspecified时候, ToUniversalTime会首先假定该值是 Local

// 以下代码的执行环境是北京时间
var dt1 = new DateTime(2019, 6, 4, 9, 45, 29, DateTimeKind.Local);
var dt1_temp = dt1.ToUniversalTime();

var dt2 = new DateTime(2019, 6, 4, 9, 45, 29, DateTimeKind.Utc);
var dt2_temp = dt2.ToUniversalTime();

var dt3 = new DateTime(2019, 6, 4, 9, 45, 29, DateTimeKind.Unspecified);
var dt3_temp = dt3.ToUniversalTime();

var dt4 = new DateTime(2019, 6, 4, 9, 45, 29); // Unspecified DateTime
var dt4_temp = dt4.ToUniversalTime();
Console.WriteLine(dt1_temp.ToString());
Console.WriteLine(dt2_temp.ToString());
Console.WriteLine(dt3_temp.ToString());       // Unspecified DateTime在被应用ToUniversalTime 方法时会被假定是Local
Console.WriteLine(dt4_temp.ToString());
output: 
2019/6/4 1:45:29
2019/6/4 9:45:29
2019/6/4 1:45:29
2019/6/4 1:45:29

 when to use datetime?

  • 你只处理当地时间,你没有跨越时区的计算

  • 你只处理 UTC时间

  • 处理抽象日期/时间(使用 Unspecified):例如跨国公司的跨时区门店都在早上9点开业

DateTimeOffset

表示时间点,通常表示为一天中相对于UTC时间的日期/时间,该结构体自然带有相对于UTC时间的偏移信息

when to use DateTimeOffset?

  • 代码需要应对不同时区的时间值

  • 时区之间相互转化

  • 需要进行跨时区的计算

Date/Time序列化

  Web API2 和ASP.NET Core内置的JSON序列化器是Newtonsoft.Json,JSON.NET将日期时间序列化为ISO-8601格式:

Date value
serialized value
DateTime.Now (Pacific time)
2016-10-11T19:25:34.8346658-07:00
DateTime.UtcNow
2016-10-12T01:25:34.8346658Z
new DateTime(2016, 6, 6, 4, 5,5 )
2016-06-06T04:05:05
new DateTimeOffset(new DateTime(2016, 6, 6, 4, 5,5 ), new TimeSpan(-2, 0,0))
2016-06-06T04:05:05-02:00
  ASP.NET MVC 5内置的JSON Serializer 还是System.Web.Script.Serialization.JavaScriptSerializer, 该序列化器将date序列化为时间戳:“\/Date(ticks)\/”, ticks 表示从1970-1-1 00:00:00 UTC(Unix Epoch)经历的毫秒数,

这样的格式在客户端需要使用JavaScript做一些本地转化, 转化后的值无法体现时区。 

public ActionResult Contact()
{
       var data = new Test
       {
            Name = "hj",
            Time = DateTime.Now
        };
        return Json(data,JsonRequestBehavior.AllowGet);
 }

output:
{
    "Name": "hj",
    "Time": "/Date(1559634654877)/"
JavaScriptSerializer还有更多缺点,现在社区鼓励使用Json.Net 序列化器。

Date/Time字符串转换

  在开发中,常涉及Date/Time 参数绑定和字符串转换, 当中也有一些坑位需要规避。

一个时间日期字符串, 若没有相对于UTC的偏移信息,转换后的DateTime对象的DateTimeKind是Unspecified;

若指定了offset,转换后的DateTime对象的DateTimeKind是Local, 并且时间值被调整到机器的当地时间。

最近我们生产环境WebSite再迁移到k8s集群 (UTC时间)之后,就遇到这样的问题:

订单转储的预期是:北京时间2019-05-11--->2019-05-12

实际情况是在网站端转换为Unspecified, 而进一步ToUniversalTime()过滤的时候,该时间段又被假定是机器的当地时间, 也就是说查询时间段变成了: 伦敦时间2019-05-11--->2019-05-12

这样自然与预期不符。

解决思路: 添加偏移信息,告知明确的时间段: 2019-05-11 00:00:00+08:00 ----> 2019-05-12 00:00:00+08:00

     开发者每天都在使用的Date/Time有很多学问, 涉及Culture、Calendar、夏令时, 这里只是浅谈最常用的知识点和坑位。

文字+Visio制图均为原创,为方便与在移动端与读者互动,现已注册个人微信公众号,扫描下方二维码即可,欢迎大家关注,及时分享技术博文、职场心得、人生态度。
如果您觉得本文对您有帮助,请点一下左下角“推荐”按钮,您的“推荐”将是我最大的写作动力!另外您也可以选择关注我,可以很方便找到我!
    本文版权归作者和博客园共有,来源网址:http://www.cnblogs.com/julianhuang 欢迎各位转载,转载文章务必在文章页面显著位置给出作者和原文连接,否则保留追究法律责任的权利!
个人微信公众号

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK