System.DateTime 详解(续)

  在《System.DateTime 详解》一文中,我们从跨时区的角度剖析了我们熟悉的System.DateTime类型。如果你还是采用传统的ADO.NET编程方式,并使用DataSet作为数据实体,可能你会熟悉System.Data.DataSetDateTime这么一个类型。这个类型也是为实现跨时区场景下对时间处理而设计的,为了对前文的补充,这篇文章就来谈谈基于DataSet的时间处理问题。

  一、你是否关注过DataColumn的DateTimeMode属性

  在ADO.NET编程模型中,DataColumn代表DataTable的一个数据列,大家在熟悉不过了。不过,是否有人关注过一个名称为DateTimeMode属性,该属性在DataColumn中的定义如下:

1: public class DataColumn : MarshalByValueComponent 2: { 3: //Others... 4: public DataSetDateTime DateTimeMode { get; set; } 5: }

  从上面的代码我们可以看出,DateTimeMode属性的类型为DataSetDateTime,这实际上是一个枚举类型。下面给出了DataSetDateTime的定义,该枚举一共包含4个枚举值:Local、Utc、Unspecified和UnspecifiedLocal。

1: public enum DataSetDateTime 2: { 3: Local = 1, 4: Unspecified = 2, 5: UnspecifiedLocal = 3, 6: Utc = 4 7: }

  如果你读过前文,了解了DateTimeKind,技术你之前不曾关注过这个类型,也会猜出DataColumn用类型为DataSetDateTime的DateTimeMode属性指定时间的Kind。那么对于不同的DateTimeMode设置之间有何差异?为什么DataSetDateTime提供了另一个额外的成员UnspecifiedLocal呢?

  二、不同的DateTimeMode设置对DateTimeKind的影响

  这个问题很简单,对于数据类型为DateTime的DataColumn,如果你对DateTimeMode属性设置了不同的DataSetDateTime枚举值,当你对其赋值的时候,系统会自动将设置的时间转换成相应DateTimeKind的时间。下面的列表提供了基于不同DataSetDateTime枚举值采用的的转换规则:

DataSetDateTime.Local: 对于DateTimeKind.Local时间,不做任何转换;对于DateTimeKind.Utc时间,基于时区偏移量进行转换,并将Kind属性转换成DateTimeKind.Local;对于DateTimeKind.Unspecified,直接将Kind属性转换成DateTimeKind.Local,时间值(年、月、日、时、分、秒、毫秒等)保持不变; DataSetDateTime.Utc: 对于DateTimeKind.Utc时间,不做任何转换;对于DateTimeKind.Local时间,基于时区偏移量进行转换,并将Kind属性转换成DateTimeKind.Utc;对于DateTimeKind.Unspecified,直接将Kind属性转换成DateTimeKind.Utc,时间值(年、月、日、时、分、秒、毫秒等)保持不变; DataSetDateTime.Unspecified|UnspecifiedLocal:对于任何DateTimeKind类型的时间,直接将Kind属性转换成DateTimeKind.Unspecified,时间值(年、月、日、时、分、秒、毫秒等)保持不变。

  在这里我需要强调一下,在前文中我们提到:不同是调用DateTime的ToLocalTime/ToUtcTime方法,还是调用TimeZoneInfo的ConvertToUtcTime/ConvertFromUtcTime,如果将DateTimeKind.Unspecified转换成DateTimeKind.Local时间,实际上是将其当成DateTimeKind.Utc时间;反之,如果转换成DateTimeKind.Utc时间,则当成是DateTimeKind.Local时间。但是,在这里DateTimeKind.Unspecified的时间值会保留,改变的仅仅是Kind属性。

  三、一个简单的例子

  为了加深对上述转换规则的理解,我写了一个简单的例子。首先我创建了一个ContractDataSet的强类型的DataSet,里面具有一个Contact数据表表示一个联系人。Contact数据的结构如右图所示:处理表示Id和名称的两个字段之外,我添加了四个DateTime类型的字段表示生日。它们分别是:LocalBirthday、UtcBirthday、UnspecifiedBirthday和UnspecifiedLocalBirthday,前缀表示该数据列采用的DateTimeMode。

  然后,我写了下面三个辅助的方法:CreateContact通过传入的表示生日的DateTime创建一个ContractDataSet,DisplayBirthday分别将上诉四个字段的时间和Kind打印出来。

1: static ContactDataSet CreateContact(DateTime birthday) 2: { 3: var ds = new ContactDataSet(); 4: var row = ds.Contact.NewContactRow(); 5: row.Id = Guid.NewGuid().ToString(); 6: row.Name = "Foo"; 7: SetBirthDay(row,birthday); 8: ds.Contact.AddContactRow(row); 9: return ds; 10: } 11: static void SetBirthDay(ContactDataSet.ContactRow row,DateTime birthDay) 12: { 13: row.LocalBirthDay = birthDay; 14: row.UtcBirthDay = birthDay; 15: row.UnspecifiedBirthDay = birthDay; 16: row.UnspecifiedLocalBirthDay = birthDay; 17: } 18: static void DispalyBirthday(ContactDataSet ds) 19: { 20: var row = ds.Contact[0]; 21:
Console.WriteLine("\tLocal: \t\t\t{0}", row.LocalBirthDay); 22: Console.WriteLine("\tUtc: \t\t\t{0}", row.UtcBirthDay); 23: Console.WriteLine("\tUnspecified: \t\t{0}", row.UnspecifiedBirthDay); 24: Console.WriteLine("\tUnspecifiedLocal: \t{0}\n", row.UnspecifiedLocalBirthDay); 25:  26: Console.WriteLine("\tLocal: \t\t\t{0}", row.LocalBirthDay.Kind); 27: Console.WriteLine("\tUtc: \t\t\t{0}", row.UtcBirthDay.Kind); 28: Console.WriteLine("\tUnspecified: \t\t{0}", row.UnspecifiedBirthDay.Kind); 29: Console.WriteLine("\tUnspecifiedLocal: \t{0}\n", row.UnspecifiedLocalBirthDay.Kind); 30: }

  我们的实例程序是这样的:分别创建基于三种不同的DateTimeKind的DateTime对象,并据此创建三个ContractDataSet对象。最后调用DisplayBirthday方法将4个基于不同DateTimeMode的字段的时间和DateTimeKind打印出来。

1: var ds1 = CreateContact(new DateTime(1981, 8, 24, 0, 0, 0, DateTimeKind.Local)); 2: var ds2 = CreateContact(new DateTime(1981, 8, 24, 0, 0, 0, DateTimeKind.Unspecified)); 3: var ds3 = CreateContact(new DateTime(1981, 8, 24, 0, 0, 0, DateTimeKind.Utc)); 4:  5: Console.WriteLine("DateTimeKind.Local"); 6: DispalyBirthday(ds1); 7: Console.WriteLine("DateTimeKind.Unspecified"); 8: DispalyBirthday(ds2); 9: Console.WriteLine("DateTimeKind.Utc"); 10: DispalyBirthday(ds3);

  最终的输出结果证实了我们上述的关于时间转换规则的结论:

1: DateTimeKind.Local 2: Local: 8/24/1981 12:00:00 AM 3: Utc: 8/23/1981 4:00:00 PM 4: Unspecified: 8/24/1981 12:00:00 AM 5: UnspecifiedLocal: 8/24/1981 12:00:00 AM 6:  7: Local: Local 8: Utc: Utc 9: Unspecified: Unspecified 10: UnspecifiedLocal: Unspecified 11:  12: DateTimeKind.Unspecified 13: Local: 8/24/1981 12:00:00 AM 14: Utc: 8/24/1981 12:00:00 AM 15: Unspecified: 8/24/1981 12:00:00 AM 16: UnspecifiedLocal: 8/24/1981 12:00:00 AM 17:  18: Local: Local 19: Utc: Utc 20: Unspecified: Unspecified 21: UnspecifiedLocal: Unspecified 22:  23: DateTimeKind.Utc 24: Local: 8/24/1981 8:00:00 AM 25: Utc: 8/24/1981 12:00:00 AM 26: Unspecified: 8/24/1981 12:00:00 AM 27: UnspecifiedLocal: 8/24/1981 12:00:00 AM 28:  29: Local: Local 30: Utc: Utc 31: Unspecified: Unspecified 32: UnspecifiedLocal: Unspecified  四、DataSetDateTime.Unspecified V.S. DataSetDateTime.UnspecifiedLocal

  到不前为止,我们貌似还看不到DataSetDateTime.Unspecified和DataSetDateTime.UnspecifiedLoca的差别。实际上,它们的差别体现在序列化上面:DataSetDateTime.UnspecifiedLoca在序列化的时候会保留基于当前时区的偏移量,而DataSetDateTime.Unspecified则不会。这个结论我也可以实例来证实,为此我写了如下一段代码对ContactDataSet进行序列化,并将序列化后的XML打印出来。

1: var ds = CreateContact(new DateTime(1981, 8, 24, 0, 0, 0)); 2: using (MemoryStream stream = new MemoryStream(1000)) 3: { 4: ds.WriteXml(stream); 5: Console.WriteLine(ASCIIEncoding.ASCII.GetString(stream.GetBuffer()).Trim()); 6: }

  从输出的结果我们可以看出UnspecifiedBirthday和UnspecifiedLocalBirday之间的差别,后者有+8的偏移量,前者没有。

1: <ContactDataSet xmlns="http://tempuri.org/ContactDataSet.xsd"> 2: <Contact> 3: <Id>a1548a6b-9b8b-4799-bc10-31337e70c831</Id> 4: <Name>Foo</Name> 5: <UnspecifiedLocalBirthDay>1981-08-24T00:00:00+08:00</UnspecifiedLocalBirthDay> 6: <UnspecifiedBirthDay>1981-08-24T00:00:00</UnspecifiedBirthDay> 7: <UtcBirthDay>1981-08-24T00:00:00Z</UtcBirthDay> 8: <LocalBirthDay>1981-08-24T00:00:00+08:00</LocalBirthDay> 9: </Contact> 10: </ContactDataSet>

时间: 2024-10-06 19:29:53

System.DateTime 详解(续)的相关文章

一起谈.NET技术,System.DateTime 详解(续)

在<System.DateTime 详解>一文中,我们从跨时区的角度剖析了我们熟悉的System.DateTime类型.如果你还是采用传统的ADO.NET编程方式,并使用DataSet作为数据实体,可能你会熟悉System.Data.DataSetDateTime这么一个类型.这个类型也是为实现跨时区场景下对时间处理而设计的,为了对前文的补充,这篇文章就来谈谈基于DataSet的时间处理问题. 一.你是否关注过DataColumn的DateTimeMode属性 在ADO.NET编程模型中,Da

一起谈.NET技术,System.DateTime详解

最近一直在负责公司内部框架的升级工作,今天对一个小问题进行了重新思考--时间的处理.具体来说,是如何有效地进行时间的处理以提供对跨时区的支持.对于一个分布式的应用来说,倘若客户端和服务端部署与不同的地区,在对时间进行处理的时候,就需要考虑时区的问题.以我们现在的一个项目为例,这是一个为澳大利亚某机构开发的一个基于Smart Client应用(Windows Form客户端),服务器部署于墨尔本,应用的最终用户可能需要跨越不同的州.澳洲地广人稀,不同的州也有可能会跨越不同的时区.假设数据库并不支持

System.DateTime详解

最近一直在负责公司内部框架的升级工作,今天对一个小问题进行了重新思考--时间的处理.具体来说,是如何有效地进行时间的处理以提供对跨时区的支持.对于一个分布式的应用来说,倘若客户端和服务端部署与不同的地区,在对时间进行处理的时候,就需要考虑时区的问题.以我们现在的一个项目为例,这是一个为澳大利亚某机构开发的一个基于Smart Client应用(Windows Form客户端),服务器部署于墨尔本,应用的最终用户可能需要跨越不同的州.澳洲地广人稀,不同的州也有可能会跨越不同的时区.假设数据库并不支持

谁用光了磁盘?Docker System命令详解

本文讲的是谁用光了磁盘?Docker System命令详解, 为了保证可读性,本文采用意译而非直译. 用了一段时间Docker后,会发现它占用了不少硬盘空间.还好Docker 1.13引入了解决方法,它提供了简单的命令来查看/清理Docker使用的磁盘空间. 本文通过一个简单的示例,可以证明Docker能够很快地将磁盘占满.该示例通过play-with-docker.com运行.点击Add new instance即可创建新的实例,该实例安装了最新版的Docker 17.03.这篇博客主要讨论磁

PHP启动windows应用程序、执行bat批处理、执行cmd命令的方法(exec、system函数详解)_php实例

exec 或者 system 都可以调用cmd 的命令 直接上代码: 复制代码 代码如下: <?php /** 打开windows的计算器 */ exec('start C:WindowsSystem32calc.exe'); /** php生成windows的批处理文件后,再执行这个批处理文件*/ $filename = 't.bat'; $somecontent = 'C: '; $somecontent .= 'cd "C:/Program Files/MySQL-Front&quo

System.Data.DataTable计算功能详解

using System; using System.ComponentModel; using System.Data; using System.Windows.Forms; namespace WindowsApplication1 ...{ public partial class Form1 : Form ...{ public Form1() ...{ InitializeComponent(); } private void button1_Click(object sender,

详解在C++中显式默认设置的函数和已删除的函数的方法_C 语言

在 C++11 中,默认函数和已删除函数使你可以显式控制是否自动生成特殊成员函数.已删除的函数还可为您提供简单语言,以防止所有类型的函数(特殊成员函数和普通成员函数以及非成员函数)的参数中出现有问题的类型提升,这会导致意外的函数调用. 显式默认设置的函数和已删除函数的好处 在 C++ 中,如果某个类型未声明它本身,则编译器将自动为该类型生成默认构造函数.复制构造函数.复制赋值运算符和析构函数.这些函数称为特殊成员函数,它们使 C++ 中的简单用户定义类型的行为如同 C 中的结构.也就是说,可以创

C++的get()函数与getline()函数使用详解_C 语言

C++ get()函数读入一个字符 get()函数是cin输入流对象的成员函数,它有3种形式:无参数的,有一个参数的,有3个参数的. 1) 不带参数的get函数 其调用形式为 cin.get() 用来从指定的输入流中提取一个字符(包括空白字符),函数的返回值就是读入的字符. 若遇到输入流中的文件结束符,则函数值返回文件结束标志EOF(End Of File),一般以-1代表EOF,用-1而不用0或正值,是考虑到不与字符的ASCII代码混淆,但不同的C ++系统所用的EOF值有可能不同. [例]

C语言中的sscanf()函数使用详解_C 语言

sscanf() - 从一个字符串中读进与指定格式相符的数据. 函数原型: Int sscanf( string str, string fmt, mixed var1, mixed var2 ... ); int scanf( const char *format [,argument]... ); 说明: sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源. 其中的format可以是一个或多个 {%[*] [width] [{h |