Lubos
Lubos

Reputation: 45

StrToDate cannot convert value obtained from DateToStr

I have an old application. It has a date in localized format stored in its data. This string was only used for display, so it was acceptable to have it in the localized form. Now we need to reuse it as a TDateTime. It seemed to be simple: because we obtained the string from DateToStr, we will convert it back using StrToDate. So I wrote a small console program to verify it:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;
var
  S:String;
  D: TDateTime;
begin
  S := DateToStr(Now);
  Writeln(S);
  D := StrToDate(S); //! throws an EConvertError
  Readln;
end.

It throws an EConvertError: Project Project1.exe raised exception class EConvertError with message ''28. 9. 2017' is not a valid date'. This is incorrect, the in-exception mentioned date is valid! It was generated via DateToStr just a moment ago. This completely doesn't make sense to me. Could this be a bug in Windows 10?

Upvotes: 0

Views: 697

Answers (1)

Sertac Akyuz
Sertac Akyuz

Reputation: 54812

I can duplicate your issue if I enter dd. M.yyyy for short date format in regional settings of the OS. Or dd. M. yyyy to have the exact error message you quoted in the question, note the spaces in the format strings.

The reason StrToDate fails is that the ScanDate function in "sysutils.pas" fails and returns False. The reason ScanDate fails is an incorrect date separator in the default format settings record.

RTL retrieves the date separator with the below code, in which LOCALE_SDATE is passed for LocaleType parameter and "/" is passed for the Default parameter.

...
var
  Buffer: array[0..1] of Char;
begin
  if GetLocaleInfo(Locale, LocaleType, Buffer, 2) > 0 then
    Result := Buffer[0] else
  Result := Default;

In the case of your date format the required buffer is 3 characters, since the RTL provides only 2 chars the API fails with ERROR_INSUFFICIENT_BUFFER and the above function, not concerned with the fail reason, returns the default date separator of "/". So this is an error in the Delphi RTL.

If you have a valid use case for your odd format string, use the StrToDate overload that receives a format settings parameter. If not, just correct your date strings in regional settings.

Unfortunately you cannot apply a generic solution for the first alternative. That is you can retrieve the correct date separator with, e.g., like this:

function GetDateSeparator: string;
var
  NumChars: Integer;
begin
  NumChars := GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, nil, 0);
  Win32Check(NumChars <> 0);
  SetLength(Result, NumChars);
  Win32Check(GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, PChar(Result), NumChars) <> 0);
  SetLength(Result, Length(Result) - 1);
end;

But you cannot assign the returning '. ' to the DateSeparator of a TFormatSettings, because it expects a Char. You have to pretend you know the separator is a '.' and use that, and that only works because the second char is a space which is stripped in ScanDate.

Upvotes: 5

Related Questions