TXSDateTime错误"位于DST之前的缺失期内的当地时间'

时间:2017-03-30 13:37:35

标签: delphi utc dst delphi-10-seattle

我们将本地日期(无时间部分)转换为期望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 - 所以我不知道如何调整以防万一无效的'日期。

2 个答案:

答案 0 :(得分:2)

bug in unit System.DateUtils.pas(afaik仍然存在于10.1中)。

函数AdjustDateTime首先将日期和时间作为本地时间处理,然后THEN尝试将偏移量放入其中。由于在夏令时期间有一个"缺少时间" (如果是中欧,那是26.03.2017),因此在下午1:59:59 A.M你已经在凌晨3点00分左右 如果你不小心使用了这段时间(比如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年我们在2点27.03转到夏令时,但是今年26-03,所以27-3-2016 2:00:00是完全无效的日期:)

答案 1 :(得分:0)

正如@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);