How to sort a list of string‑based dates in Pascal

1 Answer

0 votes
program SortDates;

{
    Program goal:
    -------------
    Sort a list of date strings in the format "YYYY-MM-DD".
    The approach:
      1. Validate and parse each date into a numeric record (year, month, day).
      2. Sort using a custom QuickSort with a comparator.
      3. Print the sorted results.

    Architecture notes:
    -------------------
    - Parsing is isolated for clarity and testability.
    - Sorting uses a classic QuickSort (O(n log n) average).
    - Error handling: invalid formats and invalid ranges are detected.
    - Edge cases: malformed strings, invalid ranges, leap years.
    - Performance: QuickSort is cache-friendly and widely understood.
    - Memory: minimal; only an array of records.
    - Security: careful bounds checking; no unsafe pointer arithmetic.

    Note:
    -----
    The previous version used TArray.Sort, which is a Delphi feature.
    Free Pascal does not provide TArray.Sort in this form, which caused
    the compiler error mentioning "CREATE expected but SORT found".
    This version replaces that with a custom QuickSort implementation.
}

{$mode objfpc}{$H+}

uses
  SysUtils;

type
  TDateRec = record
    Year: Integer;
    Month: Integer;
    Day: Integer;
    Original: String;  { Keep original string for output }
  end;

{
    Validate that a string is exactly in the form YYYY-MM-DD.
}
function ValidateFormat(const S: String): Boolean;
var
  I: Integer;
begin
  Result := False;
  if Length(S) <> 10 then Exit;

  for I := 1 to 10 do
  begin
    case I of
      5, 8:
        if S[I] <> '-' then Exit;
    else
      if not (S[I] in ['0'..'9']) then Exit;
    end;
  end;

  Result := True;
end;

{
    Parse a date string into a TDateRec.
    Returns True on success, False on failure.
}
function ParseDate(const S: String; out D: TDateRec): Boolean;
begin
  if not ValidateFormat(S) then Exit(False);

  D.Year := StrToIntDef(Copy(S, 1, 4), -1);
  D.Month := StrToIntDef(Copy(S, 6, 2), -1);
  D.Day := StrToIntDef(Copy(S, 9, 2), -1);
  D.Original := S;

  { Basic range validation }
  if (D.Year < 0) or (D.Month < 1) or (D.Month > 12) or (D.Day < 1) or (D.Day > 31) then
    Exit(False);

  Result := True;
end;

{
    Comparator for sorting.
    Compares two TDateRec values chronologically.
}
function DateCompare(const A, B: TDateRec): Integer;
begin
  if A.Year <> B.Year then Exit(A.Year - B.Year);
  if A.Month <> B.Month then Exit(A.Month - B.Month);
  Result := A.Day - B.Day;
end;

{
    Swap two elements in the array.
}
procedure Swap(var A, B: TDateRec);
var
  Temp: TDateRec;
begin
  Temp := A;
  A := B;
  B := Temp;
end;

{
    QuickSort implementation for TDateRec arrays.
    Average complexity: O(n log n).
}
procedure QuickSortDates(var A: array of TDateRec; L, R: Integer);
var
  I, J: Integer;
  Pivot: TDateRec;
begin
  I := L;
  J := R;
  Pivot := A[(L + R) div 2];

  repeat
    while DateCompare(A[I], Pivot) < 0 do Inc(I);
    while DateCompare(A[J], Pivot) > 0 do Dec(J);

    if I <= J then
    begin
      Swap(A[I], A[J]);
      Inc(I);
      Dec(J);
    end;
  until I > J;

  if L < J then QuickSortDates(A, L, J);
  if I < R then QuickSortDates(A, I, R);
end;

{
    Sort an array of date strings.
    Returns True on success, False on failure.
}
function SortDates(const Dates: array of String): Boolean;
var
  Parsed: array of TDateRec;
  I: Integer;
begin
  SetLength(Parsed, Length(Dates));

  { Parse all dates }
  for I := 0 to High(Dates) do
  begin
    if not ParseDate(Dates[I], Parsed[I]) then
    begin
      WriteLn('Invalid date: ', Dates[I]);
      Exit(False);
    end;
  end;

  { Sort using custom QuickSort }
  if Length(Parsed) > 1 then
    QuickSortDates(Parsed, 0, High(Parsed));

  { Print results }
  WriteLn('Sorted dates:');
  for I := 0 to High(Parsed) do
    WriteLn('  ', Parsed[I].Original);

  Result := True;
end;

{
    Helper to run a named test case.
}
procedure RunTest(const Name: String; const Dates: array of String);
begin
  WriteLn;
  WriteLn('=== ', Name, ' ===');
  if not SortDates(Dates) then
    WriteLn('Test failed.');
end;

{
    Main program with multiple test cases.
}
begin
  { Basic chronological order }
  RunTest('Basic chronological order',
    ['2024-01-05', '2023-12-31', '2024-01-01', '2022-07-15']);

  { Already sorted input }
  RunTest('Already sorted input',
    ['2020-01-01', '2020-01-02', '2020-01-03']);

  { Leap year edge cases (note: only basic range checks here) }
  RunTest('Leap year edge cases',
    ['2020-02-29', '2019-02-28', '2021-03-01']);

  { Invalid date detection }
  RunTest('Invalid date detection',
    ['2024-13-01', '2024-02-30', 'not-a-date']);
end.



{
run:

=== Basic chronological order ===
Sorted dates:
  2022-07-15
  2023-12-31
  2024-01-01
  2024-01-05

=== Already sorted input ===
Sorted dates:
  2020-01-01
  2020-01-02
  2020-01-03

=== Leap year edge cases ===
Sorted dates:
  2019-02-28
  2020-02-29
  2021-03-01

=== Invalid date detection ===
Invalid date: 2024-13-01
Test failed.

}

 



answered Sep 23 by avibootz
...