谈谈你最熟悉的System.DateTime[上篇]
最近一直在负责公司内部框架的升级工作,今天对一个小问题进行了重新思考——时间的处理。具体来说,是如何有效地进行时间的处理以提供对跨时区的支持。对于一个分布式的应用来说,倘若客户端和服务端部署与不同的地区,在对时间进行处理的时候,就需要考虑时区的问题。以我们现在的一个项目为例,这是一个为澳大利亚某机构开发的一个基于Smart Client应用(Windows Form客户端),服务器部署于墨尔本,应用的最终用户可能需要跨越不同的州。澳洲地广人稀,不同的州也有可能会跨越不同的时区。假设数据库并不支持对时区的区分,服务端需要对针对客户端所在的时区对时间进行相应的处理。不过,对该问题解决方案的介绍我会放在后续的文章中,在这里我们先来介绍一些基础性的内容——谈谈我们熟悉的System.DateTime类型。 一、你是否知道System.DateTimeKind?System.DateTime类型,我们再熟悉不过。顺便说一下,这个类型不是class,而是一个struct,换言之它是值类型,而不是引用类型。DateTime处理包含我们熟悉的年、月、日、时、分、秒和毫秒等基本属性之外,还具有一个重要的表示时间类型(Kind)的属性:Kind。该属性的类型为System.DateTimeKind枚举。DateTimeKind定义如下,它具有三个枚举值:Unspecified、Utc和Local。后两个分别表示UTC(格林威治时间)和本地时间。Unspecified顾名思义,就是尚未指定具体类型,这是默认值。 1: [Serializable,ComVisible(true)] 3: { 5: Utc, 7: } 在DateTime类型中,表示时间类型的Kind属性是只读的,只能在构造函数中指定。相关构造函数和Kind属性的定义如下面的代码片断所示: struct DateTime 5: public DateTimeKind Kind { get; }
7: public DateTime(int year,int month,1)">int day,1)">int hour,1)">int minute,1)">int second,DateTimeKind kind); 9: 10: }
虽然,Kind属性是只读的,但是我们还用另外一中设定Kind的方式,那就是调用DateTime的静态方法的SpecifyKind。该方法不会真正去修改一个现有DateTime对象的Kind属性,而是会重新创建一个新的DateTime对象。方法返回的对象具有和指定时间相同的基本属性(年、月、日、时、分、秒和毫秒),该DateTime对象具有你指定的DateTimeKind值。 2: {
static DateTime SpecifyKind(DateTime value,1)" id="lnum5"> 5: }
二、几个常用DateTime对象的DateTimeKind处理直接通过构造函数构建DateTime对象之外,我们还经常用到DateTime的几个静态只读属性去获取一些特殊的时间,比如Now、UtcNow、MinValue和MaxValue等,那么这些DateTime对象的DateTimeKind又是什么呢?
上面列表对几个常用DateTime对象Kind属性的描述可以通过下面的程序来证实: 2: Console.WriteLine("endOfTheWorld.Kind = {0}",endOfTheWorld.Kind);
5: Console.WriteLine( 6: Console.WriteLine("DateTime.Now.Kind = {0}",DateTime.Now.Kind); 8: Console.WriteLine("DateTime.MinValue.Kind = {0}",DateTime.MinValue.Kind);
1: endOfTheWorld.Kind = Unspecified 3: endOfTheWorld.Kind = Unspecified 5: DateTime.UtcNow.Kind = Utc 7: DateTime.MaxValue.Kind = Unspecified 三、DateTime的对等性问题接下来,我们来谈谈另外一个比较有意思的问题——两个DateTime对象对等性。在这之前,我首先提出这样一个问题:“如果两个DateTime对象相等,是否意味着它们表示同一个时间点?”我想有人会认为是。但是答案是“不一定”,我们可以举一个反例。在下面的程序中,我创建了三个DateTime对象,年、月、日、时、分、秒均是相同的,但Kind分分别指定为DateTimeKind.Local、DateTimeKind.Unspecified和DateTimeKind.Utc。 2: DateTime endOfTheWorld2 = 3: DateTime endOfTheWorld3 = 4:?
"endOfTheWorld2 == endOfTheWorld3 = {0}",endOfTheWorld2 == endOfTheWorld3); 由于我们处于东8区,基于DateTimeKind.Local的endOfTheWorld1和基于DateTimeKind.Utc的endOfTheWorld3,不可能表示的是同一个时刻。但是从下面的输出结果来看,它们却是“相等的”,不但如此,Kind为Unspecified的endOfTheWorld2也和这两个时间对象相等。 2: endOfTheWorld2 == endOfTheWorld3 = True
由此可见,DateTimeKind对等性判断和DateTimeKind无关,那么在内部是如何进行判断的呢?要回答这个问题,这就要谈谈DateTime另外一个重要的属性——Ticks了。该属性定义如下,是DateTime的只读属性,类型为长整型,表示该DateTime对象通过日期和时间体现出来的计时周期数。每个计时周期表示一百纳秒,即一千万分之一秒。1 毫秒内有 10,000 个计时周期。此属性的值表示自公元元年( 0001 年) 1 月 1 日午夜 12:00:00(表示 DateTime.MinValue)以来经过的以100 纳秒为间隔的间隔数。 5: }
注意,这里的基准时间0001 年 1 月 1 日午夜 12:00:00,并没有说是一定是UTC时间,所以Ticks和DateTimeKind无关,这里通过下面的实例看出来: "endOfTheWorld2.Ticks = {0}",endOfTheWorld2.Ticks);
1: endOfTheWorld1.Ticks = 634917312000000000 3: endOfTheWorld3.Ticks = 634917312000000000 我们经常说的UTC时间和本地时间之间的相互转化,实际上指的就是将一个具有某种DateTimeKind的DateTime对象转化成具有另外一种DateTimeKind的DateTime对象,并且确保两个DateTime对象对象表示相同的时间点。关于时间转换的实现,我们有很多不同的选择。 四、通过DateTime类型的ToLocalTime和ToUniversalTime方法实现UTC和Local的转换对基于三种不同DateTimeKind的DateTime对象之间的转化,最方便的就是直接采用DateTime类型的两个对应的方法:ToLocalTime和ToUniversalTime,这两个方法的定义如下。 public DateTime ToUniversalTime();
"endOfTheWorld1.ToLocalTime() = {0}",endOfTheWorld1.ToLocalTime()); "endOfTheWorld3.ToLocalTime() = {0}n",endOfTheWorld3.ToLocalTime()); "endOfTheWorld1.ToUniversalTime() = {0}",endOfTheWorld1.ToUniversalTime()); 11: Console.WriteLine("endOfTheWorld3.ToUniversalTime() = {0}",endOfTheWorld3.ToUniversalTime());
对于DataTimeKind为Utc和Local之间的转化,没有什么可以说得,就是一个基于时差的换算而已。大家容易忽视的是DataTimeKind.Unspecifed时间分别向其他两种DateTimeKind时间的转换问题。从下面的输出我们可以看出,当DateTimeKind.Unspecifed时间向DateTimeKind.Local转换的时候,实际上是当成DateTimeKind.Utc时间;而向DateTimeKind.Utc转换的时候,则当成是DateTimeKind.Local。顺便补充一下:不论被转换的时间属于怎么的DateTimeKind,调用ToLocalTime和ToUniversalTime方法的返回的时间的Kind属性总是DateTimeKind.Local和DateTimeKind.Utc,两者之间的转换并不只是年月日和时分秒的改变。 ? 2: endOfTheWorld2.ToLocalTime() = 12/21/2012 8:00:00 AM
5: endOfTheWorld1.ToUniversalTime() = 12/21/2012 4:00:00 PM 7: endOfTheWorld3.ToUniversalTime() = 12/21/2012 12:00:00 AM ?五、通过TimeZoneInfo实现Utc和Local的转换 上面提供的方式虽然简单,但是功能上确有局限,因为转换的过程是基于本机当前的时区。这解决不了我在开篇介绍的应用场景:服务端根据访问者所在的时区(而不是本机的时区)进行时间的转换。换句话说,我们需要能够基于任意时区的时间转换方式,这就可以通过System.TimeZoneInfo。 TimeZoneInfo实际上对原来System.TimeZone类型的一个改进。它是一个可序列化的类型(这一点在分布式场景中进行基于时区的时间处理实现非常重要),表示具体某个时区的信息。它提供了一系列静态方法供我们对某个DateTime对象进行基于指定TimeZoneInfo的时间转换,在这我们介绍我们常用的2个:ConvertTimeFromUtc和ConvertTimeToUtc。前者将一个DateTimeKind.Utc或者Unspecified的DateTime时间转换成基于指定时区的DateTimeKind.Local时间;后者则将一个基于指定时区的DateTimeKind.Local或者DateTimeKind.Unspecified时间象转化成一DateTimeKind.Utc时间。此外,TimeZoneInfo还提供了两个静态属性Local和Utc表示本地时区和格林威治时区。 static DateTime ConvertTimeFromUtc(DateTime dateTime,TimeZoneInfo destinationTimeZone);
7:? static TimeZoneInfo Utc { get; } "TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2,TimeZoneInfo.Local) = {0}",1)" id="lnum6"> 6: TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2,TimeZoneInfo.Local)); "TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld1,1)" id="lnum11"> 11: TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld1,TimeZoneInfo.Local)); 1: TimeZoneInfo.ConvertTimeFromUtc(endOfTheWorld2,TimeZoneInfo.Local) = 12/22/2012 8:00:00 AM 3:? 5: TimeZoneInfo.ConvertTimeToUtc(endOfTheWorld2,TimeZoneInfo.Local) = 12/21/2012 4:00:00 PM ? Parameter name: sourceTimeZone”。 [相关阅读]
[1] 谈谈你最熟悉的System.DateTime[上篇]
[2] 谈谈你最熟悉的System.DateTime[下篇]
[3] 如何解决分布式系统中的跨时区问题[原理篇]
[4] 如何解决分布式系统中的跨时区问题[实例篇]
作者:Artech
出处:http://artech.cnblogs.com 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- asp.net-mvc – 在MVC命令,优先级和功能问题中授权属性
- asp.net – Azure上的联合身份验证
- asp.net-mvc – 当ASP.NET 5(vNext)无法重定向绑定时,我该怎
- ASP.NET MVC模型绑定 – JSON属性和C#模型属性的不同名称
- AspNet MVC中各种上下文理解
- asp.net-mvc – 如何下载Razor View引擎
- ASP.NET MVC中的单元测试比Web窗体更好?
- asp.net-mvc-3 – 具有MVC3的多用户应用程序,ASP.NET成员资
- entity-framework – 实体框架Add-Migration失败,出现“找不
- asp.net – MVC3的远程模型验证操作中的参数名称
- asp.net-mvc-3 – 在MVC 3上启用黄屏死机
- asp.net-mvc – 如何在MVC 4.0 Razor中进行授权
- asp.net-mvc – 使用哪个:“AcceptGet,AcceptPo
- asp.net – MVC 4导出到CSV – 另存为对话框在Ch
- 一个ASP.Net页面中的多个reCAPTCHA
- asp.net-mvc – ASP MVC – 多对多的关系
- One to One 的数据库模型设计与NHibernate配置
- asp.net-mvc – 如何在ASP.NET MVC路由中使用带有
- 检测到在集成的托管管道模式下不适用的ASP.NET设
- IIS错误 – 无法运行两个不同版本的ASP.NET