如何在Delphi中正确执行扩展JSON$date,还是System.JSON中关于时区和UTC的错误?
我正在尝试使用TJSONObjectBuilder…AddPairs解析扩展JSON。我的JSON包含一个$date,我需要它在Utc中用于MongoDB。但不知何故,时区被打破了,不管我的输入是否已经是Utc如何在Delphi中正确执行扩展JSON$date,还是System.JSON中关于时区和UTC的错误?,json,mongodb,delphi,utc,delphi-10.2-tokyo,Json,Mongodb,Delphi,Utc,Delphi 10.2 Tokyo,我正在尝试使用TJSONObjectBuilder…AddPairs解析扩展JSON。我的JSON包含一个$date,我需要它在Utc中用于MongoDB。但不知何故,时区被打破了,不管我的输入是否已经是Utc Input : {"Zulu":{"$date":"2019-01-01T00:00:00.000Z"},"Utc+1":{"$date":"2019-01-01T01:00:00.000+01:00"}} Output: {"Zulu":{"$date":"2019-01-01T01:
Input : {"Zulu":{"$date":"2019-01-01T00:00:00.000Z"},"Utc+1":{"$date":"2019-01-01T01:00:00.000+01:00"}}
Output: {"Zulu":{"$date":"2019-01-01T01:00:00.000Z"},"Utc+1":{"$date":"2019-01-01T01:00:00.000Z"}}
^ ^
如果没有TJsonDateTimeZoneHandling.Utc,这是正确的,但这对我没有帮助,因为我需要Utc中的结果:
Output: {"Zulu":{"$date":"2019-01-01T01:00:00.000+01:00"},"Utc+1":{"$date":"2019-01-01T01:00:00.000+01:00"}}
下面是我的最简单代码:
program SystemJsonDateTest;
{$APPTYPE CONSOLE}
uses
System.Classes, System.JSON.Types, System.JSON.Writers, System.JSON.Builders;
var
StringWriter: TStringWriter;
JsonWriter: TJsonTextWriter;
Builder: TJSONObjectBuilder;
begin
StringWriter:= TStringWriter.Create;
JsonWriter:= TJsonTextWriter.Create(StringWriter);
JsonWriter.ExtendedJsonMode:= TJsonExtendedJsonMode.StrictMode;
JsonWriter.DateTimeZoneHandling:= TJsonDateTimeZoneHandling.Utc;
TJSONObjectBuilder.Create(JsonWriter)
.BeginObject
.AddPairs('{"Zulu":{"$date":"2019-01-01T00:00:00.000Z"},'
+ '"Utc+1":{"$date":"2019-01-01T01:00:00.000+01:00"},'
+ '"Unix":{"$date":1546300800000}}')
.EndObject
.Free;
JsonWriter.Free;
WriteLn(StringWriter.ToString);
StringWriter.Free;
ReadLn;
end.
背景:我正在使用TMongoDocument.AsJSON,发现了这种行为,并试图用最少的代码和不引用MongoDB组件的情况下重现它。如果我正在做一些奇怪的事情或者演示可以更加简化,请评论
在该MongoDocument中,使用了TBsonWriter,但它显示了相同的问题:
Stream:= TFileStream.Create('file.bson', fmCreate);
BsonWriter:= TBsonWriter.Create(Stream);
TJSONObjectBuilder.Create(BsonWriter).BeginObject.AddPairs(//see above
我知道,这是一段很长的文字——如果你忘了这个问题,它就在标题中 MongoDB客户端可能支持JSON输入中日期字段的$date扩展语法中的区域,即使Delphi客户端似乎忽略了它,但MongoDB服务器不会处理其BSON存储中的区域 事实上,日期值存储为UTC—它们甚至在BSON格式中被称为UTC日期,并存储为Unix毫秒数的Int64: BSON Date是一个64位整数,表示自Unix纪元1970年1月1日以来的毫秒数。这导致了过去和未来约2.9亿年的代表性日期范围 因此,Utc+1和Zulu字段都将包含完全相同的Utc时间戳,即使在客户端库正确转换时区之后也是如此 因此,您最好只向MongoDB发送UTC日期,并在客户端进行转换。即使正确转换,在所有情况下都会丢失区域信息,因为它将存储为UTC。不要使用ISO-8601文本进行传输,只使用UnixtTime值作为整数:
function DateTimeToUnixMSTime(const AValue: TDateTime): Int64;
begin
result := Round((AValue - UnixDateDelta) * MSecsPerDay);
end;
顺便说一句,最好只在任何类型的数据库中使用UTC日期,然后使用即时转换到当前用户本地显示/报告,并将本地区域存储在单独的字段中(如果确实需要),作为文本标识符,或者,作为浮点偏差,在几天内可能更方便-请注意,区域偏差不是必需的整数,例如,对于阿富汗。是的,这是一个错误,在10.3中已修复
在unit System.JSON.Builders中,TJSONCollectionBuilder.TBaseCollection.WriteJSON创建一个TJsonTextReader,默认DateTimeZoneHandling=Local,这意味着任何日期时间都转换为Local
但在System.JSON.Writers、TJsonTextWriter.WriteValueValue:TDateTime中,当DateTimeZoneHandling=Utc时,不包含时区的DateTime值应为Utc,并被解释为Utc
因此,TJSONCollectionBuilder需要DateTimeZoneHandling=Local的编写器,这使得无法以正确的Utc获取输出
…调试完之后,我就知道用谷歌搜索是为了什么:
TMongoDocument和TJSONCollectionBuilder错误地解析ISO日期
数据,数据\FireDAC,RTL,RTL\Delphi RSP-17046
[FireDAC,MongoDB]
[TJSONCollectionBuilder.TBaseCollection.WriteJSON]日期始终为
如果值在文本模式数据中更新,则转换为本地时区,
数据\FireDAC,RTL\Delphi RSP-20571
谢谢-使用UnixTime值作为整数是一种有效的解决方法,对我有帮助。不过,你主要是回答我的背景资料,而不是我的问题。我的例子根本不是关于MongoDB或BSON的。使用UTC是我所知道和想要的-但当我这样做时,会发生一些错误的时区转换,无论是在我的最小示例中,还是在我使用MongoDoc.AsJSON:=MyJson的真实案例中;包含UTC格式的$日期。我调试了它,认为它是TJSONObjectBuilder中的一个bug。主要的问题是,这是真的吗?我已经有了一个我更喜欢的解决方法,因为我希望能够轻松地破译我的JSON。在将其发送到AsJSON之前,我将日期替换为:Value:=DateToISO8601ISO8601ToDateValue,True,False;-这会将其转换为错误的值,因此在出现错误后它会变得正确。为了完整性起见,我尝试了DateUtils.DateTimeToUnix*1000解决方案{$date:157550800000},但它会导致完全相同的1小时差异,因此我可以将其添加到我的演示代码中…因此尝试使用另一个MongoDB库,如我们的开源,这对日期没有问题。