如何使用 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];
}
}
我使用 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];
}
}