Как преобразовать строку ISO 8601 в Delphi TDate?

Я могу легко преобразовать формат Delphi TDate в формат ISO 8601, используя это:

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

Какой идиоматический способ сделать обратное преобразование? StringToDateTime() не существует.

Очевидно, я могу сделать это "жестким" способом, вручную проанализировав строку и закодировав результат, но это кажется плохим выбором.


person Roddy    schedule 11.07.2011    source источник
comment
возможный дубликат Преобразование строки в TDateTime на основе произвольного формат   -  person NGLN    schedule 28.03.2012


Ответы (7)


зачем заново изобретать велосипед?

XML использует ISO 8601 для хранения даты и времени.

Delphi имеет встроенную поддержку для этого, начиная с Delphi 6 в XSBuiltIns.

Этот ответ объясняет, как для DateTime это только для Date с использованием TXSDate класс:

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;
person Jeroen Wiert Pluimers    schedule 12.07.2011
comment
использует XSBuiltins; XMLTimeToDateTime(str, True); - тоже будет работать. - person T.S; 03.07.2018
comment
Хорошая точка зрения. Это работает с Delphi 7 и выше. Однако обратите внимание, что он делает то же самое, что и приведенный выше код с TXSDateTime внутри, поэтому он также включает временную часть. - person Jeroen Wiert Pluimers; 03.07.2018

Начиная с XE8, используйте ISO8601ToDateDateToISO8601) из dateutils.pas.

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

person Roddy    schedule 22.09.2015

Я думаю, это должно работать... в документации говорится, что перегруженная версия этих методов предназначена для использования в потоках, но она может быть полезна для указания параметров формата, которые вы хотите использовать в данный момент.

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 с эквивалентной функциональностью.

person James Barrass    schedule 11.07.2011
comment
Возможно, вы также захотите инициализировать настройки формата с системными значениями по умолчанию. В зависимости от того, собираетесь ли вы использовать его для чего-то другого, кроме парсинга дат: GetLocaleFormatSettings(LOCALE_SYSTEM_DEFAULT, FormatSettings); заполняет запись FormatSettings системными значениями по умолчанию. - person R-D; 11.07.2011
comment
@ Роальд, спасибо .. Я только что провел пару тестов! Я обновлю это через мгновение - person James Barrass; 11.07.2011
comment
Определенно используйте потокобезопасные перегруженные версии, в противном случае вы измените способ отображения дат в вашем приложении, если вы используете DateToStr или FormatDateTime с «c» или «ddddd» или что-либо еще, что использует ShortDateFormat. - person Gerry Coll; 12.07.2011

Процедуры преобразования Iso-8601 можно найти в нашем подразделе SynCommons.

Он был глубоко оптимизирован для скорости, поэтому он намного быстрее, чем функции 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. Для всех зависимостей констант проверьте исходный код модуля.

person Arnaud Bouchez    schedule 11.07.2011
comment
Типичный. Я все равно использую синапс, но никогда не знал, что это было включено :-) - person Roddy; 11.07.2011
comment
Однако эта функция не полностью совместима с ISO8601. Спецификация говорит использовать T в качестве разделителя между строками даты и времени. Отказ от него допускается только по обоюдному согласию. Во-вторых, нет поддержки указания часового пояса в конце строки, что требуется многим веб-сервисам. - person Anders E. Andersen; 25.01.2014

Для большей гибкости вы можете рассмотреть Марко ван де Воорта процедура сканирования, которая обрабатывает вашу строку в любом формате:

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

См. окончательную версию (7 КБ .zip), добавленную в FPC.

person NGLN    schedule 11.07.2011
comment
английский дефинитив не равен голландскому дефинитиву. В этом контексте голландский definitieve versie лучше переводить как окончательную версию. Окончательная версия больше похожа на окончательную версию... Опять же, возможно, вы хотели сказать это :-)) - person Marjan Venema; 11.07.2011
comment
Всегда рад помочь земляку(женщине)мужчине - person Marjan Venema; 11.07.2011

USES Soap.XSBuiltIns;
...
Function XMLDateTimeToLocalDateTime(const Value: String): TDateTime;
begin
  with TXSDateTime.Create do
  try
    XSToNative(Value);
    result := AsDateTime;
  finally
    Free;
  end;
end;

Делфи XE3

person Akella225    schedule 09.09.2015

Начиная с XE6 вы можете использовать функцию System.DateUtils.ISO8601ToDate:

uses
  System.DateUtils;
var
  vDat: TDateTime;
begin
  vDat := ISO8601ToDate('2018-03-26T11:01:35.867Z');
end.
person Marlon Nardi    schedule 26.03.2018
comment
Означает ли последняя строка Начиная с XE6 и выше? - person Uli Gerhardt; 26.03.2018
comment
Кроме того, см. ответ. - person Uli Gerhardt; 26.03.2018