如何使用 protobuf-net 将 C# 日期时间转换为 Python 日期时间?

How to convert C# datetimes into Python datetimes with protobuf-net?

我使用 protobuf-net 序列化了一个 C# DateTime.Now 对象数组,并使用其消息结构的原型文件 (struct_pb2.py) 在 Python 中打开文件.

// The struct definition in C#

    [ProtoContract]
    public struct struct_tick
    {
        [ProtoMember(1)]
        public uint[] arr_currentIndex;

        [ProtoMember(2)]
        public string[] arr_currentType;

        [ProtoMember(3)]
        public DateTime[] arr_currentTime;   // <- for DateTime.Now objects

        public struct_tick(byte size_index, byte size_type, byte size_time)
        {
            arr_currentIndex = new uint[size_index];
            arr_currentType = new string[size_type];
            arr_currentTime = new DateTime[size_time];
        }
    }
syntax = "proto3";

import "google/protobuf/timestamp.proto";

message struct_tick 
{
  repeated uint32 arr_currentIndex  = 1;
  repeated string arr_currentType   = 2;
  repeated google.protobuf.Timestamp arr_currentTime = 3;
}

它在 Python 中打开得很好,但是如何正确地将 C# 日期时间对象转换为 Python 日期时间对象?让我感到困惑的是文件的日期时间数组中显示的内容。

>>> struct = struct_pb2.struct_tick()
>>> with open(filename, "rb") as f:
>>>     struct.ParseFromString(f.read())

>>> lst_time = [ time for time in struct.arr_currentTime]
>>> lst_time[:5]
[seconds: 1573571465
nanos: 335624000
, seconds: 1573571465
nanos: 335624000
, seconds: 1573571465
nanos: 335624000
, seconds: 1573571465
nanos: 335624000
, seconds: 1573571465
nanos: 335624000
]

>>> lst_time[-5:]
[seconds: 1573571465
nanos: 337636000
, seconds: 1573571465
nanos: 337636000
, seconds: 1573571465
nanos: 337636000
, seconds: 1573571465
nanos: 337636000
, seconds: 1573571465
nanos: 337636000
]

当我在 C# 中读取同一个文件时,第一个 datetime 对象表示 21:59:39.7450630,最后一个对象表示 23:38:50.3848014,这意味着几乎有差距2小时。但是Python中显示的完全不同。

是不是因为我序列化的时候用了DateTime,反序列化的时候用了google.protobuf.Timestamp?我怎样才能正确转换它们?

已添加

这是接收数据并将其写入其结构中的数组的回调函数:


static void Handler_Real(string szCode)
{
    string code          = REAL_TR.GetData("Out", "sym");
    string currentType   = REAL_TR.GetData("Out", "cg");
    DateTime currentTime = DateTime.Now;

    if (dic_codeToTRstructCounter[code] == arraySize)
    {
        dic_codeToTRstructCounter[code] = 0;

        using (var fileStream = new FileStream(dic_codeToTRfilename[code], FileMode.Append))
        {
            Serializer.Serialize(fileStream, dic_codeToTRstruct[code]);
        }
    }

    dic_codeToTRstruct[code].arr_currentIndex[dic_codeToTRstructCounter[code]]  = dic_codeToTRCounter[code];
    dic_codeToTRstruct[code].arr_currentType[dic_codeToTRstructCounter[code]]   = currentType;
    dic_codeToTRstruct[code].arr_currentTime[dic_codeToTRstructCounter[code]]   = currentTime;

    dic_codeToTRstructCounter[code] += 1;
    dic_codeToTRCounter[code] += 1;
}

这是我在 C# 中读取文件的方式,效果很好。

using (var fileStream = new FileStream("file.bin", FileMode.Open))
{
    struct_tick str = Serializer.Deserialize<struct_tick>(fileStream);

    for (int startNum = 0; startNum < str.arr_currentIndex.Length; startNum++)
    {
        string str_print = string.Format("{0}, {1}, {3}", str.arr_currentIndex[startNum],
                                                          str.arr_currentType[startNum],
                                                          str.arr_currentTime[startNum].ToString("HH:mm:ss.fffffff"));
        listBox1.Items.Add(str_print);
    }
}

这就是我在 Python

中读取同一个文件的方式
def convert_TRtoArray(filename):
    struct = struct_pb2.struct_TR()
    with open(filename, "rb") as f:
        struct.ParseFromString(f.read())

    lst_currentIndex  = [  indexNum for indexNum in struct.arr_currentIndex  ]
    lst_currentType   = [  type for type  in struct.arr_currentType ]
    lst_currentTime   = [  time for time in struct.arr_currentTime ]

而当我检查lst_currentTime时,虽然C#程序已经接收了2个小时的数据,但数值如上。

在像 Protobuf 这样的二进制可序列化流中使用 DateTime 不是好的做法。最好将 DateTime 值转换为其他内容,例如纪元时间甚至字符串,然后对纪元时间或字符串值使用序列化。否则,您将无法正确恢复该字段的内容。

最简单的方法是使用:

[ProtoMember(..., DataFormat = DataFormat.WellKnown)]

关于您的 DateTime / TimeSpan 用法;这使得 protobuf-net 使用 "well known" 时间戳和持续时间布局而不是默认格式。它不自动执行此操作的原因是当 protobuf-net 第一次必须为它们决定 some 布局时,它们 (timestamp/duration) 并不存在。

但是请注意,添加此内容是一项重大更改:布局不兼容。

如果您需要使用旧布局,它们是 described/defined in bcl.proto

或者,在 protobuf-net v3 中,如果您愿意,时间戳/持续时间有明确的类型。


鉴于讨论(评论),坦率地说,您似乎没有正确填充数组,或者在上下文之间泄漏了该数组。在本地测试,它看起来不错 - 下面的演示显示它工作(使用你的 struct_tick),包括显示 seconds/nanos 看起来正确的二进制数据的细分.

using ProtoBuf;
using System;
using System.IO;

class Program
{

    static void Main()
    {
        var payload = new struct_tick(2, 2, 2);
        payload.arr_currentIndex[0] = 12;
        payload.arr_currentIndex[1] = 14;
        payload.arr_currentType[0] = "abc";
        payload.arr_currentType[1] = "def";
        payload.arr_currentTime[0] = new DateTime(2019, 11, 12, 21, 59, 39, 745, DateTimeKind.Utc);
        payload.arr_currentTime[1] = new DateTime(2019, 11, 12, 23, 38, 50, 385, DateTimeKind.Utc);

        var ms = new MemoryStream();
        Serializer.Serialize(ms, payload);
        var hex = BitConverter.ToString(ms.GetBuffer(), 0, (int)ms.Length);
        Console.WriteLine(hex);
        ms.Position = 0;
        var clone = Serializer.Deserialize<struct_tick>(ms);
        foreach(var time in clone.arr_currentTime)
            Console.WriteLine(time); // writes 12/11/2019 21:59:39 and 12/11/2019 23:38:50

        // hex is 08-0C-08-0E-12-03-61-62-63-12-03-64-65-66-
        //        1A-0C-08-CB-D6-AC-EE-05-10-C0-98-9F-E3-02-
        //        1A-0C-08-8A-85-AD-EE-05-10-C0-C4-CA-B7-01
        // 08-0C = field 1, 12
        // 08-0E = field 1, 14
        // 12-03-61-62-63 = field 2, "abc"
        // 12-03-64-65-66 = field 2, "def"
        // 1A-OC = field 3, length 12
        //    08-CB-D6-AC-EE-05 = field 1, 1573595979 = seconds
        //    10-C0-98-9F-E3-02 = field 2, 745000000 = nanos
        // 1A-OC = field 3, length 12
        //    08-8A-85-AD-EE-05 = field 1, 1573601930 = seconds
        //    10-C0-C4-CA-B7-01 = field 2, 385000000 = nanos
    }
}

[ProtoContract]
public struct struct_tick
{
    [ProtoMember(1)]
    public uint[] arr_currentIndex;

    [ProtoMember(2)]
    public string[] arr_currentType;

    [ProtoMember(3, DataFormat = DataFormat.WellKnown)]
    public DateTime[] arr_currentTime;   // <- for DateTime.Now objects

    public struct_tick(byte size_index, byte size_type, byte size_time)
    {
        arr_currentIndex = new uint[size_index];
        arr_currentType = new string[size_type];
        arr_currentTime = new DateTime[size_time];
    }
}