【问题标题】:Don't Serialize, or Remove, TimeZone from Serialized DateTime objects不要从序列化的 DateTime 对象中序列化或删除 TimeZone
【发布时间】:2012-04-25 23:14:28
【问题描述】:

我得到的奇怪任务是使用 XML 序列化序列化一个 LARGE 对象。此对象包含多个嵌套的 UserDefined 类,以及多个 DateTime 字段。 DateTime 数据的要求是它必须始终显示在最初创建和设置数据的用户的时区中。因此,我不能使用 UTC 或本地时间,因为在反序列化时,它们不会与原来相同。我也无法以 UTC 显示值,它们必须以当地时间显示。我需要的是一些奇怪的序列化格式,代表“绝对本地时间”的概念......那就是“没有时区的本地时间”。

我可以使用正则表达式从日期字符串中去除 TZ,这很简单。但是我正在处理的对象的绝对大小意味着我经常会遇到 OutOfMemoryException。我看到它在没有调试的情况下运行一次,并且在操作过程中我使用的内存从 100k 飙升到 800k。不是很好。那是较小的文件之一。

Doc.DocumentElement.InnerXML = Regex.Replace(Doc.DocumentElement.InnerXML, "(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})(\\+|-)(\\d{2}:\\d{2})", "$1")

到目前为止,我看到的唯一选择是创建所有 dateTime 字段的副本,将 DT 字段本身设置为“XmlIgnore()”,然后在 doc 之后手动从序列化字符串数据中恢复所有日期重新加载。这也不实用。 见Custom DateTime XML Serialization

有没有办法强制序列化引擎在没有 TimeZone 数据的情况下序列化 DateTime 对象?最好是不需要单独应用于对象中每个 DT 属性的通用内容?

!!编辑!!

我可能找到了部分解决方案。它至少可能有助于向前发展。 DateTimeKind.Unspecified 在序列化时似乎没有附加任何 TimeZone 数据。这是我正在寻找的解决方案。使用 DateTime.SpecifyKind 强制转换我的所有 DateTime 数据?

public DateTime? StartDate
    {
        get 
        { return _StartDate; }
        set
        {
            if (_StartDate == value)
                return;

            if (value != null)
                _StartDate = DateTime.SpecifyKind(value.Value, DateTimeKind.Unspecified);
            else
                _StartDate = value;

            OnPropertyChanged("StartDate");
        }
    }

【问题讨论】:

  • 您想保留时区(“显示在最初创建的用户的时区中”)并同时删除它?选择一个你想要的......似乎修复显示代码以你想要的方式处理时区会更容易。
  • 保留时区是可见的效果。我真正想做的是完全忽略 TimeZone 数据。如果 TimeZone 从未包含在序列化数据中,则在反序列化时不会影响存储的 dateTime 值。
  • 所以您想让用户输入“2011 年 5 月 5 日上午 9:00”并且无论时区如何都应该保持在上午 9 点? (类似于早上叫醒你的闹钟)即在印度的半小时时区输入/序列化上午 9 点,当在美国的一个时区(相隔近 12 小时和整小时)反序列化时,仍显示为上午 9 点?
  • 这或多或少与您的问题中所述相反...建议作为答案发布。
  • 太奇怪了!!提出问题的人回答了他自己的问题,没有投票。答案标记为正确,投反对票。发生了什么。 +1 为您的答案而不是问题。在过去的几个小时里,这件小事一直困扰着我。

标签: c# datetime xml-serialization


【解决方案1】:

我认为您需要重新评估您的要求或假设。

你写的:

日期时间数据的要求是它必须始终显示在最初创建和设置数据的用户的时区中。因此,我不能使用 UTC 或本地时间,因为在反序列化时,它们不会与它们相同。

我不认为你的分析是正确的。在我看来,您不必要地将序列化与存储与向用户“显示”混合在一起。但这两件事不应该相关。据我了解,要求是:

  • 您想要序列化和反序列化多个不同的时间值。
  • “显示”这些时间时,您希望显示使用原始时区。

这些是不同的要求。

序列化 DateTime 将及时存储一个时刻,但您会丢失 TimeZone 信息。在我看来,您需要单独序列化时区信息,每个 XML 文档一次。如果您这样做,那么时间的反序列化会自动工作 - 您总是会得到您最初放入 存储的确切时间 out 存储。

当您显示时间时,请使用单独存储在 XML 文档中的时区信息。如果原始对象中不存在包含 TimeZone 的属性,那么在我看来,您的对象模型不太适合应用程序的要求,在这种情况下,您需要修改对象定义以包含一个标识时区。 (见http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx

至于内存不足的错误,那可能是一个无关的问题。这也可能是因为您使用大型 XmlDocument 对象。使用 Xml 序列化时,这应该是不必要的。

【讨论】:

  • 您对不同的要求是正确的。我已经有对象(及其日期时间)的序列化工作。第二点需要稍微重申一下。显示 DateTime 信息时,我应该完全忽略 TimeZone。我什至不应该显示原始的时区,虽然那是效果,但更多的是时区的想法甚至不应该首先进入等式。
  • 除非你存储两个数量,否则你不能这样做。要在不考虑时区的情况下准确地序列化和反序列化时间,需要 UTC。当您添加特殊显示要求时,很明显您需要存储或序列化额外的信息位 - “无时区”时间,或应始终显示时间的原始时区。我认为您可能需要多考虑一下您的要求。
  • 要求是“静态”数据。它不应该改变。目标。中国的 Jimmy 输入他所有的数据,然后通过我们的数据传输系统将序列化的对象发送给纽约的 Bob,Bob 将其反序列化,以便进行数据分析和奖金计算。 Bob 不在乎 Jimmy 输入数据时在纽约的时间。
  • 好的,默认情况下 DateTime 会使用偏移量进行序列化。它是一个“时刻”,相对于 UTC 表示。要反序列化然后相对于其原始时区显示该值,您可以:(a)摆弄反序列化以“剥离”偏移信息,或(b)除了时间之外还序列化原始时区并在格式化时应用该时区显示的时间值。似乎 (a) 是错误的做事方式,更容易造成混乱。
  • @Cheeso DateTime 默认情况下不会使用偏移量进行序列化;偏移量是一个可选组件。偏移量的序列化是相对于 DateTime 值的 DateTimeKind 的:Local 添加偏移量; Unspecified 不添加偏移量; Utc 添加“Z”。
【解决方案2】:

我建议创建自定义类型来保存此类日期。它可以让你以任何你想要的方式处理序列化。作为更简单的方法,可以考虑将它们保存为 ISO8601 格式 (2012-05-04-26T12:57) 的字符串,而没有专门针对这种情况指定的时区。

从序列化数据中删除时区可能不是一个好主意,因为一旦您真正需要节省绝对时间,它就会引起有趣的问题。特别是如果代码是共享的。

【讨论】:

    【解决方案3】:

    找到了答案。这不是我想要的,但可以作为一种有效的解决方法。

    private static readonly Regex DTCheck = new Regex(@"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})([\+|-]\d{2}:\d{2})");
    
        /// <summary>
        /// Removes any instances of the TimeZoneOffset from the RigX after it has been serialized into an XMLString ++ Called from the "Save" process
        /// </summary>
        /// <param name="rigx"></param>
        /// <returns>StringReader referencing the re-formatted XML String</returns>
        private static StringReader RemoveTZOffsetFromRigX(RigX rigx)
        {
            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            XmlSerializer ser = new XmlSerializer(typeof(RigX));
    
            ser.Serialize(sw, rigx);
    
            string xmlText = sb.ToString();
    
            if (DTCheck.IsMatch(xmlText))
                xmlText = DTCheck.Replace(xmlText, "$1");
    
            StringReader Sreader = new StringReader(xmlText);
    
            return Sreader;
        }
    
        /// <summary>
        /// Removes the TimeZone offset from a RigX as referenced by stream.  Returns a reader linked to the new stream  ++ Called from the "Load" process
        /// </summary>
        /// <param name="stream">stream containing the initial RigX XML String</param>
        /// <returns>StringReader referencing the re-formatted XML String</returns>
        private StringReader RemoveTZOffsetFromXML(MemoryStream stream)
        {
            stream.Position = 0;
            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
    
            string xmlText = reader.ReadToEnd();
            reader.Close();
            stream.Close();
    
            if (DTCheck.IsMatch(xmlText))
                xmlText = DTCheck.Replace(xmlText, "$1");
    
            StringReader Sreader = new StringReader(xmlText);
    
            return Sreader;
        }
    

    在从文件中读取 XML 之后,在通过序列化程序运行它之前,在裸 XML 文本上运行正则表达式以删除偏移量。该函数返回一个针对修改后的 XML 字符串运行的字符串阅读器,然后可以通过反序列化将其运行到对象中。

    与其使用序列化器将xml直接保存到输出流,不如使用stringBuilder截取序列化后的xml。然后使用与加载过程相同的过程,通过正则表达式删除 TimeZone 偏移量,然后返回一个链接到修改后文本的 StringReader,然后用于将数据写回文件中。

    有点生硬的感觉,但很有效。虽然非常占用内存,但如果可以,请避免直接调试函数,或者如果必须,尽量不要评估字符串,上次我尝试它完全崩溃了我的 VS 实例。

    【讨论】:

      猜你喜欢
      • 2015-10-06
      • 2011-03-17
      • 1970-01-01
      • 2010-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多