View Issue Details

IDProjectCategoryView StatusLast Update
0035307FPCRTLpublic2019-05-08 18:57
ReporterOndrej PokornyAssigned ToMichael Van Canneyt 
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version3.3.1Product Build 
Target VersionFixed in Version3.3.1 
Summary0035307: [Feature]: DateToISO8601 is missing
DescriptionDateToISO8601 is missing in DateUtils.

Information: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.DateUtils.DateToISO8601

DateToISO8601 is used in JSON or in web applications, that expect ISO8601 format with timezone information.
TagsNo tags attached.
Fixed in Revision41958.
FPCOldBugId
FPCTarget3.2.0
Attached Files

Activities

Ondrej Pokorny

2019-04-01 18:17

reporter   ~0115167

(ISO8601ToDate and TryISO8601ToDate from the same pot are missing as well.)

Ondrej Pokorny

2019-04-02 07:52

reporter   ~0115179

Last edited: 2019-04-02 07:52

View 2 revisions

Here is DateToISO8601:

function DateToISO8601(const ADate: TDateTime; AInputIsUTC: Boolean = True): string;
const
  FmtUTC: string = 'yyyy''-''mm''-''dd''T''hh'':''nn'':''ss''.''zzz''Z''';
  FmtOffset: string = '%.02d:%.02d';
  Sign: array[Boolean] of Char = ('+', '-');
var
  Offset: Integer;
begin
  Result := FormatDateTime(FmtUTC, ADate);
  if not AInputIsUTC then
  begin
    Offset := GetLocalTimeOffset;
    if Offset<>0 then
    begin
      Result[Length(Result)] := Sign[Offset>0];
      Offset := Abs(Offset);
      Result += Format(FmtOffset, [Offset div MinsPerHour, Offset mod MinsPerHour]);
    end;
  end;
end;

Michael Van Canneyt

2019-04-28 13:48

administrator   ~0115872

Added slightly adapted, and added inverse functions

Function ISO8601ToDate(const DateString: string; ReturnUTC : Boolean): TDateTime;
Function ISO8601ToDateDef(const DateString: string; ReturnUTC : Boolean; aDefault : TDateTime): TDateTime;
Function TryISO8601ToDate(const DateString: string; ReturnUTC : Boolean;out ADateTime: TDateTime) : Boolean;

Ondrej Pokorny

2019-04-28 14:39

reporter   ~0115876

Thank you very much for implementing the functions!

There is however a small issue: ScanDatetime is not the right function to use in TryISO8601ToDate - it raises an exception in case of an invalid input.

program ISODate;
uses
  SysUtils, DateUtils;
var
  D: TDateTime;
begin
  if TryISO8601ToDate('xyz', True, D) then
    Writeln(D)
  else
    WriteLn('error');
end.

Ondrej Pokorny

2019-04-28 15:00

reporter   ~0115877

For your information: I use the following methods to convert string to ISO date/time. You may use them in FPC if you wish - but you have to modify ISOTryStrToDateTime to support the time zone shift (which I haven't implemented yet). I have planned to implement the timezone shift as well but I haven't done it yet.

My functions support the different formats of the ISO standard (with the date/time separators "-"/":" and without them).

function ISOTryStrToDate(const aString: string; var outDate: TDateTime): Boolean;
var
  xYear, xMonth, xDay: Integer;
begin
  case Length(aString) of
    8: Result :=
          TryStrToInt(Copy(aString, 1, 4), xYear) and
          TryStrToInt(Copy(aString, 5, 2), xMonth) and
          TryStrToInt(Copy(aString, 7, 2), xDay) and
          TryEncodeDate(xYear, xMonth, xDay, outDate);
    10: Result :=
          TryStrToInt(Copy(aString, 1, 4), xYear) and
          TryStrToInt(Copy(aString, 6, 2), xMonth) and
          TryStrToInt(Copy(aString, 9, 2), xDay) and
          TryEncodeDate(xYear, xMonth, xDay, outDate);
  else
    Result := False;
  end;

  if not Result then
    outDate := 0;
end;

function ISOTryStrToDateTime(const aString: string; var outDateTime: TDateTime): Boolean;
var
  xLength: Integer;
  xDate, xTime: TDateTime;
begin
  xLength := Length(aString);
  if (xLength>11) and CharInSet(aString[11], [' ', 'T']) then
  begin
    Result := ISOTryStrToDate(Copy(aString, 1, 10), xDate{%H-})
      and ISOTryStrToTime(Copy(aString, 12, High(Integer)), xTime{%H-});
  end else
  if (xLength>9) and CharInSet(aString[9], [' ', 'T']) then
  begin
    Result := ISOTryStrToDate(Copy(aString, 1, 8), xDate)
      and ISOTryStrToTime(Copy(aString, 10, High(Integer)), xTime);
  end else
    Result := False;

  if Result then
    outDateTime := xDate+xTime
  else
    outDateTime := 0;
end;

function ISOTryStrToTime(const aString: string; var outTime: TDateTime): Boolean;
var
  xHour, xMinute, xSecond, xMillisecond, xLength: Integer;
begin
  Result := True;
  xLength := Length(aString);
  if (xLength>0) and (aString[xLength] = 'Z') then
  begin
    Dec(xLength);
  end else
  if (xLength>6) and CharInSet(aString[xLength-5], ['+', '-']) then
  begin
    Result :=
      TryStrToInt(Copy(aString, xLength-4, 2), xHour) and
      (aString[xLength-2] = ':') and
      TryStrToInt(Copy(aString, xLength-1, 2), xMinute);
    Dec(xLength, 6);
  end else
  if (xLength>5) and CharInSet(aString[xLength-4], ['+', '-']) then
  begin
    Result :=
      TryStrToInt(Copy(aString, xLength-3, 2), xHour) and
      TryStrToInt(Copy(aString, xLength-1, 2), xMinute);
    Dec(xLength, 5);
  end else
  if (xLength>3) and CharInSet(aString[xLength-2], ['+', '-']) then
  begin
    Result :=
      TryStrToInt(Copy(aString, xLength-1, 2), xHour);
    Dec(xLength, 3);
  end;
  if not Result then
  begin
    outTime := 0;
    Exit;
  end;

  case xLength of
    2: Result :=
          TryStrToInt(aString, xHour) and
          TryEncodeTime(xHour, 0, 0, 0, outTime);
    4: Result :=
          TryStrToInt(Copy(aString, 1, 2), xHour) and
          TryStrToInt(Copy(aString, 3, 2), xMinute) and
          TryEncodeTime(xHour, xMinute, 0, 0, outTime);
    5: Result :=
          TryStrToInt(Copy(aString, 1, 2), xHour) and
          (aString[3] = ':') and
          TryStrToInt(Copy(aString, 4, 2), xMinute) and
          TryEncodeTime(xHour, xMinute, 0, 0, outTime);
    6: Result :=
          TryStrToInt(Copy(aString, 1, 2), xHour) and
          TryStrToInt(Copy(aString, 3, 2), xMinute) and
          TryStrToInt(Copy(aString, 5, 2), xSecond) and
          TryEncodeTime(xHour, xMinute, xSecond, 0, outTime);
    8: Result :=
          TryStrToInt(Copy(aString, 1, 2), xHour) and
          (aString[3] = ':') and
          TryStrToInt(Copy(aString, 4, 2), xMinute) and
          (aString[6] = ':') and
          TryStrToInt(Copy(aString, 7, 2), xSecond) and
          TryEncodeTime(xHour, xMinute, xSecond, 0, outTime);
    10: Result :=
          TryStrToInt(Copy(aString, 1, 2), xHour) and
          TryStrToInt(Copy(aString, 3, 2), xMinute) and
          TryStrToInt(Copy(aString, 5, 2), xSecond) and
          (aString[7] = '.') and
          TryStrToInt(Copy(aString, 8, 3), xMillisecond) and
          TryEncodeTime(xHour, xMinute, xSecond, xMillisecond, outTime);
    12: Result :=
          TryStrToInt(Copy(aString, 1, 2), xHour) and
          (aString[3] = ':') and
          TryStrToInt(Copy(aString, 4, 2), xMinute) and
          (aString[6] = ':') and
          TryStrToInt(Copy(aString, 7, 2), xSecond) and
          (aString[9] = '.') and
          TryStrToInt(Copy(aString, 10, 3), xMillisecond) and
          TryEncodeTime(xHour, xMinute, xSecond, xMillisecond, outTime);
  else
    Result := False;
  end;

  if not Result then
    outTime := 0;
end;

Michael Van Canneyt

2019-04-28 19:16

administrator   ~0115880

You're right, I overlooked the exceptions in ScanDateTime :(
I ised your functions, added TZ handling. Result committed in 41958

Ondrej Pokorny

2019-05-08 18:57

reporter   ~0116082

Thank you! There is still one small issue left: 0035544

Issue History

Date Modified Username Field Change
2019-04-01 18:16 Ondrej Pokorny New Issue
2019-04-01 18:17 Ondrej Pokorny Note Added: 0115167
2019-04-01 18:39 Michael Van Canneyt Assigned To => Michael Van Canneyt
2019-04-01 18:39 Michael Van Canneyt Status new => assigned
2019-04-02 07:52 Ondrej Pokorny Note Added: 0115179
2019-04-02 07:52 Ondrej Pokorny Note Edited: 0115179 View Revisions
2019-04-28 13:48 Michael Van Canneyt Status assigned => resolved
2019-04-28 13:48 Michael Van Canneyt Resolution open => fixed
2019-04-28 13:48 Michael Van Canneyt Fixed in Version => 3.3.1
2019-04-28 13:48 Michael Van Canneyt Fixed in Revision => 41954
2019-04-28 13:48 Michael Van Canneyt Note Added: 0115872
2019-04-28 13:50 Michael Van Canneyt Target Version => 4.0.0
2019-04-28 13:52 Michael Van Canneyt Target Version 4.0.0 =>
2019-04-28 13:52 Michael Van Canneyt FPCTarget => 3.2.0
2019-04-28 14:39 Ondrej Pokorny Status resolved => feedback
2019-04-28 14:39 Ondrej Pokorny Resolution fixed => reopened
2019-04-28 14:39 Ondrej Pokorny Note Added: 0115876
2019-04-28 15:00 Ondrej Pokorny Note Added: 0115877
2019-04-28 15:00 Ondrej Pokorny Status feedback => assigned
2019-04-28 19:16 Michael Van Canneyt Status assigned => resolved
2019-04-28 19:16 Michael Van Canneyt Fixed in Revision 41954 => 41958.
2019-04-28 19:16 Michael Van Canneyt Note Added: 0115880
2019-04-28 22:37 Michael Van Canneyt Resolution reopened => fixed
2019-05-08 18:57 Ondrej Pokorny Status resolved => closed
2019-05-08 18:57 Ondrej Pokorny Note Added: 0116082