【问题标题】:How to serialize a DateTimeOffset into a binary stream如何将 DateTimeOffset 序列化为二进制流
【发布时间】:2016-07-31 11:55:22
【问题描述】:

我想知道将DateTimeOffset 序列化为二进制流(使用BinaryWriter)并再次反序列化(使用BinaryReader)的最佳方法是什么。

要序列化DateTime,我有:

    public void WriteValue(DateTime value)
    {
        _writer.Write(value.ToBinary());
    }

    public DateTime ReadDateTime()
    {
        return DateTime.FromBinary(_reader.ReadInt64());
    }

就性能和存储大小而言,序列化/反序列化 DateTimeOffset 的最佳方法是什么?

【问题讨论】:

  • A DateTime 是一个双精度数,整数是从 1900 年 1 月 1 日(第 1 天)开始的天数,小数是一天的小数。 double 存储为 32 位浮点数。所以使用 Int64 会产生舍入误差。
  • BinaryFormatter 很容易做到这一点。看起来您正在滚动自己的序列化程序?
  • @Plutonix:是的,我愿意。
  • 您可以序列化它的 UtcTicks 和 Offset.Ticks 属性,使用 DateTimeOffset(long, Timespan) 构造函数反序列化。如果空间很重要,那么您可以以分钟为单位序列化 Offset 属性,简短就足够了。
  • 如果你不能通过使用秒表来解决这个问题,那么你做错了。对此进行微优化通常是没有意义的,文件 I/O 比这样的代码要昂贵得多。

标签: c# .net base-class-library


【解决方案1】:

根据 Hans Passant 的评论,我提出了以下解决方案。序列化:

    public void WriteValue(DateTimeOffset value)
    {
        WriteValue(value.DateTime);
        WriteValue((short)value.Offset.TotalMinutes);
    }

并反序列化:

    public DateTimeOffset ReadDateTimeOffset()
    {
        var dateTime = ReadDateTime();
        var minutes = ReadInt16();
        return new DateTimeOffset(dateTime, TimeSpan.FromMinutes(minutes));
    }

因此,这些方法调用了问题中概述的DateTime 的现有序列化方法。

我仍然想知道这是否是最有效的方法。拨打TotalMinutesTimeSpan.FromMinutes有多快?

【讨论】:

    【解决方案2】:

    并不是说我的回答增加了很多,但我已经编写了这个用于打包和解包 DateTimeOffset 的小实用程序:

    public static class DateTimeOffsetExtensions
    {
        /// <summary>
        /// Packs DateTimeOffset to bytes
        /// </summary>
        /// <param name="dateTimeOffset"></param>
        /// <returns>10 byte packed bytearray</returns>
        public static byte[] GetBytes(this DateTimeOffset dateTimeOffset)
        {
            return BitConverter.GetBytes(dateTimeOffset.Ticks).
               Concat(BitConverter.GetBytes((Int16)dateTimeOffset.Offset.TotalMinutes)).ToArray();
        }
        /// <summary>
        /// Reads 10 bytes from a buffer and turns back to DateTimeOffset
        /// </summary>
        /// <param name="bytes">Buffer</param>
        /// <param name="offset">Offset to read from</param>
        /// <returns></returns>
        public static DateTimeOffset FromBytes(byte[] bytes, int offset)
        {
            var ticks = BitConverter.ToInt64(bytes, offset);
            var offsetMinutes = BitConverter.ToInt16(bytes, offset + 8);
            return new DateTimeOffset(ticks, TimeSpan.FromMinutes(offsetMinutes));
        }
    }
    

    【讨论】:

    • 这看起来不错。不过,我不太了解 FromBytes 的偏移参数。那不总是0吗?或者您是否需要在转换为字节数组(字节数组本身除外)后存储一些内容,以使您能够返回 DateTimeOffset,即某种偏移量。
    【解决方案3】:

    序列化和反序列化日期时间对象(任何语言)的正确方法是使用时区和夏令时独立值,例如 UTC 时间。在反序列化时,要么保持 UTC(如果仅由代码使用),要么选择当前的本地时区偏移量(如果由人类读取)。这样可以避免不同时区的问题,即使值保留在一个国家/地区,也可以避免在序列化/反序列化之间夏时制变化时出现问题。

    DateTimeOffset.UtcTicks 属性(64 位整数)非常适合二进制序列化程序。注意不要使用DateTimeOffset.Ticks,因为该属性包含任何偏移量。

    DateTimeOffset dto = DateTimeOffset.Now;
    using (var w = new BinaryWriter(...))
    {
        w.Write(dto.UtcTicks); // do not use dto.Ticks!
    }
    

    反序列化看起来很笨拙,因为没有简单的方法可以从 UTC 滴答声中创建 DateTimeOffset。如果对象从未向人类展示过,您可以跳过转换为当地时间。

    using (var r = new BinaryReader(...))
    {
        long utcTicks = r.ReadInt64();
        dto = new DateTimeOffset(utcTicks, TimeSpan.Zero).ToLocalTime();
    }
    

    在存储方面,您需要 8 字节 = 64 位。我不确定反序列化性能(未经测试),但我认为它应该尽可能快。

    编辑:为了保留时区(偏移)信息,

    DateTimeOffset dto = DateTimeOffset.Now;
    using (var w = new BinaryWriter(...))
    {
        w.Write(dto.Ticks);
        w.Write(dto.Offset.Ticks);
    }
    
    using (var r = new BinaryReader(...))
    {
        long ticks = r.ReadInt64();
        long offsetTicks = r.ReadInt64();
        dto = new DateTimeOffset(ticks, new TimeSpan(offsetTicks);
    }
    

    【讨论】:

    • 如@Patrick Stalph 建议的那样,将 DateTimeOffset 减少为时区和夏令时独立值,会导致信息丢失;我认为它不是(总是)序列化日期时间对象的正确方法。 DateTimeOffset 存储了一个时区偏移量和一个时间(无论是在UTC还是在指定的时区是一个实现细节),这提供了三种时间:事件的墙上时间、UTC时间戳和查看时间用户的本地时区。仅存储 UtcTicks 会丢失事件的原始挂起时间。
    • 你是对的,但我想一个国家对另一个国家的实际挂钟感兴趣的情况很少见 :-)
    • 我们编写软件来监控世界各地的事件。您可以想象有人在夜间 03:00 进入建筑物(在他们的时区,而不是我的时区)应该触发不同的响应,然后有人在早上 08:00 进入。在其他时候,操作员想知道事件在他自己的时区中的时间,因为最近发生的事件可能需要与几个小时前发生的事件不同的响应。
    猜你喜欢
    • 1970-01-01
    • 2017-10-17
    • 2013-07-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    • 1970-01-01
    • 2012-12-13
    • 2023-03-30
    相关资源
    最近更新 更多