How do I convert an ISO 8601 string to a Delphi TD

2020-02-01 00:26发布

I can convert a Delphi TDate to ISO 8601 format easily using this:

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

What's the idiomatic way to do the inverse conversion? StringToDateTime() doesn't seem to exist.

Obviously I can do it the "hard" way by manually parsing the string and encoding the result, but that seems a poor choice.

7条回答
▲ chillily
2楼-- · 2020-02-01 00:59

You can find Iso-8601 conversion routines in our SynCommons unit.

It has been deeply optimized for speed, so it's much faster than the DateTimeToString() functions and such, but of course, code is more difficult to follow. ;)

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;

This is able to handle a very fast conversion from an UTF-8 encoded buffer into a TDateTime. For all constants dependencies, check the unit source code.

查看更多
smile是对你的礼貌
3楼-- · 2020-02-01 00:59

For more flexibility, you could consider Marco van de Voort's scandate routine which handles your string in any format:

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

See final version (7kB .zip) as added to FPC.

查看更多
Bombasti
4楼-- · 2020-02-01 01:01

From XE8 onwards, use ISO8601ToDate (and DateToISO8601) from dateutils.pas.

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

查看更多
三岁会撩人
5楼-- · 2020-02-01 01:09

Starting with XE6 you can use function System.DateUtils.ISO8601ToDate:

uses
  System.DateUtils;
var
  vDat: TDateTime;
begin
  vDat := ISO8601ToDate('2018-03-26T11:01:35.867Z');
end.
查看更多
干净又极端
6楼-- · 2020-02-01 01:13

why re-invent the wheel?

XML uses ISO 8601 for date and date-time storage.

Delphi has had built-in support for that since Delphi 6 in the XSBuiltIns unit.

This answer explains how for DateTime, this is for Date only using the TXSDate class:

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;
查看更多
我欲成王,谁敢阻挡
7楼-- · 2020-02-01 01:20

I think this should work... the documentation says the overloaded version of these methods is for use in threads, but it can be handy for specifying the format settings you wish to use at the time.

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

You can of course write variants of this with StrToDateDef and TryStrToDate with equivalent functionality

查看更多
登录 后发表回答