How do I convert an ISO 8601 string to a Delphi TDate?
From XE8 onwards, use ISO8601ToDate
(and DateToISO8601
) from dateutils.pas
.
http://docwiki.embarcadero.com/Libraries/XE8/en/System.DateUtils.ISO8601ToDate
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;
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
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.