TXSDateTime 错误"本地时间位于 DST 之前的缺失时间段内"



我们将本地日期(无时间部分)转换为需要 UTC 日期时间字符串的外部系统,方法是将TTimeZone.Local.UTCOffset(现在为 2 小时)添加到 TDateTime.
这在我们切换到 DST 的夜晚(02:00)失败。

来自System.RTLConst的错误:

SLocalTimeInvalid = 'The given "%s" local time is invalid (situated within the missing period prior to DST).';

发生于System.DateUtils

function TTimeZone.GetUtcOffsetInSeconds(const ADateTime: TDateTime; const ForceDaylight: Boolean): Int64;
var
LOffset, LDSTSave: Int64;
LType: TLocalTimeType;
begin
{ Obtain the information we require }
DoGetOffsetsAndType(ADateTime, LOffset, LDSTSave, LType);
{ Select the proper offset }
if (LType = lttInvalid) then
raise ELocalTimeInvalid.CreateResFmt(@SLocalTimeInvalid, [DateTimeToStr(ADateTime)])
else if (LType = lttDaylight) or ((LType = lttAmbiguous) and ForceDaylight) then
Result := LOffset + LDSTSave
else
Result := LOffset;
end;

要重现的代码

function DateTime2UTCString(ADateTime: TDateTime): String;
var XSD: TXSDateTime;
begin
XSD := TXSDateTime.Create;
try
try
XSD.AsDateTime := ADateTime;
Result := XSD.NativeToXS;
except
on E:Exception do
Result := E.Message;
end;
finally
XSD.Free;
end;
end;
function Date2UTCString(ADateTime: TDateTime): String;
// Input is guaranteed to have no time fraction
begin
ADateTime := ADateTime + TTimeZone.Local.UTCOffset;
Result := DateTime2UTCString(ADateTime);
end;
procedure TFrmUTCandDST.Button1Click(Sender: TObject);
var
lDT: TDateTime;
l  : integer;
begin
lDT := EncodeDate(2016,3,25);
for l := 0 to 2 do
begin
lDT := lDT +1;
Memo1.Lines.Add(DateToStr(lDT) + ' -> ' + Date2UTCString(lDT));
end;
end;

(不要忘记使用SOAP.XSBuiltIns, System.DateUtils, System.TimeSpan)。

输出:

26-3-2016 -> 2016-03-26T02:00:00.000+01:00
27-3-2016 -> The given "27-3-2016 2:00:00" local time is invalid (situated within the missing period prior to DST).
28-3-2016 -> 2016-03-28T02:00:00.000+02:00

我怎样才能优雅地规避这一点?我可以使用TTimeZone.Local.IsInvalidTime(ADateTime)来检测无效日期,26-3-2016 2:00:00是错误的(这正是我们迁移到 DST 的时间),而不是27-3-2016 2:00:00- 所以我不知道如何在"无效"日期的情况下进行调整。

unitSystem.DateUtils.pas中存在错误(afaik 在 10.1 中仍然存在)。

函数 AdjustDateTime 首先将日期和时间处理为本地时间,然后尝试将偏移量放入其中。由于在夏令时期间有一个"缺失小时"(在中欧的情况下是 26.03.2017),因此在凌晨 1:59:59 之后,您有 3:00:00 A.M.
如果您不小心使用了这个时间段(如 2:17:35),您将获得异常。

这在其他函数中也存在。

重现异常的简单代码 (C++):

ShowMessage(ISO8601ToDate("2017-03-26T02:22:50.000Z",false));

但是这个运行正常:

ShowMessage(ISO8601ToDate("2017-03-26T02:22:50.000Z",true));`

现在要避免异常,请使用 XSD。AsUTCDateTime,然后应用本地偏移量。 C ++中的示例:

TTimeZone * localTz = TTimeZone::Local;
TDateTime TimeSomething = localTz->ToLocalTime(XSD->AsUTCDateTime);

在您的情况下,本地时间确实无效(没有"2:00"), 或者在您尝试将 UTC 时间视为本地时间的某个地方,这当然是无效的。解决这个问题,你就会解决你的问题。

我怎样才能优雅地规避这一点?我可以使用 TTimeZone.Local.IsInvalidTime(ADateTime) 来检测无效日期,但 26-3-2016 2:00:00 将是错误的(这正是我们移动到 DST 的时间),而不是 27-3-2016 2:00:00 - 所以我不知道如何在"无效"日期的情况下进行调整。

此外,我认为您错过了在 2016年我们在 27.03 的 2:00 移至 DST,但今年在 26-03,因此 27-3-2016 2:00:00 是完全无效的日期:)

正如@Vancalar所说,在 DST 转换附近从 UTC 转换为本地时间时,ISO8601ToDate中存在错误。此错误在 Rio 10.3.3 中仍然存在。

一个简单的解决方法是避免ISO8601ToDate的时区转换,并让Microsoft这样做。 也就是说,将false值替换为true并随后调用UTCToTZLocalTime,如以下 Pascal 代码段所示:

// Get UTC datetime from ISO8601 string
datetime := ISO8601ToDate(ISO8601UTCstring, true);
// Convert to local time w/DST conversion
datetime := UTCToTZLocalTime(zoneinfo,datetime);  

最新更新