如何将 DateTimeOffset 序列化为二进制流

How to serialize a DateTimeOffset into a binary stream

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

要序列化 ​​DateTime,我有:

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

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

在性能和存储大小方面 serialize/deserialize DateTimeOffset 的最佳方法是什么?

根据 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 有多快?

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

之间变化时出现问题

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);
}

并不是说我的回答增加了很多,但我已经编写了这个用于打包和解包 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));
    }
}