PAYATHON 2026

如何避免 DataSet.ReadXml 将 UTC 日期时间转换为本地时间?

数据小陈

可以。对于表示 UTC 时刻的 NOW 列,应在加载数据前将其 DateTimeMode 设置为 DataSetDateTime.Utc。题目中的 XML 自带 schema,可以先读取 schema,设置列属性,然后加载数据。

为什么 16
会变成 18
?

题目中的 XML 值是:

<NOW>2026-04-01T16:15:58.573+00:00</NOW>

+00:00 表示零偏移。如果读取进程的本地时区是 UTC+2,同一时刻对应的本地时间就是 18:15:58.573,与问题中观察到的大约 18

一致。

schema 中对 NOW 的定义是:

<xs:element name="NOW" msdata:ReadOnly="true" type="xs:dateTime" />

这里指定了数据类型,但没有指定 DateTimeMode。DateTime 列的默认模式是 DataSetDateTime.UnspecifiedLocal,不会因为 SQL 中使用了 GETUTCDATE() 就自动改为 UTC 模式。DataSetDateTime 文档

默认模式读取带偏移的 XML 时间时,可能得到本地钟表时间,而保存后的 DateTime.Kind 是 Unspecified。因此,即使 Kind 不是 Local,也不能据此判断没有发生时区换算。这与 .NET 的日期存储及 XML 转换实现有关。DateTimeStorage 源码

客户端如何按 UTC 读取?

对于题目中的完整 XML,可以读取两次:第一次只读取 schema,不加载数据;设置好 NOW 列后,再用新的 reader 加载数据。

using System;
using System.Data;
using System.IO;

string xml = (string)moData;
DataSet moDataSet = new DataSet();

// 第一次只建立表和列。
using (StringReader schemaReader = new StringReader(xml))
{
    moDataSet.ReadXmlSchema(schemaReader);
}

DataColumn nowColumn = moDataSet.Tables["Table1"].Columns["NOW"];
nowColumn.DateTimeMode = DataSetDateTime.Utc;

// 使用已配置的 schema 加载数据。
using (StringReader dataReader = new StringReader(xml))
{
    moDataSet.ReadXml(dataReader, XmlReadMode.IgnoreSchema);
}

DateTime now = (DateTime)moDataSet.Tables["Table1"].Rows[0]["NOW"];
Console.WriteLine(now.ToString("O"));
Console.WriteLine(now.Kind);

对于题目中的值,预期结果为:

2026-04-01T16:15:58.5730000Z
Utc

这样可以保留同一个时刻,并以 UTC 表示。如果以后输入的是非零偏移,例如 18:15:58+02:00,UTC 结果应为 16:15:58Z,小时数会随偏移换算。

第二次读取使用 XmlReadMode.IgnoreSchema,使数据按已经配置好的表结构加载。该模式会忽略内联 schema,不符合现有表结构的数据可能被丢弃,因此实际接口中的表名、列名和 namespace 必须匹配。XmlReadMode 文档

为什么必须在加载数据之前设置?

不能先用原代码把所有数据读入,再直接将已有列的 DateTimeMode 改为 Utc。列中已有数据后,DateTimeMode 的修改受到限制;允许的例外是 Unspecified 与 UnspecifiedLocal 之间互换。DataColumn.DateTimeMode 文档

对于已经按默认模式读取的数据,建议从原始 XML 重新加载,并在加载前配置 UTC 模式。

如果能够修改 API,也应在服务端创建日期列时明确其 UTC 语义,并让序列化输出携带对应的 schema 配置。只对确实表示 UTC 时刻的列使用 Utc,不要统一修改所有日期列。

还有哪些注意事项?

  • 不要固定减去两小时。偏移取决于读取进程的本地时区以及日期对应的夏令时规则。
  • 不要删除 XML 中的 +00:00。删除后会失去输入中已经明确的偏移信息。
  • 不要把 DateTime.SpecifyKind(value, DateTimeKind.Utc) 当作时区转换。它只修改标记,不会把已经变成 18
    的值换算回 16
    。
  • Unspecified 适合没有时区语义的钟表时间。对这里明确表示 UTC 时刻的字段,优先使用 Utc;仅切换成 Unspecified 不能作为保留 UTC 的可靠方案。

排查时,同时检查列的 DateTimeMode、值的 Kind 和 ToString("O") 输出,比只看调试器里的小时数更容易确认问题。

备注:内容仅供参考。