如何将 NodaTime 与 BinaryReader & BinaryWriter (.NET, C#) 一起使用?

How to Use NodaTime with BinaryReader & BinaryWriter (.NET, C#)?

在我们主要使用 Untiy 引擎开发的跨平台多应用网络系统中,我们使用自定义二进制序列化,它使用 BinaryWriter 简单地写入原语,然后使用镜像布局,使用 BinaryReader 读取相同的原语。请注意,我们不使用 BinaryFormatter,而是依赖于手动指定的二进制布局。它对我们来说非常有效,因为我们可以绝对控制序列化的内容和方式。我们还使用对象的原始表示而不是某种字符串格式,然后进行解析以保持紧凑。因此,例如我们会直接写一个整数而不是 int.ToString()。换句话说,紧凑而不是人类可读的。最低限度地明确反序列化回序列化的内容。

最近我接触到了 NodaTime,它是 "better"(.NET 的 DateTime、DateTimeOffset、TimeZoneInfo)解决方案,用于处理所有时间问题。我特别喜欢类型的严格性,它迫使我思考我到底在说什么(干净的通用时间线与时区之字形的疯狂等)。

我将日期和时间管理移至 NodaTime,但现在我无法将 Noda 类型序列化为二进制流作为基元。似乎没有"standard" 属性 访问范例。有些类型有 getter,比如 Duration.TotalNanoseconds,有些类型有类似 ToXyz() 的方法,比如 Instant.ToUnixTicks()。顺便说一句,这是我建议简化的事情:用于纯粹访问预期的类型的准系统表示的 getters 和用于转换计算的方法 a'la ToXyz()。例如,对于 Instant 类型,这是缺失的。

我确实用 Noda 的格式模式尝试了 format/parse 方法,但由于某些原因,大多数提供的格式模式都不是往返的,甚至抛出一个异常,即这些模式不支持解析,只支持格式化:

binaryWriter.Write(ZonedDateTimePattern.ExtendedFormatOnlyIso.Format(value));
ZonedDateTimePattern.ExtendedFormatOnlyIso.Parse(binaryReader.ReadString()).Value;

我想要的是这么简单的东西:

binaryWriter.Write(instant.Nanoseconds);
instant = new Instant(binaryReader.ReadDouble());

binaryWriter.Write(duration.nanoseconds);
duration= new Duration(binaryReader.ReadDouble());

我可以使用哪种一致的、最好是基于非格式化解析往返的方法来实现所有 NodaTime 类型的紧凑二进制序列化?

如果这不可能,那么每种 NodaTime 类型的推荐往返模式是什么?

PS:代码示例中的文本模式在其名称中明确表示它们不是往返(仅格式,无解析)。我的错。

我们直接在 NodaTime 1.x 和 2.x 中支持二进制序列化,但将从 3.x 中删除。

如果你想直接写,我建议创建扩展方法来简化这个:

public static void WriteInstant(this BinaryWriter writer, Instant instant)

等这样你就可以写 writer.WriteInstant(instant); 并在一个地方隔离精确的格式。这是一个未经测试的实现:

public static class BinaryWriterNodaTimeExtensions
{
    public static void WriteInstant(this BinaryWriter writer, Instant instant) =>
        writer.WriteDuration(instant - NodaConstants.UnixEpoch);

    public static void WriteDuration(this BinaryWriter writer, Duration duration)
    {
        writer.Write(duration.Days);
        writer.Write(duration.NanosecondOfDay);

        // Alternative implementation if you don't need durations bigger than +/- 292 years
        // writer.Write(duration.ToInt64Nanoseconds());
    }

    public static void WriteLocalDateTime(this BinaryWriter writer, LocalDateTime localDateTime)
    {
        writer.WriteLocalDate(localDateTime.Date);
        writer.WriteLocalTime(localDateTime.TimeOfDay);
    }

    public static void WriteZonedDateTime(this BinaryWriter writer, ZonedDateTime zonedDateTime)
    {
        writer.WriteLocalDateTime(zonedDateTime.LocalDateTime);
        // Note: no indication of the DateTimeZoneProvider. There's no standard way of representing
        // that, but most applications would use the same one everywhere.
        writer.Write(zonedDateTime.Zone.Id);
        writer.WriteOffset(zonedDateTime.Offset);
    }

    public static void WriteLocalDate(this BinaryWriter writer, LocalDate localDate)
    {
        // Casting to byte to optimize for space, as requested in the question.
        writer.Write((byte) localDate.Year);
        writer.Write((byte) localDate.Month);
        writer.Write((byte) localDate.Day);
        // You could omit this if you'll only ever use the ISO calendar.
        writer.Write(localDate.Calendar.Id);
    }

    public static void WriteLocalTime(this BinaryWriter writer, LocalTime localTime) =>
        writer.Write(localTime.NanosecondOfDay);

    public static void WriteOffset(this BinaryWriter writer, Offset offset) =>
        writer.Write(offset.Seconds);

    public static void WriteOffsetDateTime(this BinaryWriter writer, OffsetDateTime offsetDateTime)
    {
        writer.WriteLocalDateTime(offsetDateTime.LocalDateTime);
        writer.WriteOffset(offsetDateTime.Offset);
    }
}

这并未涵盖所有类型,但可能是您最可能需要的类型。 (其他人可以很容易地以类似的方式创建。) reader 方面将是相似的,但只是相反。在读取持续时间时,您可能会创建两个持续时间,一个用于几天,一个用于一天中的纳秒,然后将它们相加。

评论您的 API 设计说明:

There seems to be no "standard" property accessing paradigm. Some types have getters, like Duration.TotalNanoseconds, some instead have a'la ToXyz() methods like Instant.ToUnixTicks(). By the way, this is something I suggest to simplify: getters for pure access of expectable bare-bones representation of the type and methods a'la ToXyz() for conversion calculation. This is missing, for example, for Instant type.

这完全是故意的。持续时间内的纳秒总数是它固有的。 Instant(等)的 "number of ticks since the Unix epoch" 是人为的,因为将 Unix 纪元引入了该问题。我们碰巧在内部使用了 Unix 纪元,但我不希望它渗透到 API 设计中——因此它被建模为一种方法,使它具有更明确的 "conversion-like" 感觉。我将 而不是 添加 Nanoseconds 属性(甚至 UnixNanoseconds)到 Instant