如何在Delphi中正确执行扩展JSON$date,还是System.JSON中关于时区和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:

我正在尝试使用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: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库,如我们的开源,这对日期没有问题。