DST切换时段DateTime经WriteXML/ReadXML往返丢失精度,如何解决?
解决DataSet序列化/反序列化时UTC DateTime在夏令时切换时段的精度丢失问题
问题背景
在东部时区(UTC-5)环境下,使用DataSet的WriteXml和ReadXml进行XML序列化与反序列化时,夏令时(DST)切换时段的UTC DateTime无法准确保留。例如,原始UTC时间为2023年3月12日2:01:00,反序列化后被错误修正为3:01:00——原因是该UTC时间对应的本地时间(东部时区3月12日2:01 AM)属于DST跳过时段,系统自动调整了时间,但这完全破坏了UTC时间的精度。
可复现代码:
using System; using System.Data; static class Program { [STAThread] static int Main(string[] args) { DataSet data = new DataSet(); data.Tables.Add(new DataTable()); data.Tables[0].Columns.Add("Date", typeof(DateTime)); data.Tables[0].Rows.Add(new DateTime(2023, 3, 12, 2, 1, 0, DateTimeKind.Utc)); string path = System.IO.Path.GetTempFileName(); data.WriteXml(path, XmlWriteMode.WriteSchema); data = new DataSet(); data.ReadXml(path); System.Diagnostics.Debug.WriteLine($"Date is {data.Tables[0].Rows[0][0]}"); // 预期输出: Date is 3/12/2023 2:01:00 AM // 实际输出: Date is 3/12/2023 3:01:00 AM return 0; } }
问题根源
DataSet内置的XML序列化逻辑会将DateTime类型的UTC时间转换为本地时间存储到XML中,反序列化时再转换回UTC。但在DST切换的“缺失时段”,对应的本地时间不存在,系统会自动执行时间修正(如将2:01 AM调整为3:01 AM),最终导致原始UTC时间丢失。
解决方案
1. 改用DateTimeOffset类型(推荐)
将DataTable中的列类型从DateTime改为DateTimeOffset,该类型会同时存储时间和偏移量,序列化时不会丢失UTC信息,也不会触发本地时间转换:
using System; using System.Data; static class Program { [STAThread] static int Main(string[] args) { DataSet data = new DataSet(); data.Tables.Add(new DataTable()); // 改为DateTimeOffset类型 data.Tables[0].Columns.Add("Date", typeof(DateTimeOffset)); // 存储带UTC偏移量的时间 data.Tables[0].Rows.Add(new DateTimeOffset(2023, 3, 12, 2, 1, 0, TimeSpan.Zero)); string path = System.IO.Path.GetTempFileName(); data.WriteXml(path, XmlWriteMode.WriteSchema); data = new DataSet(); data.ReadXml(path); System.Diagnostics.Debug.WriteLine($"Date is {data.Tables[0].Rows[0][0]}"); // 输出: Date is 3/12/2023 2:01:00 AM +00:00 return 0; } }
2. 自定义XML序列化逻辑
绕过DataSet内置的序列化,使用XmlSerializer手动处理DateTime,明确以UTC格式序列化:
using System; using System.Data; using System.Xml.Serialization; using System.IO; static class Program { [STAThread] static int Main(string[] args) { DateTime originalUtcTime = new DateTime(2023, 3, 12, 2, 1, 0, DateTimeKind.Utc); string path = System.IO.Path.GetTempFileName(); // 自定义序列化:以UTC格式写入 var serializer = new XmlSerializer(typeof(DateTime)); using (var writer = new StreamWriter(path)) { serializer.Serialize(writer, originalUtcTime); } // 自定义反序列化:直接解析为UTC时间 DateTime restoredTime; using (var reader = new StreamReader(path)) { restoredTime = (DateTime)serializer.Deserialize(reader); restoredTime = DateTime.SpecifyKind(restoredTime, DateTimeKind.Utc); } System.Diagnostics.Debug.WriteLine($"Date is {restoredTime}"); // 输出: Date is 3/12/2023 2:01:00 AM return 0; } }
3. 优化临时字符串存储方案
如果无法立即替换类型,可将UTC时间序列化为带明确标记的字符串,反序列化时精确解析:
using System; using System.Data; static class Program { [STAThread] static int Main(string[] args) { DateTime originalUtcTime = new DateTime(2023, 3, 12, 2, 1, 0, DateTimeKind.Utc); DataSet data = new DataSet(); data.Tables.Add(new DataTable()); // 用字符串列存储UTC时间 data.Tables[0].Columns.Add("DateString", typeof(string)); // 以ISO 8601格式存储,带Z标记表示UTC data.Tables[0].Rows.Add(originalUtcTime.ToString("yyyy-MM-ddTHH:mm:ssZ")); string path = System.IO.Path.GetTempFileName(); data.WriteXml(path, XmlWriteMode.WriteSchema); data = new DataSet(); data.ReadXml(path); // 反序列化时精确解析UTC字符串 string dateString = data.Tables[0].Rows[0]["DateString"].ToString(); DateTime restoredTime = DateTime.ParseExact( dateString, "yyyy-MM-ddTHH:mm:ssZ", null, DateTimeStyles.AdjustToUniversal ); System.Diagnostics.Debug.WriteLine($"Date is {restoredTime}"); // 输出: Date is 3/12/2023 2:01:00 AM return 0; } }
内容的提问来源于stack exchange,提问作者user21414783
相关产品推荐
相关产品推荐

