• DateTimeOffset和DateTime之间有什么关系?
  • 发布于 2个月前
  • 164 热度
    0 评论
  • 飛雲
  • 0 粉丝 39 篇博客
  •   
小编在日常开发中,用得最多的时间类型就是DateTime,直到一次偶然的邂逅,让小编遇见了DateTimeOffset。当时小编也是一脸迷茫,因为在小编的C#编程字典里就没出现过DateTimeOffset的字样,实属惭愧。于是心中立马产生疑惑:DateTimeOffset是用来干嘛的?它和DateTime之间又是什么关系?带着种种疑问和不解,小编今天就带你一起一窥究竟!

开场前,先了解下GMT、UTC、TimeZone这三个时间概念
GMT:Greenwich Mean Time,格林威治平时(也称格林威治时间)。它规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。1972年之前,格林威治时间(GMT)一直是世界时间的标准。1972年之后,GMT 不再是一个时间标准了,取而代之的是UTC。

UTC:Coodinated Universal Time,协调世界时,又称世界统一时间、世界标准时间、国际协调时间。UTC 是现在全球通用的时间标准,全球各地都同意将各自的时间进行同步协调。

TimeZone: 时间区域,简称时区。按照规定从格林威治本初子午线起,经度每向东或者向西间隔15°,就划分一个时区,在这个区域内,大家使用同样的标准时间。全球共分为24个标准时区,相邻时区的时间相差一个小时。

举个例子:北京位于东八区,和伦敦时间(UTC时间)相差+8个小时。当小编每天早上8点出门上班的时候 ,大洋彼岸的英国人民正处于睡梦中,因为那儿正值午夜零点,这就是我们常说的时差。顺带提一句,虽然我国地域辽阔横跨5个时区,但统一采用北京时间作为标准时间,目的就是为了方便时间管理。

首先登场的是:DateTime
DateTime表示的是一个日期和时间值。这里我们先思考一个问题:假设有个陌生人电话告诉你现在时间是某月某日某时某分,但你又不知道对方在哪里,那么这个时间对你来说是不清晰的,它可能是当地时间(东京时间、悉尼时间...),也有可能是UTC时间。因此DateTime有个只读属性Kind,它是一个DateTimeKind枚举,用来表示时间类型。定义如下:
public enum DateTimeKind
{
    // 堆代码 duidaima.com
    // 未指明是UTC时间,还是当地时间
    Unspecified = 0,
    // 表示UTC时间
    Utc = 1,
    // 表示本地时间
    Local = 2
 }
 通过DateTime构造函数,可以看出Kind属性值是由构造函数传入的。下面来看DateTime的几种常见用法:
DateTime dt1 = new DateTime(2023, 7, 27, 8, 40, 25);
Console.WriteLine($"DateTime:{dt1}, 时间类型:{dt1.Kind}, 转本地时间:{dt1.ToLocalTime()},转UTC时间:{dt1.ToUniversalTime()}");

DateTime dt2 = DateTime.Now;
Console.WriteLine($"DateTime:{dt2}, 时间类型:{dt2.Kind}, 转本地时间:{dt2.ToLocalTime()},转UTC时间:{dt2.ToUniversalTime()}");

DateTime dt3 = DateTime.UtcNow;
Console.WriteLine($"DateTime:{dt3}, 时间类型:{dt3.Kind}, 转本地时间:{dt3.ToLocalTime()},转UTC时间:{dt3.ToUniversalTime()}");

DateTime dt4 = new DateTime(2023, 7, 27, 8, 40, 25, DateTimeKind.Local);
Console.WriteLine($"DateTime:{dt4}, 时间类型:{dt4.Kind}, 转本地时间:{dt4.ToLocalTime()},转UTC时间:{dt4.ToUniversalTime()}");

DateTime dt5 = new DateTime(2023, 7, 27, 8, 40, 25, DateTimeKind.Utc);
Console.WriteLine($"DateTime:{dt5}, 时间类型:{dt5.Kind}, 转本地时间:{dt5.ToLocalTime()},转UTC时间:{dt5.ToUniversalTime()}");
 输出结果:
DateTime: 2023 / 7 / 27 8:40:25, 时间类型: Unspecified, 转本地时间: 2023 / 7 / 27 16:40:25,转UTC时间: 2023 / 7 / 27 0:40:25
DateTime: 2023 / 7 / 27 14:04:00, 时间类型: Local, 转本地时间: 2023 / 7 / 27 14:04:00,转UTC时间: 2023 / 7 / 27 6:04:00
DateTime: 2023 / 7 / 27 6:04:00, 时间类型: Utc, 转本地时间: 2023 / 7 / 27 14:04:00,转UTC时间: 2023 / 7 / 27 6:04:00
DateTime: 2023 / 7 / 27 8:40:25, 时间类型: Local, 转本地时间: 2023 / 7 / 27 8:40:25,转UTC时间: 2023 / 7 / 27 0:40:25
DateTime: 2023 / 7 / 27 8:40:25, 时间类型: Utc, 转本地时间: 2023 / 7 / 27 16:40:25,转UTC时间: 2023 / 7 / 27 8:40:25
由此可见,DateTime是通过Kind属性来区分是本地时间还是UTC时间,同时本地时间和UTC时间之间可以相互转换,转换条件就是根据你电脑当前所设置的时区。不足之处就是DateTime提供了非常有限的时区信息,如果不看Kind属性,你无法判断它是来自本地时间还是UTC时间,因此在跨时区系统间的数据移植性较差(除非是指定是UTC时间)。

接着出场的就是:DateTimeOffset
DateTimeOffset表示一个时间点,通常以相对于协调世界时 (UTC) 的日期和时间来表示。它既包含了DateTime属性,又包含了一个TimeSpan类型的Offset属性(表示相对于UTC时间的偏移量).通过DateTimeOffset构造函数,可以看出Offset属性值是由构造函数传入的。下面来看DateTimeOffset的几种常见用法:
DateTimeOffset dto1 = new DateTimeOffset(2023, 7, 27, 8, 40, 25, TimeSpan.Zero);
Console.WriteLine($"DateTimeOffset:{dto1}, 时间偏移:{dto1.Offset}, 转本地时间:{dto1.ToLocalTime()},转UTC时间:{dto1.ToUniversalTime()}");

DateTimeOffset dto2 = DateTimeOffset.Now;
Console.WriteLine($"DateTimeOffset:{dto2}, 时间偏移:{dto2.Offset}, 转本地时间:{dto2.ToLocalTime()},转UTC时间:{dto2.ToUniversalTime()}");

DateTimeOffset dto3 = DateTimeOffset.UtcNow;
Console.WriteLine($"DateTimeOffset:{dto3}, 时间偏移:{dto3.Offset}, 转本地时间:{dto3.ToLocalTime()},转UTC时间:{dto3.ToUniversalTime()}");

DateTimeOffset dto4 = new DateTimeOffset(2023, 7, 27, 8, 40, 25, new TimeSpan(8, 0, 0));
Console.WriteLine($"DateTimeOffset:{dto4}, 时间偏移:{dto4.Offset}, 转本地时间:{dto4.ToLocalTime()},转UTC时间:{dto4.ToUniversalTime()}");

DateTimeOffset dto5 = new DateTimeOffset(new DateTime(2023, 7, 27, 8, 40, 25), new TimeSpan(-8, 0, 0));
Console.WriteLine($"DateTimeOffset:{dto5}, 时间偏移:{dto5.Offset}, 转本地时间:{dto5.ToLocalTime()},转UTC时间:{dto5.ToUniversalTime()}");
 输出结果:
DateTimeOffset:2023/7/27 8:40:25 +00:00, 偏移量:00:00:00, 转本地时间:2023/7/27 16:40:25 +08:00,转UTC时间:2023/7/27 8:40:25 +00:00
DateTimeOffset:2023/7/27 15:46:14 +08:00, 偏移量:08:00:00, 转本地时间:2023/7/27 15:46:14 +08:00,转UTC时间:2023/7/27 7:46:14 +00:00
DateTimeOffset:2023/7/27 7:46:14 +00:00, 偏移量:00:00:00, 转本地时间:2023/7/27 15:46:14 +08:00,转UTC时间:2023/7/27 7:46:14 +00:00
DateTimeOffset:2023/7/27 8:40:25 +08:00, 偏移量:08:00:00, 转本地时间:2023/7/27 8:40:25 +08:00,转UTC时间:2023/7/27 0:40:25 +00:00
DateTimeOffset:2023/7/27 8:40:25 -08:00, 偏移量:-08:00:00, 转本地时间:2023/7/28 0:40:25 +08:00,转UTC时间:2023/7/27 16:40:25 +00:00
可以看出,DateTimeOffset除了包含日期和时间之外,还包含相对于UTC时间的偏移量,因此不再需要Kind属性,就能区分出是UTC时间(如:dto1和dto3)还是本地时间(如:dto2、dto4和dto5),同时还能反馈时间所在的时区(如:dto2和dto4是东八区,dto5是西八区)。

最后得出结论:
DateTimeOffset和DateTime之间的确存在血缘关系,DateTimeOffset相当于是DateTime的一个包装,它除了具有DateTime的所有功能之外,还包含时区信息。如果你的系统不需要考虑时区问题或者只用UTC时间,那么DateTime就够用了。如果你的系统是全球化的,客户来自不同时区的不同国家,那么服务端就需要用DateTimeOffset来保存时间,这样客户端很容易将带有时区的时间字符串(如:"2023/7/27 8:40:25 +08:00")转成本地时间来显示。总而言之,小编认为DateTimeOffset功能比DateTime更强大、也更加灵活。微软官方建议用DateTimeOffset作为应用开发的默认时间类型。
用户评论