Recent

Author Topic: TryStrToDate - do not read, if you do not believe in miracles  (Read 2160 times)

Nicole

  • Hero Member
  • *****
  • Posts: 970
TryStrToDate - do not read, if you do not believe in miracles
« on: January 28, 2023, 09:53:20 am »
Here comes an error, which cannot exist. And it hides away for many days and then to re-occurs again.
And to my mind, the favorite ghosting day is Saturday.
In case you do not believe me such a story, - I attach a screenshot.

But let us start by what I wanna do, a really simple thing:
There is a date I have as string in the format MM/DD/YYYY.
It shall be converted into a Delphi-Type date.
 
Code: Pascal  [Select][+][-]
  1. TryStrToDate(s, dat);
In about one out of six or ten the date is thought by Lazarus to be zero.
If I repeat the task long enough, - the conversion works again.
The error message looks "impressive" in the sense of "what..."?
It is translated to English (gueltig = valid) and you find it below in the line "if not gueltig then show Message"

Code: Text  [Select][+][-]
  1. There is an invalid date: 27.01.2023

This is the source, where the thing stands in.
And probably there is anything, I can change, but whatever I tried there, - the ghost was not banned.

Code: Pascal  [Select][+][-]
  1. function TZeit.KorrigiereAmiDatum(s: string): TDate;  // 12/31/2017 => 31.12.2017 in TDate
  2. Var gueltig: Boolean;
  3.     dat: TDateTime;
  4.     j: integer;
  5.     s1, s2: string;
  6. begin
  7.   result:=0;
  8. //  s:=AnsiReplaceStr(s,'/','.');
  9.   j:=Pos('/',s);
  10.   s1:=copy(s,0,j - 1 ); // kopiert das Monat = Ziffern vor dem 1. Strich, z.B. '6'
  11.   s1:=trim(s1);
  12.   If Length(s1) = 1 then s1:='0' + s1; // macht aus 6 ein 06
  13.   s:=copy(s,j+1,Length(s)); // hat jetzt nur mehr Tag und Jahr, wie '31/2017'
  14.   j:=Pos('/',s); // sucht den Endschrägstrich vom Ami-Tag
  15.   s2:=copy(s, 0 ,j - 1 );
  16.   s2:=trim(s2);
  17.   If Length(s2) = 1 then s2:='0' + s2; // macht aus 6 ein 06
  18.   s:=s2 +  '.' + s1 + '.' + RightStr(s,4);
  19.   gueltig:=TryStrToDate(s, dat);  // Direktaufruf ging nicht, 10.12.2022 das ist völlig unverständlich, das String sieht richtig aus
  20.   if not gueltig then      // startete Lazarus neu, danach ging es einfach wieder
  21.     showMessage('Zeit.KorrigiereUSDatum: Es wurde ein ungültiges Datum übergeben: ' + s)
  22.      else result:=trunc(dat);
  23. end;

 :'(

Any ideas?

TRon

  • Hero Member
  • *****
  • Posts: 2506
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #1 on: January 28, 2023, 10:12:57 am »
You are trying to fix the date time string manually to adjust for your local settings. Why so complicated ?

Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode objfpc}{$H+}
  4. uses
  5.   sysutils;
  6.  
  7. // 12/31/2017 => 31.12.2017
  8.  
  9. function CorrectAmiDate(const s: string): TDate;
  10. var
  11.   valid: boolean;
  12.   date : TDateTime;
  13. begin
  14.   valid := TryStrToDate(S, date, 'MM/DD/YYYY', '/');
  15.   if valid then result := date else date := 0;
  16. end;
  17.  
  18. var
  19.   dates: array of string = ('12/31/2017','1/1/2016','5/3/1999','34/2/8088','0/1/0','1/3/1','4/4/4444');
  20.   date: string;
  21.  
  22. begin
  23.   for date in dates do
  24.     writeln('original: ', date, ' converted: ', DateToStr(CorrectAmiDate(date)));
  25. end.
  26.  
which, for me prints:

Code: [Select]
original: 12/31/2017 converted: 31-12-17
original: 1/1/2016 converted: 1-1-16
original: 5/3/1999 converted: 3-5-99
original: 34/2/8088 converted: 30-12-99
original: 0/1/0 converted: 30-12-99
original: 1/3/1 converted: 3-1-01
original: 4/4/4444 converted: 4-4-44
If you wish for the date string to be converted to the other date format (to return a string instead) then you can use dateToStr but using the formatsettings parameter in order to override your local locale settings.

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #2 on: January 28, 2023, 10:28:55 am »
As TRon shows you are safest specifying format parameters to the overloaded TryStrToDate() function.
If you only give a TDateTime parameter, you are implicitly using the FormatSettings values of your current FPC installation, which may not be what you think.
So specify all parameters, so you know precisely what shortdate format and what date separator the function will use.
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$apptype console}
  5.  
  6. uses
  7.   SysUtils;
  8.  
  9. var
  10.   dateStr: String = '27.01.2023';
  11.   shortDateFmt: String = 'DD.MM.YYYY';
  12.   dateSeparator: Char = '.';
  13.   dt: TDateTime;
  14.  
  15. begin
  16.   case TryStrToDate(dateStr, dt, ShortDateFmt, dateSeparator) of
  17.     True:  WriteLn(dateStr,' is a valid date according to the format ',shortDateFmt,' using separator "',dateSeparator,'"');
  18.     False: WriteLn(dateStr,' is not a valid date according to the format ',shortDateFmt,' using separator "',dateSeparator,'"');
  19.   end;
  20.   ReadLn;
  21. end.

Nicole

  • Hero Member
  • *****
  • Posts: 970
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #3 on: January 28, 2023, 10:56:42 am »
Thank you for the answers!

I wrote
valid := TryStrToDate(S, date, 'MM/DD/YYYY', '/');

for now and it worked - for now
Let us let pass by 2 or 3 Saturdays ....




Thaddy

  • Hero Member
  • *****
  • Posts: 14371
  • Sensorship about opinions does not belong here.
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #4 on: January 28, 2023, 11:03:45 am »
Although some of these "work"  >:D, they contaminate the global formatsettings record for the system. Do it like this:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2. uses sysutils;
  3. var
  4.   temp:TFormatSettings;
  5.   td:TdateTime;
  6. begin
  7.   temp:=FormatSettings; // copy current formatsettings
  8.   temp.DateSeparator :='.';
  9.   temp.LongDateFormat:='dd.mm.yyyy';
  10.   writeln(trystrtodate('21.02.1958',td,temp)); // should return true  
  11.   writeln(DateToStr(td)); // proof that formatsettings is preserved
  12. end.
Then you do not screw up the system wide formatsettings.... O:-)
« Last Edit: January 28, 2023, 11:19:49 am by Thaddy »
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #5 on: January 28, 2023, 11:13:43 am »
No, they are used as local parameters here for TryStrToDate, just like your temp FormatSettings.

Thaddy

  • Hero Member
  • *****
  • Posts: 14371
  • Sensorship about opinions does not belong here.
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #6 on: January 28, 2023, 11:21:02 am »
That may be is not the case, but and it is better to offer clean solutions.
You also did not read all code but made assumptions. That is even worse.

(FormatSettings being a heap allocated global record, that is... It WILL get modified) >:D
« Last Edit: January 28, 2023, 11:25:40 am by Thaddy »
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #7 on: January 28, 2023, 11:52:26 am »
Falsified by experiment:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. uses
  3.   SysUtils;
  4. var
  5.   d: TDate;
  6.   s: String;
  7. begin
  8.   WriteLn('FormatSettings BEFORE: ', FormatSettings.ShortDateFormat, ' ', FormatSettings.DateSeparator);
  9.   s := '12-28-2023';
  10.   if TryStrToDate(s, d, 'mm/dd/yyyy', '-') then
  11.     WriteLn('String "', s, '" converted to date ', DateToStr(d))
  12.   else
  13.     WriteLn('String "', s ,'" could not be converted to a date.');
  14.   WriteLn('FormatSettings AFTER: ', FormatSettings.ShortDateFormat, ' ', FormatSettings.DateSeparator);
  15.   ReadLn;
  16. end.
  17.  
Output:
Code: [Select]
FFormatSettings BEFORE: dd.MM.yyyy .
String "12-28-2023" converted to date 28.12.2023
FormatSettings AFTER: dd.MM.yyyy .
« Last Edit: January 28, 2023, 12:35:33 pm by wp »

TRon

  • Hero Member
  • *****
  • Posts: 2506
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #8 on: January 28, 2023, 12:00:07 pm »
Thank you for the answers!
You're welcome !

Glad that it worked for you and thank you for reporting back.

Quote
for now and it worked - for now
Let us let pass by 2 or 3 Saturdays ....
Can't wait for that to happen. Please do share in case you run into issues with that.

Thaddy

  • Hero Member
  • *****
  • Posts: 14371
  • Sensorship about opinions does not belong here.
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #9 on: January 28, 2023, 12:54:10 pm »
If it is falsified (I am a great Karl Popper fan)... Ok. I still think my solution is more robust.

(For others: wp means that he can give a counter example of my proposition. He does NOT mean the code is incorrect, because it is. Karl Popper, a philosopher, developed the falsification theory as opposed to the usual verification. One counter example proofs umpty verifications can be false. See: https://en.wikipedia.org/wiki/Karl_Popper and https://en.wikipedia.org/wiki/Falsifiability . He is a bit of my hero!  Although tied to social sciences in most literature, the theory is equally applicable to computer science! But alas very underestimated in that field. Then again Philosophy is the mother of all science).
« Last Edit: January 28, 2023, 01:12:21 pm by Thaddy »
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #10 on: January 28, 2023, 01:55:53 pm »
I still think my solution is more robust.
Why?
Code: Pascal  [Select][+][-]
  1. function TryStrToDate(const S: AnsiString; out Value: TDateTime;
  2.                     const useformat : string; separator : char = #0): Boolean;
  3. Var
  4.   Msg : Ansistring;
  5. begin
  6.   Result:=Length(S)<>0;
  7.   If Result then
  8.     begin
  9.     Value:=IntStrToDate(Msg,PChar(S),Length(S),useformat,DefaultFormatSettings,Separator);
  10.     Result:=(Msg='');
  11.     end;
  12. end;
  13.  

It calls
Code: Pascal  [Select][+][-]
  1. function IntStrToDate(Out ErrorMsg : AnsiString; const S: PChar; Len : integer; const useformat : string; const defs:TFormatSettings; separator : char = #0): TDateTime;
where the global FormatSettings are not touched at all, and the parameter defs is a const. Study the source code.

Thaddy

  • Hero Member
  • *****
  • Posts: 14371
  • Sensorship about opinions does not belong here.
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #11 on: January 28, 2023, 02:38:50 pm »
I did. Your code is only valid when you specify the parameters explicitly. Anyway, I still think my code is the better solution in the long term and more concise.
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5469
  • Compiler Developer
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #12 on: January 29, 2023, 01:32:40 pm »
Although some of these "work"  >:D, they contaminate the global formatsettings record for the system. Do it like this:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2. uses sysutils;
  3. var
  4.   temp:TFormatSettings;
  5.   td:TdateTime;
  6. begin
  7.   temp:=FormatSettings; // copy current formatsettings
  8.   temp.DateSeparator :='.';
  9.   temp.LongDateFormat:='dd.mm.yyyy';
  10.   writeln(trystrtodate('21.02.1958',td,temp)); // should return true  
  11.   writeln(DateToStr(td)); // proof that formatsettings is preserved
  12. end.
Then you do not screw up the system wide formatsettings.... O:-)

TryStrToDate with the string format parameter will not affect the global FormatSettings.

(FormatSettings being a heap allocated global record, that is... It WILL get modified) >:D

FormatSettings is not allocated on the heap. You can't deallocate it using Dispose or FreeMem. It's part of the data segment that is allocated by the OS itself like any global variable is.

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #13 on: January 29, 2023, 03:23:21 pm »
Anyway, I still think my code is the better solution in the long term and more concise.
It is "better" in the sense that it is Delphi-compatible - Delphi only supports the formatsettings argument, but not the format string allowed in FPC. It is "worse" in the sense that it requires more code lines: variable declaration + assignment of format string to local formatsettings + assignment of date separator.

dseligo

  • Hero Member
  • *****
  • Posts: 1220
Re: TryStrToDate - do not read, if you do not believe in miracles
« Reply #14 on: January 29, 2023, 03:35:46 pm »
Anyway, I still think my code is the better solution in the long term and more concise.
It is "better" in the sense that it is Delphi-compatible - Delphi only supports the formatsettings argument, but not the format string allowed in FPC. It is "worse" in the sense that it requires more code lines: variable declaration + assignment of format string to local formatsettings + assignment of date separator.

IMHO, it is less error prone. I use it like Thaddy showed for number formatting. Decimal separator here is comma, but sometimes I need to format numbers from (or to) dot decimal separator. I usually have (global :)) variable, set up early (in .lpr) and then just use when I need specific format.

 

TinyPortal © 2005-2018