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

时间:2019-11-12 15:27:10

标签: c# python datetime protobuf-net

我已经使用DateTime.Now序列化了一个C#protobuf-net对象数组,并使用其消息结构的原始文件(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日期时间对象?使我感到困惑的是文件的datetime数组中显示的内容。

>>> 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个小时的数据,但是这些值与上面的一样。

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

最简单的方法是使用:

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

您的DateTime / TimeSpan使用情况;这使得protobuf-net使用“众所周知的”时间戳和持续时间布局而不是默认格式。它没有自动执行此操作的原因是,当protobuf-net首次为它们确定 some 布局时,它们(时间戳/持续时间)就不存在了。

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

如果需要使用旧版式,则会在bcl.proto中对它们进行描述/定义。

或者,在protobuf-net v3中,您可以根据需要使用明确的时间戳/持续时间类型。


给出讨论(评论),坦率地说,您似乎没有正确填充数组,或者在上下文之间泄漏了该数组。在本地测试,看起来还不错-以下演示演示了它的工作原理(与您的struct_tick一起使用),其中包括二进制数据的细分,其中显示了看起来正确的秒/纳米。

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