Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Changine StrToDate Format 1

Status
Not open for further replies.

6volt

Programmer
Jun 4, 2003
74
US
I've been fighting this one for a few days now and have pretty much exhausted internet searches.

There are people that say if you change

ShortDateFormat,

it will change the format used by StrToDate.

I don't think this is true.

I even wrestled around with stuff like

GetLocaleInfo (..., LOCALE_SSHORTDATE...)

to no avail.

I'm afraid I'm going to have to write stupid, brute force code to convert '1-Jan-02' to a TDateTime.

Tell me it ain't so,

Thanks in advance,
Tom

 
You do not say which version of Delphi you are using. But the Delphi 5 help says:
<snip>
Code:
function StrToDate(const S: string): TDateTime;
Description

Call StrToDate to parse a string that specifies a date. If S does not contain a valid date, StrToDate raises an EConvertError exception.

S must consist of two or three numbers, separated by the character defined by the DateSeparator global variable. The order for month, day, and year is determined by the ShortDateFormat global variable--possible combinations are m/d/y, d/m/y, and y/m/d.
</snip>
So, StrToDate will not convert 1-Jan-02 to a TDateTime.


Something like this should do what you want:-
Code:
function Str2Date ( d: string ): TDateTime;
var
  mon: integer;
begin
  d := StringReplace ( d, '-', '/', [rfReplaceAll] );
  for mon := 1 to 12 do
    d := StringReplace ( d, ShortMonthNames[mon], IntToStr(mon), [] );
  result := StrToDate ( d );
end;

Andrew
 
towerbase,

Thanks for the algorithm.

What really caught my curiosity was you <snip> portion where the Borland manual says under StrToDate:

&quot;The correct format of the date string varies if you change the value of some of the date and time typed constants.&quot;

Then, on the web site under StrToDate, they say under the Dates and Times Tutorial:

&quot;Formatting control variables

The variables and their default values are given below. Note that these control conversions of date time values to strings, and sometimes from strings to date time values (such as DateSeparator).&quot;

I LOVE the use of the word &quot;sometimes.&quot;

Why can't someone be specific here?

I am using Delphi 5 but could be using 7. Would 7 make this any easier?

Thanks
Tom
 
SysUtils.PAS: ShortDateFormat : String;

It gets set on application startup to:
Code:
  ShortDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SSHORTDATE, 'm/d/yy'));

As for StrToDate, here is the comment from inside the SysUtils unit:
{ StrToDate converts the given string to a date value. The string mustconsist of two or three numbers, separated by the character defined by the DateSeparator global variable. The order for month, day, and year is determined by the ShortDateFormat global variable--possible combinations are m/d/y, d/m/y, and y/m/d. If the string contains only two numbers, it is interpreted as a date (m/d or d/m) in the current year. Year values between 0 and 99 are assumed to be in the current century. If the given string does not contain a valid date, an EConvertError exception is raised. }

Besides the passive voice, this clearly indicates that you cannot use StrToDate if the date format has letters in it.

Here is my code to parse any date. It refers to some other functions (like BreakApart). If you cannot figure them out, please ask:
Code:
{
This function checks AString for:
Legal date (accepted by StrToDate based on current SysUtils.ShortDateFormat
All numeric digits (Year assumed to be two digits)
Six-digits
Four-digits
Five-digits
Unambiguous digits consistent with the DateFormat: 13297, 23689, 72390
Ambiguous digits: 12295
Month Name
}

function StrToDateEx(AString : string; AShortDateFormat : string = '') : TDateTime;
var
   DatePartSequence : array[0..2] of string[4];
   MonthIndex, DayIndex, YearIndex : System.Integer;
   RawMask : string;
   Month, Day, Year : System.Integer;

   function IsLegalDay(ADay, AMonth, AYear : Integer) : Boolean;
   begin
       if (ADay >= 1) and (ADay <= DaysInMonth(AMonth, AYear)) then
           Result := True
       else
           Result := False;
   end;

   function IsLegalMonth(AMonth, AYear : Integer) : Boolean;
   begin
       if (AMonth >= 1) and (AMonth <= 12) then
           Result := True
       else
           Result := False;
   end;

   procedure LoadAndStrip(var Target : System.Integer; Start, Len : Integer);
   begin
       Target := StrToIntDef(Copy(AString, Start, Len), 0);
       Delete(AString, Start, Len);
   end;

   procedure Parse5Digits;
       {       Month, Day, Year : Integer are global to the containing proc  }

       function GrabBeginning(var Target : System.Integer; Code : string) : Boolean;
       begin
           if DatePartSequence[0] = Code then
           begin
               LoadAndStrip(Target, 1, 2);
               Result := True;
           end
           else
               Result := False;
       end;

       function GrabEnd(var Target : System.Integer; Code : string) : Boolean;
       begin
           if DatePartSequence[2] = Code then
           begin
               LoadAndStrip(Target, 4, 2);
               Result := True;
           end
           else
               Result := False;
       end;

   begin
       Month := 0;
       Day := 0;
       Year := 0;

       if not (GrabEnd(Year, 'YY') or GrabEnd(Month, 'MM') or GrabEnd(Day, 'DD')) then
           ;
       if not (GrabBeginning(Year, 'YY') or GrabBeginning(Month, 'MM') or GrabBeginning(Day, 'DD')) then
           ;
       if Length(AString) = 1 then
       begin
           if MonthIndex = 2 then
               LoadAndStrip(Month, 1, 1)
           else if DayIndex = 2 then
               LoadAndStrip(Day, 1, 1);
       end
       else if Year = 0 then
           (* *)
       else if (DatePartSequence[0] = 'D') and (DatePartSequence[1] = 'M') then
       begin
           if IsLegalDay(StrToIntDef(Copy(AString, 1, 2), 0), StrToIntDef(Copy(AString, 3, 1), 0), Year) and
               IsLegalMonth(StrToIntDef(Copy(AString, 3, 1), 0), Year) then
           begin
               LoadAndStrip(Day, 1, 2);
               LoadAndStrip(Month, 3, 1);
           end
           else if IsLegalDay(StrToIntDef(Copy(AString, 1, 1), 0), StrToIntDef(Copy(AString, 2, 2), 0), Year) and
               IsLegalMonth(StrToIntDef(Copy(AString, 2, 2), 0), Year) then
           begin
               LoadAndStrip(Day, 1, 1);
               LoadAndStrip(Month, 2, 2);
           end;
       end
       else if (DatePartSequence[1] = 'D') and (DatePartSequence[0] = 'M') then
       begin
           if IsLegalDay(StrToIntDef(Copy(AString, 3, 1), 0), StrToIntDef(Copy(AString, 1, 2), 0), Year) and
               IsLegalMonth(StrToIntDef(Copy(AString, 1, 2), 0), Year) then
           begin
               Day := StrToIntDef(Copy(AString, 3, 1), 0);
               Month := StrToIntDef(Copy(AString, 1, 2), 0);
           end
           else if IsLegalDay(StrToIntDef(Copy(AString, 2, 2), 0), StrToIntDef(Copy(AString, 1, 1), 0), Year) and
               IsLegalMonth(StrToIntDef(Copy(AString, 1, 1), 0), Year) then
           begin
               Day := StrToIntDef(Copy(AString, 2, 2), 0);
               Month := StrToIntDef(Copy(AString, 1, 1), 0);
           end
       end;
   end;

   procedure SetIndex(var Index : System.Integer; Code : string);
   var
       Counter : System.Integer;
   begin
       for Counter := 0 to 2 do
       begin
           if DatePartSequence[Counter][1] = Code then
           begin
               Index := Counter;
               Break;
           end;
       end;
   end;

var
   Pointer, Counter : System.Integer;
   ThisString : string;
   BreakArray : TStringList;
   ThisCentury : System.Integer;
begin
   if AShortDateFormat = '' then
   try
       Result := StrToDate(AString);
       Exit;
   except
       on EConvertError do
           ;
   end;

   Result := 0.0;

   Month := 0;
   Day := 0;
   Year := 0;

   if AShortDateFormat = '' then
       RawMask := SysUtils.ShortDateFormat                                     //  from Windows LOCALE_SSHORTDATE
   else
       RawMask := AShortDateFormat;                                            //  e.g. mm/dd/yy

   while Pos(DateSeparator, RawMask) > 0 do
       Delete(RawMask, Pos(SysUtils.DateSeparator, RawMask), 1);

   Pointer := 2;
   Counter := 0;
   DatePartSequence[0] := UpCase(RawMask[1]);
   while Pointer <= Length(RawMask) do
   begin
       if RawMask[Pointer] = RawMask[Pointer - 1] then
           DatePartSequence[Counter] := DatePartSequence[Counter] + UpCase(RawMask[Pointer])
       else
       begin
           Inc(Counter);
           DatePartSequence[Counter] := UpCase(RawMask[Pointer]);
       end;
       Inc(Pointer);
   end;

   SetIndex(DayIndex, 'D');
   SetIndex(MonthIndex, 'M');
   SetIndex(YearIndex, 'Y');

   BreakArray := TStringList.Create;
   try
       BreakApart(AString, SysUtils.DateSeparator + ':/-., ', BreakArray);
       if BreakArray.Count = 3 then
       begin
           for Counter := 0 to 2 do
           begin
               if StrToIntDef(BreakArray[Counter], -1) = -1 then
               begin
                   MonthIndex := Counter;
                   if Counter = 1 then
                   begin
                       if (BreakArray[0][1] = '0') then
                       begin
                           YearIndex := 0;
                           DayIndex := 2;
                       end
                       else
                       begin
                           YearIndex := 2;
                           DayIndex := 0;
                       end
                   end
                   else if Counter = 0 then
                   begin
                       DayIndex := 1;
                       YearIndex := 2;
                   end
                   else
                   begin
                       DayIndex := 1;
                       YearIndex := 0;
                   end;
                   Break;
               end;
           end;

           for Counter := 0 to 2 do
           begin
               if Counter = YearIndex then
               begin
                   Year := StrToIntDef(BreakArray[Counter], 0);
                   if Year < 50 then
                   begin
                       ThisCentury := StrToInt(FormatDateTime('yyyy', Date));
                       Inc(Year, ((ThisCentury div 100) + 1) * 100);
                   end
                   else if Year < 100 then
                   begin
                       ThisCentury := StrToInt(FormatDateTime('yyyy', Date));
                       Inc(Year, (ThisCentury div 100) * 100);
                   end;
               end

               else if Counter = MonthIndex then
               begin
                   Month := StrToIntDef(BreakArray[Counter], 0);
                   if Month = 0 then
                   begin
                       for Pointer := 1 to 12 do
                       begin
                           if SameText(BreakArray[Counter], FormatDateTime('mmm', EncodeDate(1000, Pointer, 1))) then
                           begin
                               Month := Pointer;
                               Break;
                           end;
                           if SameText(BreakArray[Counter], FormatDateTime('mmmm', EncodeDate(1000, Pointer, 1))) then
                           begin
                               Month := Pointer;
                               Break;
                           end;
                       end;
                   end;
               end

               else if Counter = DayIndex then
                   Day := StrToIntDef(BreakArray[Counter], 0);
           end;
       end;
       AString := StickTogether(BreakArray, '');
   finally
       BreakArray.Free;
   end;

   if (Year <> 0) and (Month <> 0) and (Day <> 0) then

       //      jump to the bottom

   else if StrToIntDef(AString, 0) = 0 then
       Exit

   else if (Length(AString) = 4) then
   begin
       for Counter := 0 to 2 do
           if Counter = YearIndex then
               LoadAndStrip(Year, 1, 2)
           else if Counter = MonthIndex then
               LoadAndStrip(Month, 1, 1)
           else if Counter = DayIndex then
               LoadAndStrip(Day, 1, 1);
   end
   else if (Length(AString) = 6) then
   begin
       ThisString := AString;
       for Counter := 0 to 2 do
       begin
           if Counter = YearIndex then
               LoadAndStrip(Year, 1, 2)
           else if Counter = MonthIndex then
               LoadAndStrip(Month, 1, 2)
           else if Counter = DayIndex then
               LoadAndStrip(Day, 1, 2);
       end;

       if (not IsLegalMonth(Month, Year)) or
           (not IsLegalDay(Day, Month, Year)) or
           (DatePartSequence[YearIndex] = 'YYYY') then
       begin
           AString := ThisString;
           for Counter := 0 to 2 do
           begin
               if Counter = YearIndex then
                   LoadAndStrip(Year, 1, 4)
               else if Counter = MonthIndex then
                   LoadAndStrip(Month, 1, 1)
               else if Counter = DayIndex then
                   LoadAndStrip(Day, 1, 1);
           end
       end;
   end
   else if (Length(AString) = 8) then
   begin
       for Counter := 0 to 2 do
       begin
           if Counter = YearIndex then
               LoadAndStrip(Year, 1, 4)
           else if Counter = MonthIndex then
               LoadAndStrip(Month, 1, 2)
           else if Counter = DayIndex then
               LoadAndStrip(Day, 1, 2);
       end;
   end
   else if (Length(AString) = 5) then
       Parse5Digits;

   if (Year > 0) and (Month > 0) and (Day > 0) then
       Result := EncodeDate(Year, Month, Day)
   else
       raise EConvertError.Create(AString + ' is not a valid date');
end;

Cheers
 
I think the Delphi 5 help (which is almost the same as the Delphi 7 help for StrToDate) is quite specific and unambiguous:

S must consist of two or three numbers, separated by the character defined by the DateSeparator global variable.

It may not do what you want it to do, but the work around is not too difficult.

The site is not an offical Borland site. So it should be read in conjunction with the offical Borland documentation (and source code if you have the time).

Andrew
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top