【问题标题】:How do I convert an ISO 8601 string to a Delphi TDate?如何将 ISO 8601 字符串转换为 Delphi TDate?
【发布时间】:2011-07-11 14:39:40
【问题描述】:

我可以使用以下方法轻松地将 Delphi TDate 转换为 ISO 8601 格式:

DateTimeToString(result, 'yyyy-mm-dd', myDate);

进行逆转换的惯用方法是什么? StringToDateTime() 似乎不存在。

显然,我可以通过手动解析字符串并对结果进行编码来以“硬”的方式做到这一点,但这似乎是一个糟糕的选择。

【问题讨论】:

标签: delphi iso8601


【解决方案1】:

为什么要重新发明轮子?

XML 使用 ISO 8601 进行日期和日期时间存储。

自 Delphi 6 以来,Delphi 已在 XSBuiltIns 单元中内置支持。

This answer explains how 用于 DateTime,这仅用于使用 TXSDate 类的 Date:

with TXSDate.Create() do
  try
    AsDate := Date; // convert from TDateTime
    DateString := NativeToXS; // convert to WideString
  finally
    Free;
  end;

with TXSDate.Create() do
  try
    XSToNative(DateString); // convert from WideString
    Date := AsDate; // convert to TDateTime
  finally
    Free;
  end;

【讨论】:

  • 使用 XSBuiltins; XMLTimeToDateTime(str, True); - 也可以。
  • 好点。这适用于 Delphi 7 及更高版本。但请注意,它与上面的代码在内部使用 TXSDateTime 的功能相同,因此它也包含时间部分。
【解决方案2】:

从 XE8 开始,使用 dateutils.pas 中的 ISO8601ToDate(和 DateToISO8601)。

http://docwiki.embarcadero.com/Libraries/XE8/en/System.DateUtils.ISO8601ToDate

【讨论】:

    【解决方案3】:

    我认为这应该可行...文档说这些方法的重载版本用于线程,但它可以方便地指定您当时希望使用的格式设置。

    Function ISO8601ToDateTime(Value: String):TDateTime;
    var
        FormatSettings: TFormatSettings;
    begin
        GetLocaleFormatSettings(GetThreadLocale, FormatSettings);
        FormatSettings.DateSeparator := '-';
        FormatSettings.ShortDateFormat := 'yyyy-MM-dd';
        Result := StrToDate(Value, FormatSettings);
    end;
    

    您当然可以使用具有等效功能的 StrToDateDef 和 TryStrToDate 编写它的变体

    【讨论】:

    • 您可能还想使用系统默认值初始化格式设置。取决于您是否要将其用于解析日期以外的其他用途:GetLocaleFormatSettings(LOCALE_SYSTEM_DEFAULT, FormatSettings); 使用系统默认值填充 FormatSettings 记录。
    • @Roald,谢谢.. 我只是在上面运行了几个测试!我马上更新它
    • 一定要使用“线程安全”的重载版本,否则如果您使用 DateToStr、FormatDateTime 和 'c' 或 'ddddd' 或其他任何使用 ShortDateFormat 的东西,您将更改应用程序显示日期的方式.
    【解决方案4】:

    您可以在我们的SynCommons unit 中找到 Iso-8601 转换例程。

    它已经针对速度进行了深度优化,因此它比 DateTimeToString() 等函数快得多,但当然,代码更难遵循。 ;)

    procedure Iso8601ToDateTimePUTF8CharVar(P: PUTF8Char; L: integer; var result: TDateTime); 
    var i: integer;
        B: cardinal;
        Y,M,D, H,MI,SS: cardinal;
    // we expect 'YYYYMMDDThhmmss' format but we handle also 'YYYY-MM-DD hh:mm:ss'
    begin
      result := 0;
      if P=nil then
        exit;
      if L=0 then
        L := StrLen(P);
      if L<4 then
        exit; // we need 'YYYY' at least
      if P[0]='T' then
        dec(P,8) else begin
        B := ConvertHexToBin[ord(P[0])]; // first digit
        if B>9 then exit else Y := B; // fast check '0'..'9'
        for i := 1 to 3 do begin
          B := ConvertHexToBin[ord(P[i])]; // 3 other digits
          if B>9 then exit else Y := Y*10+B;
        end;
        if P[4] in ['-','/'] then begin inc(P); dec(L); end; // allow YYYY-MM-DD
        D := 1;
        if L>=6 then begin // YYYYMM
          M := ord(P[4])*10+ord(P[5])-(48+480);
          if (M=0) or (M>12) then exit;
          if P[6] in ['-','/'] then begin inc(P); dec(L); end; // allow YYYY-MM-DD
          if L>=8 then begin // YYYYMMDD
            D := ord(P[6])*10+ord(P[7])-(48+480);
            if (D=0) or (D>MonthDays[true][M]) then exit; // worse is leap year=true
          end;
        end else
          M := 1;
        if M>2 then // inlined EncodeDate(Y,M,D)
          dec(M,3) else
        if M>0 then begin
          inc(M,9);
          dec(Y);
        end;
        with Div100(Y) do
          result := (146097*YDiv100) shr 2 + (1461*YMod100) shr 2 +
                (153*M+2) div 5+D-693900;
        if (L<15) or not(P[8] in [' ','T']) then
          exit;
      end;
      H := ord(P[9])*10+ord(P[10])-(48+480);
      if P[11]=':' then inc(P); // allow hh:mm:ss
      MI := ord(P[11])*10+ord(P[12])-(48+480);
      if P[13]=':' then inc(P); // allow hh:mm:ss
      SS := ord(P[13])*10+ord(P[14])-(48+480);
      if (H<24) and (MI<60) and (SS<60) then // inlined EncodeTime()
        result := result + (H * (MinsPerHour * SecsPerMin * MSecsPerSec) +
                 MI * (SecsPerMin * MSecsPerSec) + SS * MSecsPerSec) / MSecsPerDay;
    end;
    

    这能够处理从 UTF-8 编码缓冲区到TDateTime 的非常快速的转换。对于所有常量依赖项,请查看单元源代码。

    【讨论】:

    • 典型。无论如何我都在使用突触,但从未意识到它被包括在内:-)
    • 此功能不完全符合 ISO8601。规范说使用“T”作为日期和时间字符串之间的分隔符。只有在双方同意的情况下才允许省略它。其次,不支持字符串末尾的时区指示,这是许多 Web 服务所要求的。
    【解决方案5】:

    为了获得更大的灵活性,您可以考虑Marco van de Voortscandate routine,它可以处理任何格式的字符串:

    var
      D: TDateTime;
    begin
      D := ScanDate('yyyy-mm-dd', '2011-07-11');
    

    请参阅添加到 FPC 的 final version (7kB .zip)。

    【讨论】:

    • 英文的定义不等于荷兰的“定义”。在这种情况下,荷兰语的“definitieve versie”最好翻译为“最终版本”。 “最终版本”更类似于说“终极”版本......再说一次,也许你是想说 :-))
    • 总是乐于帮助同胞(wo)men
    【解决方案6】:
    USES Soap.XSBuiltIns;
    ...
    Function XMLDateTimeToLocalDateTime(const Value: String): TDateTime;
    begin
      with TXSDateTime.Create do
      try
        XSToNative(Value);
        result := AsDateTime;
      finally
        Free;
      end;
    end;
    

    德尔福 XE3

    【讨论】:

      【解决方案7】:

      从XE6开始你可以使用函数System.DateUtils.ISO8601ToDate:

      uses
        System.DateUtils;
      var
        vDat: TDateTime;
      begin
        vDat := ISO8601ToDate('2018-03-26T11:01:35.867Z');
      end.
      

      【讨论】:

      • 最后一行的意思是“从 XE6 向上”吗?
      • 此外,请参阅Roddy's answer
      猜你喜欢
      • 1970-01-01
      • 2011-01-13
      • 2011-07-19
      • 1970-01-01
      • 1970-01-01
      • 2015-11-16
      • 1970-01-01
      • 1970-01-01
      • 2014-07-29
      相关资源
      最近更新 更多