Jump to content
Incus J

Puzzled by StrToDate

Recommended Posts

var
  fmt:TFormatSettings;
  dt:TDate;
begin
  fmt:=TFormatSettings.Create('en-GB');
  fmt.DateSeparator:='/';
  fmt.ShortDateFormat:='mmm/d/yyyy';
  dt:=StrToDate('Feb/7/2023',fmt);
end;

I'm having trouble converting dates in a string to TDate.  The above test code raises an EConvertError - 'Feb/7/2023' is not a valid date.  Where am I going wrong?

 

Edited by Incus J

Share this post


Link to post

 using mask "MMM/d/yyyy" it's works! = no exception!

Now, "Names" = month-names short!

  • MSWindows Datetime formats:
    • MM = nn
    • MMM = Sss

image.png

 

NOTE: in my RAD11.2 "mmm" works too!

Edited by programmerdelphi2k
  • Thanks 2
  • Haha 1

Share this post


Link to post

In Delphi 11 they fixed the date separator, I had to include a new helper to bridge the difference between 10.4 and 11.

Let's see if I can dig it up for you.

Share this post


Link to post

This is the method I ended up using:

Procedure FixDateSeparator;
Var
  Buffer: Array[0..2] Of Char;
Begin
  If GetLocaleInfo(GetThreadLocale, LOCALE_SDATE, Buffer, 3) > 0 Then
  Begin
    FormatSettings.DateSeparator := Buffer[0];
    FormatSettings.ShortDateFormat := FormatSettings.ShortDateFormat.Replace(FormatSettings.DateSeparator, '/');
  End
  Else
    FormatSettings.DateSeparator := '/';
End;

It's a close copy to Delphi 11.2's GetLocaleChar implementation which will fix the buffer underrun in prior versions. Add a TFormatSettings parameter and call it with "fmt" before you call StrToDate.

 

This is the good news. The bad news is, parsing strings as dates was heavily refactored in D11. If the above won't fix your problem, you'll have to start playing around.

For me the date format was "yyyy. mm. dd." D10.4 was happy to parse "2023. 02. 07" but D11 required the final dot.

  • Thanks 1

Share this post


Link to post
13 hours ago, Attila Kovacs said:

I can't see any month name parsing up to version 20.

If you are looking how the Delphi is converting the month names you can search for @AFormatSettings.ShortMonthNames in the System.SysUtils.

  • Thanks 1

Share this post


Link to post
2 hours ago, Lajos Juhász said:

If you are looking how the Delphi is converting the month names you can search for @AFormatSettings.ShortMonthNames in the System.SysUtils.

As I said, up to BDE 20 the firt check is number/separator/number. If it doesn't match, Exit then raise.

Edited by Attila Kovacs

Share this post


Link to post
2 minutes ago, Attila Kovacs said:

BDE

BDE (Borland Database Enginge) is deprecated for about 20 years you should not use that. If you are referencing to the Delphi version you should use the official one that everyone can understand.

Share this post


Link to post
2 hours ago, Lajos Juhász said:

If you are referencing to the Delphi version you should use the official one that everyone can understand.

BDS Version 20

If you can't deal with this information, just don't comment on it.

Edited by Attila Kovacs

Share this post


Link to post

Thank you for all the responses.  I'm in 10.4.1 and so far can't get either mmm nor MMM to parse the months - but this is an FMX project, so I can probably risk taking it into 11 Alexandria.

 

My general take away from this is that the behaviour of Delphi's built in StrToDate function differs between versions, and might be buggy.  I think I'm going to roll my own StrToDate function, since ideally I'd like to parse dates containing characters like commas too, such as "Feb 7, 2023".

 

Interestingly I found that VarToDateTime (variant to date time) parses dates with commas OK on Windows, but sadly not on macOS - so I'm guessing the OS provides the conversion for that function.

Share this post


Link to post
1 minute ago, Incus J said:

but this is an FMX project, so I can probably risk taking it into 11 Alexandria.

I could readily accept such a statement for VCL, but still FMX tends to change a lot. Less than in the XE versions of Delphi but still.

Share this post


Link to post

FMX is just another reason to keep up with the Delphi version. Unlike Windows, other platforms are not backward compatible.

Share this post


Link to post

I agree FMX tends to change more between versions, and it can be a challenge to keep up.  But to clarify (hopefully) - my development system is HiDPI and I find when I open any existing VCL form in Alexandria it modifies the position and dimension values (the actual stored x/y/width/height) of all objects on the form.  The forms then no longer work on older versions of Windows (e.g. 7/8) - so I avoid Alexandria for VCL projects.

 

Don't get me wrong - I love the look of the HiDPI IDE in Alexandria, and FMX forms behave fine in it, but feel I can not use it for VCL projects.  I may have strayed off topic.

  • Like 1

Share this post


Link to post

I've created a general purpose string to date function that is quite tolerant and can parse a variety of date strings using just a couple of parameters like this:

'February 7th, ''23', TDateOrder.doMDY

Its current tolerance for variations (e.g. 'Feb 7 2023' '02/07/23' "7th.02 '23") risks it getting creative if fed a string that isn't really a date, but I can tighten it up later if it needs to be stricter (famous last words - what could possibly go wrong).

Share this post


Link to post

I think you could create "input patterns" so you could eliminate error expectations.  

For each "input pattern", you could create the pre-analysis procedure/function, and at the end, the final completion code, either to show the error, or to return the value to the user in the expected format.  as Delphi does, that is, procedures that call sub-procedures to find the appropriate use case (a kind of "overloads").  

in general, a simple call to Delphi's standard function would be enough to check the validity or not of a date or time, otherwise, your function could continue after this first evaluation by default.

Share this post


Link to post

Yes - at the moment I have a private InitialParse subroutine that basically attempts to grab three date components from whatever string it is fed, and drops them into a TStringlist (e.g. from 'Feb 7th, ''23' it will extract 'Feb' and '7th' and '23').

What to do with those three components (how to interpret them) is decided later.  It works, but if necessary this subroutine could be made much stricter in terms of what it will accept as input.

class procedure TDateUtils.InitialParse(s: string; var ts:TStringlist);
{Pull three components from the date string.}
var
  i:integer;
  c:char;
  bSkip:boolean;
  sPart:string;
begin
  sPart:='';
  bSkip:=true;
  for i:=1 to Length(s) do begin
    c:=s[i];
    if c.IsLetterOrDigit then begin
      if bSkip then begin
        bSkip:=false;
        AddPart(ts,sPart);
        end;
      sPart:=sPart+c;
      end
    else bSkip:=true;
    end;
  AddPart(ts,sPart);
  while ts.Count<3 do ts.Add('0'); //Ensure 3 date components.  
  end;

class procedure TDateUtils.AddPart(var ts:TStringlist; var s:string);
{Add a complete component e.g. '7th' to the list.}
begin
  if s<>'' then begin
    if ts.Count<3 then ts.Add(s);
    s:=''; //Reset.
    end;
  end;

It could also handle the 'more than three components present' situation more efficiently.

 

Share this post


Link to post

hi @Incus J

 

in fact, the Delphi give to you System.Date.utils and others with many functions/procedures to do many tasks, then, why not use it?

note: isn't it overkill to use a TStringList to store so little data? I think it would be suitable to use Arrays

 

uses
  System.DateUtils;

procedure TForm1.Button1Click(Sender: TObject);
const
  LArrOfXX: array [1 .. 31] of string =                                                              { }
    ('st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', { }
    'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th');
var
  FS               : TFormatSettings;
  LMyDateTimeValue : string;
  LMyDateTimeResult: TDateTime;
begin
  LMyDateTimeValue := 'Feb/16/2023';
  //
  FS                 := TFormatSettings.Create('en-GB');
  FS.ShortDateFormat := 'MMM/dd/yyyy';
  try
    // first test using Delphi function, of course!
    LMyDateTimeResult := StrToDateTime(LMyDateTimeValue, FS); // is valid? not... then do it another way!
    //
    Memo1.Lines.Add(                                                           { your format... }
      FS.ShortMonthNames[LMyDateTimeResult.Month] + ' ' +          { }
      LMyDateTimeResult.Day.ToString + LArrOfXX[LMyDateTimeResult.Day] + ' ' + { }
      LMyDateTimeResult.Year.ToString);
    //
    Memo1.Lines.Add(LMyDateTimeResult.ToISO8601);
    //
    // NOTE: dont use letters used on format function, like this... or use! you decide!
    Memo1.Lines.Add(LMyDateTimeResult.Format('Hello World: MMMM, dd, yyyy', FS)); // dd/mm/yyyy, MMM/dd/yyyy  ... all valid format!
    //
    // etc ...
  except
    // what to do?
    Memo1.Lines.Add('Invalid datetime format...');
  end;
end;

image.thumb.png.49452b3f66bc4d11a04ee719a660167c.png   

 

try some like this:

Quote

    FS.ShortDateFormat := 'My Year: yy  Hello: mm World: dd, dd, mm, yyyy, ss, hh, mm, ss, z';
    Memo1.Lines.Add(DateTimeToStr(LMyDateTimeResult, FS));

 

Edited by programmerdelphi2k

Share this post


Link to post

Thank you for the suggestions and code example - I will have an explore.

 

Yes I agree TStringlist is slightly heavy for the job - though I do find it useful when stepping through code in the debugger as there is a visualiser to display the contents.  I started out with a dynamic array, but got confused trying to pass the array into and out of subroutines, and the TArray type seemed to lack useful methods such as myArray.IndexOf ...basically my lack of experience with arrays led me to switch to TStringlist to save time!

 

My main reason for rolling my own is that I've discovered the built in StrToDate function differs in behaviour between Delphi versions - so I want to create my own function that I can rely on to maintain the same behaviour going forward. 

Edited by Incus J

Share this post


Link to post

for each edition you can have your StrToDate function or any other similar for the edition, for this u can use "compiler directives"  {$IFDEF xxxx } .... {$ELSE / $ELSEIF }...{$ENDIF}

for arrays, you can have yourself functions, like IndexOF();

NOTE: you can have the same approach to Dates/Times types!!! as have yourself "StrToDate(...)' for this types not matters the edition!!!

type
  TMyArrayStrings = TArray<string>;

  TMyHelperToArray = record helper for TMyArrayStrings
    function MyIndexOf(const AValue: string): integer;
  end;

function TMyHelperToArray.MyIndexOf(const AValue: string): integer;
begin
  result := -1;
  //
  for var i: integer := 0 to high(Self) do
    if (Self[i] = AValue) then
      exit(i);
end;

//...
var
  LMyArrStrings    : TArray<string>; // array of string
  i: integer;
begin
  i := LMyArrStrings.MyIndexOf('hello');
  //
  if (i > -1) then
    Delete(LMyDateTimeValue, i, 1);
end;

 

Edited by programmerdelphi2k

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×