Search code examples
delphidelphi-11-alexandriatdatetime

How to convert a formatted date/time string without separators to a TDateTime?


I have a stringified date/time in an odd format - yyyymmddhhnnss - and while I know how to convert a TDateTime to this format with FormatDateTime(), I have troubles converting it back. I tried using StrToDateTime() with the TFormatOptions parameter, but you obviously cannot set it to have no separator (you either give some separator character, or the default system setting is used).

What are some ways to handle this? I know I could parse the string manually, I can do that, but are there some more efficient ways that I'm missing?

I'm using Delphi 11.


Solution

  • Every time you have a particular datetime string format you want to parse, best practice is (still) to write your own parser.

    Then you know it will be 100% correct and bug free (yes, a simple task like this is very much possible to get 100% bug free), today and in every future Delphi RTL version, and on every system and user locale.

    Best practice also requires you to write not only the MyStringToDateTime function, but also the TryMyStringToDateTime and MyStringToDateTimeDef functions.

    Here's how I'd do it (uses Math, DateUtils):

    function TryMyStringToDateTime(const S: string; out ADateTime: TDateTime): Boolean;
    begin
    
      if S.Length <> 14 then
        Exit(False);
    
      var LYearStr := Copy(S, 1, 4);
      var LMonthStr := Copy(S, 5, 2);
      var LDayStr := Copy(S, 7, 2);
      var LHourStr := Copy(S, 9, 2);
      var LMinuteStr := Copy(S, 11, 2);
      var LSecondStr := Copy(S, 13, 2);
    
      var LYear, LMonth, LDay, LHour, LMinute, LSecond: Integer;
      if
        not
          (
            TryStrToInt(LYearStr, LYear)
              and
            TryStrToInt(LMonthStr, LMonth)
              and
            TryStrToInt(LDayStr, LDay)
              and
            TryStrToInt(LHourStr, LHour)
              and
            TryStrToInt(LMinuteStr, LMinute)
              and
            TryStrToInt(LSecondStr, LSecond)
          )
      then
        Exit(False);
    
      if not InRange(LYear, 1, 9999) then
        Exit(False);
    
      if not InRange(LMonth, 1, 12) then
        Exit(False);
    
      if not InRange(LDay, 1, DaysInAMonth(LYear, LMonth)) then
        Exit(False);
    
      if not InRange(LHour, 0, 23) then
        Exit(False);
    
      if not InRange(LMinute, 0, 59) then
        Exit(False);
    
      if not InRange(LSecond, 0, 59) then
        Exit(False);
    
      ADateTime := EncodeDateTime(LYear, LMonth, LDay, LHour, LMinute, LSecond, 0);
      Result := True;
    
    end;
    
    function MyStringToDateTime(const S: string): TDateTime;
    begin
      if not TryMyStringToDateTime(S, Result) then
        raise EConvertError.CreateFmt('Invalid datetime string: "%s"', [S]);
    end;
    
    function MyStringToDateTimeDef(const S: string; const ADefault: TDateTime): TDateTime;
    begin
      if not TryMyStringToDateTime(S, Result) then
        Result := ADefault;
    end;