这是一个很常见的反模式:
string dateTime = reader["dte_tme"].ToString();
DateTime dt = Convert.ToDateTime(dateTime);
正确的咒语如下:
DateTime dt = (DateTime) reader["dte_tme"];
虽然reader["dte_time"] 的返回类型是object,但该对象包含DateTime。如果设置断点,您会看到DateTime 已经存在。你只需要cast 它就可以分配给DateTime 变量。这称为unboxing。
如果 SQL 数据库中的 datetime 列可以为空,那么您应该像这样进行测试:
DateTime? dt = reader["dte_tme"] == DBNull.Value ? null : (DateTime) reader["dte_tme"];
或者有时候你会看到这样,同样可以接受:
DateTime? dt = reader["dte_tme"] as DateTime?;
在从数据库中检索时,不绝对需要在任何时候将其视为字符串。如果它是数据库中的 datetime,那么它是 C# 中的 DateTime。
从数据读取器中提取数据时,您应该使用强制转换操作,即使是其他数据类型,例如整数、小数、甚至字符串。您可以查看 SQL Server 数据类型和 .NET 数据类型in the chart here 之间的其他类型映射。
现在关于时区,这是一个不同的问题。首先,了解DateTime 不保留时区。它只知道分配给它的DateTimeKind。默认情况下,类型是Unspecified,这基本上意味着“我不知道;它可能是任何东西”。
也就是说,不同的协议有不同的要求。 JSON 没有预定义的日期格式,但最常见的约定(和最佳实践)是以ISO8601 格式存储日期,即YYYY-MM-DDTHH:mm:ss。时区信息可选,当DateTime 的.Kind 为DateTimeKind.Unspecified 时,通常不会包含时区信息。如果是Utc,那么最后你会看到Z,如果是Local,那么你会看到本地时区的偏移量,比如+11:00。也就是说,在那个特定的时刻,适合那个时区的任何偏移量。偏移量与“time zone”不同,因为不同的偏移量可能适用于同一时区的不同时间 - 通常适用于 daylight saving time。
XML 有点不同。 .NET 中的大部分 XML 序列化将使用 W3C XML Schema 规范,并将 DateTime 映射到 xsd:dateTime 类型。具体如何渲染将取决于Kind。
- 对于
DateTimeKind.Unspecified,它将不包含偏移量。
- 对于
DateTimeKind.Utc,它将附加一个Z
- 对于
DateTimeKind.Local,它将附加本地偏移量
当您在数据集中查看Kind 时,您问为什么Local 是Local?那是因为DataSet 有一个丑陋的行为,即假设所有时间都是本地时间。它基本上忽略了.Kind 属性并假定DateTimeKind.Local 的行为。这是一个长期存在的错误。
理想情况下,您应该在 SQL Server 中使用 datetimeoffset 类型,在 .NET 中使用 DateTimeOffset 类型。这避免了“种类”问题,并在 JSON 中很好地序列化(当您使用像 JSON.NET 这样的现代序列化程序时)。然而,在 XML 中,它应该被映射到 xsd:dateTime 并像本地 DateTime 一样呈现,只是使用正确的偏移量。然而,它最终看起来像这样:
<Value xmlns:d2p1="http://schemas.datacontract.org/2004/07/System">
<d2p1:DateTime>2015-03-18T03:34:11.3097587Z</d2p1:DateTime>
<d2p1:OffsetMinutes>-420</d2p1:OffsetMinutes>
</Value>
那是DataContractXmlSerializer。如果您使用XmlSerializer,则根本无法渲染。你只会得到一个空节点,比如<Value/>。
但是,尽管说了这么多,但您说您使用的是DataSet,这伴随着它自己的一组行为。不利的一面是,它会假设所有DateTime 值都有DateTimeKind.Local——即使它们没有,正如我上面提到的。考虑以下几点:
DataTable dt = new DataTable();
dt.Columns.Add("Foo", typeof (DateTime));
dt.Rows.Add(new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Unspecified));
dt.Rows.Add(new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Local));
dt.Rows.Add(new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc));
DataSet ds = new DataSet();
ds.Tables.Add(dt);
string xml = ds.GetXml();
Debug.Write(xml);
这是我运行它时的输出(在美国太平洋时区):
<NewDataSet>
<Table1>
<Foo>2015-01-01T00:00:00-08:00</Foo>
</Table1>
<Table1>
<Foo>2015-01-01T00:00:00-08:00</Foo>
</Table1>
<Table1>
<Foo>2015-01-01T00:00:00-08:00</Foo>
</Table1>
</NewDataSet>
不过,好消息是DateTimeOffset 的值要好一些:
DataTable dt = new DataTable();
dt.Columns.Add("Foo", typeof(DateTimeOffset));
dt.Rows.Add(new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.FromHours(11)));
dt.Rows.Add(new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.Zero));
dt.Rows.Add(new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.FromHours(-3)));
DataSet ds = new DataSet();
ds.Tables.Add(dt);
string xml = ds.GetXml();
Debug.Write(xml);
输出:
<NewDataSet>
<Table1>
<Foo>2015-01-01T00:00:00+11:00</Foo>
</Table1>
<Table1>
<Foo>2015-01-01T00:00:00Z</Foo>
</Table1>
<Table1>
<Foo>2015-01-01T00:00:00-03:00</Foo>
</Table1>
</NewDataSet>
在大多数情况下,这是正确的,尽管技术上它应该使用+00:00 而不是Z 序列化第二个,但这在实践中并不重要。
我想说的最后一件事是,总的来说,DataSet 是过去的遗物。在现代开发中,应该很少需要在日常代码中使用它。如果可能,我会认真考虑探索其他选择。