Delphi, String to date conversion [duplicate] - delphi
Is there any way in Delphi 5 to convert a string to a TDateTime where you can specify the actual format to use?
I'm working on a jobprocessor, which accepts tasks from various workstations. The tasks have a range of parameters, some of which are dates, but (unfortunately, and out of my control) they're passed as strings. Since the jobs can come from different workstations, the actual datetime format used to format the dates as a string might (and, of course, actual do) differ.
Googling around, the only quick solutions I found was to sneakily change the ShortDateFormat variable, and restore it to its original value afterwards. Since ShortDateFormat is a global variable, and I'm working in a threaded environment the only way this would work is by synchronizing every access to it, which is completely unacceptable (and undoable).
I could copy the library code from the SysUtils unit into my own methods, and tweak them to work with a specified format instead of the global variables, but I'm just wondering if there's something more adequate out there that I missed.
UPDATE
To put it more succinctly:
I need something like StrToDate (or StrToDateTime), with the added option of specifying the exact format it should use to convert the string to a TDateTime.
Use VarToDateTime instead. It supports many more date formats in the string and converts them automatically.
var
DateVal: TDateTime;
begin
DateVal := VarToDateTime('23 Sep 2010');
ShowMessage(DateToStr(DateVal));
end;
I see you're using Delphi 5. Some versions of Delphi will need to add Variants to the uses clause; most later versions add it for you. I don't remember which category Delphi 5 fell into.
I created such routine for FreePascal's dateutils unit, and it should easy to port, if porting is needed at all.
Code:
http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/packages/rtl-objpas/src/inc/dateutil.inc?revision=30628&view=co
(code is the last (huge) procedure at the end of the file)
documentation:
http://www.freepascal.org/docs-html/rtl/dateutils/scandatetime.html
Note that it is not a complete inverse of formatdatetime, and it has some extensions:
An inverse of FormatDateTime is not 100% an inverse, simply because one can put e.g. time tokens twice in the format string,and scandatetime wouldn't know which time to pick.
Strings like hn can't be reversed safely. E.g. 1:2 (2 minutes after 1) delivers 12 which is parsed as 12:00 and then
misses chars for the "n" part.
trailing characters are ignored.
no support for Eastern Asian formatting characters since they are windows only.
no MBCS support.
Extensions
#9 eats whitespace.
whitespace at the end of a pattern is optional.
? matches any char.
Quote the above chars to really match the char.
(I believe these comments are slightly outdated in the sense hat some Asian support was added later but I am not sure)
Later versions of Delphi can take an extra TFormatSettings argument to the string conversion functions. TFormatSettings is a structure containing the various format global variables (ShortDateFormat, LongDateFormat, etc). So you can override those value in a thread-safe manner and even for a single call.
I don't remember in which version of Delphi this was introduced, but I'm pretty sure it was after Delphi 5.
So yeah, as far as I know, you either need to synchronize every access to ShortDateFormat, or use a different function.
Here's the function, its two helpers, and all the code, I wrote to parse a string using an exact datetime format:
class function TDateTimeUtils.TryStrToDateExact(const S, DateFormat: string; PivotYear: Integer;
out Value: TDateTime): Boolean;
var
Month, Day, Year: Integer;
Tokens: TStringDynArray;
CurrentToken: string;
i, n: Integer;
Partial: string;
MaxValue: Integer;
nCurrentYear: Integer;
function GetCurrentYear: Word;
var
y, m, d: Word;
begin
DecodeDate(Now, y, m, d);
Result := y;
end;
begin
Result := False;
{
M/dd/yy
Valid pictures codes are
d Day of the month as digits without leading zeros for single-digit days.
dd Day of the month as digits with leading zeros for single-digit days.
ddd Abbreviated day of the week as specified by a LOCALE_SABBREVDAYNAME* value, for example, "Mon" in English (United States).
Windows Vista and later: If a short version of the day of the week is required, your application should use the LOCALE_SSHORTESTDAYNAME* constants.
dddd Day of the week as specified by a LOCALE_SDAYNAME* value.
M Month as digits without leading zeros for single-digit months.
MM Month as digits with leading zeros for single-digit months.
MMM Abbreviated month as specified by a LOCALE_SABBREVMONTHNAME* value, for example, "Nov" in English (United States).
MMMM Month as specified by a LOCALE_SMONTHNAME* value, for example, "November" for English (United States), and "Noviembre" for Spanish (Spain).
y Year represented only by the last digit.
yy Year represented only by the last two digits. A leading zero is added for single-digit years.
yyyy Year represented by a full four or five digits, depending on the calendar used. Thai Buddhist and Korean calendars have five-digit years. The "yyyy" pattern shows five digits for these two calendars, and four digits for all other supported calendars. Calendars that have single-digit or two-digit years, such as for the Japanese Emperor era, are represented differently. A single-digit year is represented with a leading zero, for example, "03". A two-digit year is represented with two digits, for example, "13". No additional leading zeros are displayed.
yyyyy Behaves identically to "yyyy".
g, gg Period/era string formatted as specified by the CAL_SERASTRING value.
The "g" and "gg" format pictures in a date string are ignored if there is no associated era or period string.
PivotYear
The maximum year that a 1 or 2 digit year is assumed to be.
The Microsoft de-factor standard for y2k is 2029. Any value greater
than 29 is assumed to be 1930 or higher.
e.g. 2029:
1930, ..., 2000, 2001,..., 2029
If the PivotYear is between 0 and 99, then PivotYear is assumed to be
a date range in the future. e.g. (assuming this is currently 2010):
Pivot Range
0 1911..2010 (no future years)
1 1912..2011
...
98 2009..2108
99 2010..2099 (no past years)
0 ==> no years in the future
99 ==> no years in the past
}
if Length(S) = 0 then
Exit;
if Length(DateFormat) = 0 then
Exit;
Month := -1;
Day := -1;
Year := -1;
Tokens := TDateTimeUtils.TokenizeFormat(DateFormat);
n := 1; //input string index
for i := Low(Tokens) to High(Tokens) do
begin
CurrentToken := Tokens[i];
if CurrentToken = 'MMMM' then
begin
//Long month names, we don't support yet (you're free to write it)
Exit;
end
else if CurrentToken = 'MMM' then
begin
//Short month names, we don't support yet (you're free to write it)
Exit;
end
else if CurrentToken = 'MM' then
begin
//Month, with leading zero if needed
if not ReadDigitString(S, n, 2{MinDigits}, 2{MaxDigits}, 1{MinValue}, 12{MaxValue}, {var}Month) then Exit;
end
else if CurrentToken = 'M' then
begin
//months
if not ReadDigitString(S, n, 1{MinDigits}, 2{MaxDigits}, 1{MinValue}, 12{MaxValue}, {var}Month) then Exit;
end
else if CurrentToken = 'dddd' then
begin
Exit; //Long day names, we don't support yet (you're free to write it)
end
else if CurrentToken = 'ddd' then
begin
Exit; //Short day names, we don't support yet (you're free to write it);
end
else if CurrentToken = 'dd' then
begin
//If we know what month it is, and even better if we know what year it is, limit the number of valid days to that
if (Month >= 1) and (Month <= 12) then
begin
if Year > 0 then
MaxValue := MonthDays[IsLeapYear(Year), Month]
else
MaxValue := MonthDays[True, Month]; //we don't know the year, assume it's a leap year to be more generous
end
else
MaxValue := 31; //we don't know the month, so assume it's the largest
if not ReadDigitString(S, n, 2{MinDigits}, 2{MaxDigits}, 1{MinValue}, MaxValue{MaxValue}, {var}Day) then Exit;
end
else if CurrentToken = 'd' then
begin
//days
//If we know what month it is, and even better if we know what year it is, limit the number of valid days to that
if (Month >= 1) and (Month <= 12) then
begin
if Year > 0 then
MaxValue := MonthDays[IsLeapYear(Year), Month]
else
MaxValue := MonthDays[True, Month]; //we don't know the year, assume it's a leap year to be more generous
end
else
MaxValue := 31; //we don't know the month, so assume it's the largest
if not ReadDigitString(S, n, 1{MinDigits}, 2{MaxDigits}, 1{MinValue}, MaxValue{MaxValue}, {var}Day) then Exit;
end
else if (CurrentToken = 'yyyy') or (CurrentToken = 'yyyyy') then
begin
//Year represented by a full four or five digits, depending on the calendar used.
{
Thai Buddhist and Korean calendars have five-digit years.
The "yyyy" pattern shows five digits for these two calendars,
and four digits for all other supported calendars.
Calendars that have single-digit or two-digit years, such as for
the Japanese Emperor era, are represented differently.
A single-digit year is represented with a leading zero, for
example, "03". A two-digit year is represented with two digits,
for example, "13". No additional leading zeros are displayed.
}
if not ReadDigitString(S, n, 4{MinDigits}, 4{MaxDigits}, 0{MinValue}, 9999{MaxValue}, {var}Year) then Exit;
end
else if CurrentToken = 'yyy' then
begin
//i'm not sure what this would look like, so i'll ignore it
Exit;
end
else if CurrentToken = 'yy' then
begin
//Year represented only by the last two digits. A leading zero is added for single-digit years.
if not ReadDigitString(S, n, 2{MinDigits}, 2{MaxDigits}, 0{MinValue}, 99{MaxValue}, {var}Year) then Exit;
nCurrentYear := GetCurrentYear;
Year := (nCurrentYear div 100 * 100)+Year;
if (PivotYear < 100) and (PivotYear >= 0) then
begin
//assume pivotyear is a delta from this year, not an absolute value
PivotYear := nCurrentYear+PivotYear;
end;
//Check the pivot year value
if Year > PivotYear then
Year := Year - 100;
end
else if CurrentToken = 'y' then
begin
//Year represented only by the last digit.
if not ReadDigitString(S, n, 1{MinDigits}, 1{MaxDigits}, 0{MinValue}, 9{MaxValue}, {var}Year) then Exit;
nCurrentYear := GetCurrentYear;
Year := (nCurrentYear div 10 * 10)+Year;
if (PivotYear < 100) and (PivotYear >= 0) then
begin
//assume pivotyear is a delta from this year, not an absolute value
PivotYear := nCurrentYear+PivotYear;
end;
//Check the pivot year value
if Year > PivotYear then
Year := Year - 100;
end
else
begin
//The input string should contains CurrentToken starting at n
Partial := Copy(S, n, Length(CurrentToken));
Inc(n, Length(CurrentToken));
if Partial <> CurrentToken then
Exit;
end;
end;
//If there's still stuff left over in the string, then it's not valid
if n <> Length(s)+1 then
begin
Result := False;
Exit;
end;
if Day > MonthDays[IsLeapYear(Year), Month] then
begin
Result := False;
Exit;
end;
try
Value := EncodeDate(Year, Month, Day);
except
Result := False;
Exit;
end;
Result := True;
end;
class function TDateTimeUtils.TokenizeFormat(fmt: string): TStringDynArray;
var
i: Integer;
partial: string;
function IsDateFormatPicture(ch: AnsiChar): Boolean;
begin
case ch of
'M','d','y': Result := True;
else Result := False;
end;
end;
begin
SetLength(Result, 0);
if Length(fmt) = 0 then
Exit;
//format is only one character long? If so then that's the tokenized entry
if Length(fmt)=1 then
begin
SetLength(Result, 1);
Result[0] := fmt;
end;
partial := fmt[1];
i := 2;
while i <= Length(fmt) do
begin
//If the characters in partial are a format picture, and the character in fmt is not the same picture code then write partial to result, and reset partial
if IsDateFormatPicture(partial[1]) then
begin
//if the current fmt character is different than the running partial picture
if (partial[1] <> fmt[i]) then
begin
//Move the current partial to the output
//and start a new partial
SetLength(Result, Length(Result)+1);
Result[High(Result)] := partial;
Partial := fmt[i];
end
else
begin
//the current fmt character is more of the same format picture in partial
//Add it to the partial
Partial := Partial + fmt[i];
end;
end
else
begin
//The running partial is not a format picture.
//If the current fmt character is a picture code, then write out the partial and start a new partial
if IsDateFormatPicture(fmt[i]) then
begin
//Move the current partial to the output
//and start a new partial
SetLength(Result, Length(Result)+1);
Result[High(Result)] := partial;
Partial := fmt[i];
end
else
begin
//The current fmt character is another non-picture code. Add it to the running partial
Partial := Partial + fmt[i];
end;
end;
Inc(i);
Continue;
end;
//If we have a running partial, then add it to the output
if partial <> '' then
begin
SetLength(Result, Length(Result)+1);
Result[High(Result)] := partial;
end;
end;
class function TDateTimeUtils.ReadDigitString(const S: string; var Pos: Integer;
MinDigits, MaxDigits: Integer; MinValue, MaxValue: Integer;
var Number: Integer): Boolean;
var
Digits: Integer;
Value: Integer;
Partial: string;
CandidateNumber: Integer;
CandidateDigits: Integer;
begin
Result := False;
CandidateNumber := -1;
CandidateDigits := 0;
Digits := MinDigits;
while Digits <= MaxDigits do
begin
Partial := Copy(S, Pos, Digits);
if Length(Partial) < Digits then
begin
//we couldn't get all we wanted. We're done; use whatever we've gotten already
Break;
end;
//Check that it's still a number
if not TryStrToInt(Partial, Value) then
Break;
//Check that it's not too big - meaning that getting anymore wouldn't work
if (Value > MaxValue) then
Break;
if (Value >= MinValue) then
begin
//Hmm, looks good. Keep it as our best possibility
CandidateNumber := Value;
CandidateDigits := Digits;
end;
Inc(Digits); //try to be greedy, grabbing even *MORE* digits
end;
if (CandidateNumber >= 0) or (CandidateDigits > 0) then
begin
Inc(Pos, CandidateDigits);
Number := CandidateNumber;
Result := True;
end;
end;
If you want to know how this was solved in later Delphi's, you can take a look at the source of a slightly more modern (looks like Delphi 6) sysutils.pas here:
http://anygen.googlecome.com/.../SysUtils.pas
Check out the overloaded versions of StrToDateTime that take a TFormatSettings parameter.
function StrToDateTime(const S: string;
const FormatSettings: TFormatSettings): TDateTime; overload;
Use RegExpr library (https://github.com/masterandrey/TRegExpr)
var
RE: TRegExpr;
begin
RE := TRegExpr.Create;
try
RE.Expression := '^(\d\d\d\d)/(\d\d)/(\d\d)T(\d\d):(\d\d):(\d\d)$';
if RE.Exec( Value ) then
begin
try
Result := EncodeDate( StrToInt( RE.Match[1] ),
StrToInt( RE.Match[2] ),
StrToInt( RE.Match[3] ) ) +
EncodeTime( StrToInt( RE.Match[4] ),
StrToInt( RE.Match[5] ),
StrToInt( RE.Match[6] ),
0 )
except
raise EConvertError.Create( 'Invalid date-time: ' + Value )
end
end
else
raise EConvertError.Create( 'Bad format: ' + Value )
finally
RE.Free
end
end;
I am not sure about what you want. I don't use Delphi 5 any more but I am pretty sure the function StrToDateTime exists in it. Using it you can convert a string to TDateTime with format settings. Then you can convert such TDateTime to any format using FormatDateTime which enables you to use any date format you wish.
I would go the other way around it. As I see it, you have about two options of wich you mentioned one yourself
Adjust the ShortDateFormat and keep every acces to it synchronized.
If you know the format of the strings you are recieving (somehow, you'll have to), just do some string juggling to first get your strings in your current shortdateformat. After that, convert the (juggled) string to a TDateTime.
I do wonder how you are going to determine the format for say 04/05/2010.
program DateTimeConvert;
{$APPTYPE CONSOLE}
uses
SysUtils;
function GetPart(const part, input, format: string): string;
var
I: Integer;
begin
for I := 1 to Length(format) do
if Uppercase(format[I]) = Uppercase(part) then
Result := Result + input[I];
end;
function GetDay(const input, format: string): string;
begin
Result := GetPart('d', input, format);
if Length(Result) = 1 then Result := SysUtils.Format('0%0:s', [Result]);
end;
function GetMonth(const input, format: string): string;
begin
Result := GetPart('m', input, format);
if Length(Result) = 1 then Result := SysUtils.Format('0%0:s', [Result]);
end;
function GetYear(const input, format: string): string;
begin
Result := GetPart('y', input, format);
end;
function ConvertToMyLocalSettings(const input, format: string): string;
begin
Result := SysUtils.Format('%0:s/%1:s/%2:s', [GetDay(input, format), GetMonth(input, format), GetYear(input, format)]);
end;
begin
Writeln(ConvertToMyLocalSettings('05/04/2010', 'dd/mm/yyyy'));
Writeln(ConvertToMyLocalSettings('05-04-2010', 'dd-mm-yyyy'));
Writeln(ConvertToMyLocalSettings('5-4-2010', 'd-m-yyyy'));
Writeln(ConvertToMyLocalSettings('4-5-2010', 'm-d-yyyy'));
Writeln(ConvertToMyLocalSettings('4-05-2010', 'M-dd-yyyy'));
Writeln(ConvertToMyLocalSettings('05/04/2010', 'dd/MM/yyyy'));
Readln;
end.
Related
delphi get hour&minutes difference in specific format
Was playing with dateutils and did some experimenting. procedure TForm1.Button1Click(Sender: TObject); var fromDate, toDate : TDateTime; begin fromDate := cxDateEdit1.Date ; toDate := cxDateEdit2.Date ; Label1.Caption := 'Hour difference '+IntToStr(HoursBetween(toDate, fromDate))+' hours'; Label2.Caption := 'Minute difference '+IntToStr(MinutesBetween(toDate, fromDate))+' minutes'; end; How can I get a time difference result in a label caption like hh/mm (example 01:05) ???
A TDateTime is intended to be used with absolute dates and times. Instead you might consider TTimeSpan from the System.TimeSpan unit. uses System.TimeSpan; .... var d1, d2: TDateTime; Span: TTimeSpan; str: string; .... d1 := ...; d2 := ...; Span := TTimeSpan.Subtract(d2, d1); str := Format('%.2d:%.2d', [Span.Hours, Span.Minutes])); This assumes that the span is less than a day. But then the format of your output seems to build in that very assumption. Whether or not this is really any better than simply subtracting two date time values I am not so sure.
SysUtils.FormatDateTime has many useful TDateTime to string conversions: Label3.Caption := 'Time difference [hh:mm] '+FormatDateTime('hh:nn',toDate-fromDate); As an alternative, use the result from MinutesBetween: var minutes: Integer; ... minutes := MinutesBetween(toDate,FromDate); Label3.Caption := 'Time difference [hh:mm] '+Format('%.2d:%.2d',[minutes div 60,minutes mod 60]);
why does StrToDateTime is throwing exception [duplicate]
Is there any way in Delphi 5 to convert a string to a TDateTime where you can specify the actual format to use? I'm working on a jobprocessor, which accepts tasks from various workstations. The tasks have a range of parameters, some of which are dates, but (unfortunately, and out of my control) they're passed as strings. Since the jobs can come from different workstations, the actual datetime format used to format the dates as a string might (and, of course, actual do) differ. Googling around, the only quick solutions I found was to sneakily change the ShortDateFormat variable, and restore it to its original value afterwards. Since ShortDateFormat is a global variable, and I'm working in a threaded environment the only way this would work is by synchronizing every access to it, which is completely unacceptable (and undoable). I could copy the library code from the SysUtils unit into my own methods, and tweak them to work with a specified format instead of the global variables, but I'm just wondering if there's something more adequate out there that I missed. UPDATE To put it more succinctly: I need something like StrToDate (or StrToDateTime), with the added option of specifying the exact format it should use to convert the string to a TDateTime.
Use VarToDateTime instead. It supports many more date formats in the string and converts them automatically. var DateVal: TDateTime; begin DateVal := VarToDateTime('23 Sep 2010'); ShowMessage(DateToStr(DateVal)); end; I see you're using Delphi 5. Some versions of Delphi will need to add Variants to the uses clause; most later versions add it for you. I don't remember which category Delphi 5 fell into.
I created such routine for FreePascal's dateutils unit, and it should easy to port, if porting is needed at all. Code: http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/packages/rtl-objpas/src/inc/dateutil.inc?revision=30628&view=co (code is the last (huge) procedure at the end of the file) documentation: http://www.freepascal.org/docs-html/rtl/dateutils/scandatetime.html Note that it is not a complete inverse of formatdatetime, and it has some extensions: An inverse of FormatDateTime is not 100% an inverse, simply because one can put e.g. time tokens twice in the format string,and scandatetime wouldn't know which time to pick. Strings like hn can't be reversed safely. E.g. 1:2 (2 minutes after 1) delivers 12 which is parsed as 12:00 and then misses chars for the "n" part. trailing characters are ignored. no support for Eastern Asian formatting characters since they are windows only. no MBCS support. Extensions #9 eats whitespace. whitespace at the end of a pattern is optional. ? matches any char. Quote the above chars to really match the char. (I believe these comments are slightly outdated in the sense hat some Asian support was added later but I am not sure)
Later versions of Delphi can take an extra TFormatSettings argument to the string conversion functions. TFormatSettings is a structure containing the various format global variables (ShortDateFormat, LongDateFormat, etc). So you can override those value in a thread-safe manner and even for a single call. I don't remember in which version of Delphi this was introduced, but I'm pretty sure it was after Delphi 5. So yeah, as far as I know, you either need to synchronize every access to ShortDateFormat, or use a different function.
Here's the function, its two helpers, and all the code, I wrote to parse a string using an exact datetime format: class function TDateTimeUtils.TryStrToDateExact(const S, DateFormat: string; PivotYear: Integer; out Value: TDateTime): Boolean; var Month, Day, Year: Integer; Tokens: TStringDynArray; CurrentToken: string; i, n: Integer; Partial: string; MaxValue: Integer; nCurrentYear: Integer; function GetCurrentYear: Word; var y, m, d: Word; begin DecodeDate(Now, y, m, d); Result := y; end; begin Result := False; { M/dd/yy Valid pictures codes are d Day of the month as digits without leading zeros for single-digit days. dd Day of the month as digits with leading zeros for single-digit days. ddd Abbreviated day of the week as specified by a LOCALE_SABBREVDAYNAME* value, for example, "Mon" in English (United States). Windows Vista and later: If a short version of the day of the week is required, your application should use the LOCALE_SSHORTESTDAYNAME* constants. dddd Day of the week as specified by a LOCALE_SDAYNAME* value. M Month as digits without leading zeros for single-digit months. MM Month as digits with leading zeros for single-digit months. MMM Abbreviated month as specified by a LOCALE_SABBREVMONTHNAME* value, for example, "Nov" in English (United States). MMMM Month as specified by a LOCALE_SMONTHNAME* value, for example, "November" for English (United States), and "Noviembre" for Spanish (Spain). y Year represented only by the last digit. yy Year represented only by the last two digits. A leading zero is added for single-digit years. yyyy Year represented by a full four or five digits, depending on the calendar used. Thai Buddhist and Korean calendars have five-digit years. The "yyyy" pattern shows five digits for these two calendars, and four digits for all other supported calendars. Calendars that have single-digit or two-digit years, such as for the Japanese Emperor era, are represented differently. A single-digit year is represented with a leading zero, for example, "03". A two-digit year is represented with two digits, for example, "13". No additional leading zeros are displayed. yyyyy Behaves identically to "yyyy". g, gg Period/era string formatted as specified by the CAL_SERASTRING value. The "g" and "gg" format pictures in a date string are ignored if there is no associated era or period string. PivotYear The maximum year that a 1 or 2 digit year is assumed to be. The Microsoft de-factor standard for y2k is 2029. Any value greater than 29 is assumed to be 1930 or higher. e.g. 2029: 1930, ..., 2000, 2001,..., 2029 If the PivotYear is between 0 and 99, then PivotYear is assumed to be a date range in the future. e.g. (assuming this is currently 2010): Pivot Range 0 1911..2010 (no future years) 1 1912..2011 ... 98 2009..2108 99 2010..2099 (no past years) 0 ==> no years in the future 99 ==> no years in the past } if Length(S) = 0 then Exit; if Length(DateFormat) = 0 then Exit; Month := -1; Day := -1; Year := -1; Tokens := TDateTimeUtils.TokenizeFormat(DateFormat); n := 1; //input string index for i := Low(Tokens) to High(Tokens) do begin CurrentToken := Tokens[i]; if CurrentToken = 'MMMM' then begin //Long month names, we don't support yet (you're free to write it) Exit; end else if CurrentToken = 'MMM' then begin //Short month names, we don't support yet (you're free to write it) Exit; end else if CurrentToken = 'MM' then begin //Month, with leading zero if needed if not ReadDigitString(S, n, 2{MinDigits}, 2{MaxDigits}, 1{MinValue}, 12{MaxValue}, {var}Month) then Exit; end else if CurrentToken = 'M' then begin //months if not ReadDigitString(S, n, 1{MinDigits}, 2{MaxDigits}, 1{MinValue}, 12{MaxValue}, {var}Month) then Exit; end else if CurrentToken = 'dddd' then begin Exit; //Long day names, we don't support yet (you're free to write it) end else if CurrentToken = 'ddd' then begin Exit; //Short day names, we don't support yet (you're free to write it); end else if CurrentToken = 'dd' then begin //If we know what month it is, and even better if we know what year it is, limit the number of valid days to that if (Month >= 1) and (Month <= 12) then begin if Year > 0 then MaxValue := MonthDays[IsLeapYear(Year), Month] else MaxValue := MonthDays[True, Month]; //we don't know the year, assume it's a leap year to be more generous end else MaxValue := 31; //we don't know the month, so assume it's the largest if not ReadDigitString(S, n, 2{MinDigits}, 2{MaxDigits}, 1{MinValue}, MaxValue{MaxValue}, {var}Day) then Exit; end else if CurrentToken = 'd' then begin //days //If we know what month it is, and even better if we know what year it is, limit the number of valid days to that if (Month >= 1) and (Month <= 12) then begin if Year > 0 then MaxValue := MonthDays[IsLeapYear(Year), Month] else MaxValue := MonthDays[True, Month]; //we don't know the year, assume it's a leap year to be more generous end else MaxValue := 31; //we don't know the month, so assume it's the largest if not ReadDigitString(S, n, 1{MinDigits}, 2{MaxDigits}, 1{MinValue}, MaxValue{MaxValue}, {var}Day) then Exit; end else if (CurrentToken = 'yyyy') or (CurrentToken = 'yyyyy') then begin //Year represented by a full four or five digits, depending on the calendar used. { Thai Buddhist and Korean calendars have five-digit years. The "yyyy" pattern shows five digits for these two calendars, and four digits for all other supported calendars. Calendars that have single-digit or two-digit years, such as for the Japanese Emperor era, are represented differently. A single-digit year is represented with a leading zero, for example, "03". A two-digit year is represented with two digits, for example, "13". No additional leading zeros are displayed. } if not ReadDigitString(S, n, 4{MinDigits}, 4{MaxDigits}, 0{MinValue}, 9999{MaxValue}, {var}Year) then Exit; end else if CurrentToken = 'yyy' then begin //i'm not sure what this would look like, so i'll ignore it Exit; end else if CurrentToken = 'yy' then begin //Year represented only by the last two digits. A leading zero is added for single-digit years. if not ReadDigitString(S, n, 2{MinDigits}, 2{MaxDigits}, 0{MinValue}, 99{MaxValue}, {var}Year) then Exit; nCurrentYear := GetCurrentYear; Year := (nCurrentYear div 100 * 100)+Year; if (PivotYear < 100) and (PivotYear >= 0) then begin //assume pivotyear is a delta from this year, not an absolute value PivotYear := nCurrentYear+PivotYear; end; //Check the pivot year value if Year > PivotYear then Year := Year - 100; end else if CurrentToken = 'y' then begin //Year represented only by the last digit. if not ReadDigitString(S, n, 1{MinDigits}, 1{MaxDigits}, 0{MinValue}, 9{MaxValue}, {var}Year) then Exit; nCurrentYear := GetCurrentYear; Year := (nCurrentYear div 10 * 10)+Year; if (PivotYear < 100) and (PivotYear >= 0) then begin //assume pivotyear is a delta from this year, not an absolute value PivotYear := nCurrentYear+PivotYear; end; //Check the pivot year value if Year > PivotYear then Year := Year - 100; end else begin //The input string should contains CurrentToken starting at n Partial := Copy(S, n, Length(CurrentToken)); Inc(n, Length(CurrentToken)); if Partial <> CurrentToken then Exit; end; end; //If there's still stuff left over in the string, then it's not valid if n <> Length(s)+1 then begin Result := False; Exit; end; if Day > MonthDays[IsLeapYear(Year), Month] then begin Result := False; Exit; end; try Value := EncodeDate(Year, Month, Day); except Result := False; Exit; end; Result := True; end; class function TDateTimeUtils.TokenizeFormat(fmt: string): TStringDynArray; var i: Integer; partial: string; function IsDateFormatPicture(ch: AnsiChar): Boolean; begin case ch of 'M','d','y': Result := True; else Result := False; end; end; begin SetLength(Result, 0); if Length(fmt) = 0 then Exit; //format is only one character long? If so then that's the tokenized entry if Length(fmt)=1 then begin SetLength(Result, 1); Result[0] := fmt; end; partial := fmt[1]; i := 2; while i <= Length(fmt) do begin //If the characters in partial are a format picture, and the character in fmt is not the same picture code then write partial to result, and reset partial if IsDateFormatPicture(partial[1]) then begin //if the current fmt character is different than the running partial picture if (partial[1] <> fmt[i]) then begin //Move the current partial to the output //and start a new partial SetLength(Result, Length(Result)+1); Result[High(Result)] := partial; Partial := fmt[i]; end else begin //the current fmt character is more of the same format picture in partial //Add it to the partial Partial := Partial + fmt[i]; end; end else begin //The running partial is not a format picture. //If the current fmt character is a picture code, then write out the partial and start a new partial if IsDateFormatPicture(fmt[i]) then begin //Move the current partial to the output //and start a new partial SetLength(Result, Length(Result)+1); Result[High(Result)] := partial; Partial := fmt[i]; end else begin //The current fmt character is another non-picture code. Add it to the running partial Partial := Partial + fmt[i]; end; end; Inc(i); Continue; end; //If we have a running partial, then add it to the output if partial <> '' then begin SetLength(Result, Length(Result)+1); Result[High(Result)] := partial; end; end; class function TDateTimeUtils.ReadDigitString(const S: string; var Pos: Integer; MinDigits, MaxDigits: Integer; MinValue, MaxValue: Integer; var Number: Integer): Boolean; var Digits: Integer; Value: Integer; Partial: string; CandidateNumber: Integer; CandidateDigits: Integer; begin Result := False; CandidateNumber := -1; CandidateDigits := 0; Digits := MinDigits; while Digits <= MaxDigits do begin Partial := Copy(S, Pos, Digits); if Length(Partial) < Digits then begin //we couldn't get all we wanted. We're done; use whatever we've gotten already Break; end; //Check that it's still a number if not TryStrToInt(Partial, Value) then Break; //Check that it's not too big - meaning that getting anymore wouldn't work if (Value > MaxValue) then Break; if (Value >= MinValue) then begin //Hmm, looks good. Keep it as our best possibility CandidateNumber := Value; CandidateDigits := Digits; end; Inc(Digits); //try to be greedy, grabbing even *MORE* digits end; if (CandidateNumber >= 0) or (CandidateDigits > 0) then begin Inc(Pos, CandidateDigits); Number := CandidateNumber; Result := True; end; end;
If you want to know how this was solved in later Delphi's, you can take a look at the source of a slightly more modern (looks like Delphi 6) sysutils.pas here: http://anygen.googlecome.com/.../SysUtils.pas Check out the overloaded versions of StrToDateTime that take a TFormatSettings parameter. function StrToDateTime(const S: string; const FormatSettings: TFormatSettings): TDateTime; overload;
Use RegExpr library (https://github.com/masterandrey/TRegExpr) var RE: TRegExpr; begin RE := TRegExpr.Create; try RE.Expression := '^(\d\d\d\d)/(\d\d)/(\d\d)T(\d\d):(\d\d):(\d\d)$'; if RE.Exec( Value ) then begin try Result := EncodeDate( StrToInt( RE.Match[1] ), StrToInt( RE.Match[2] ), StrToInt( RE.Match[3] ) ) + EncodeTime( StrToInt( RE.Match[4] ), StrToInt( RE.Match[5] ), StrToInt( RE.Match[6] ), 0 ) except raise EConvertError.Create( 'Invalid date-time: ' + Value ) end end else raise EConvertError.Create( 'Bad format: ' + Value ) finally RE.Free end end;
I am not sure about what you want. I don't use Delphi 5 any more but I am pretty sure the function StrToDateTime exists in it. Using it you can convert a string to TDateTime with format settings. Then you can convert such TDateTime to any format using FormatDateTime which enables you to use any date format you wish.
I would go the other way around it. As I see it, you have about two options of wich you mentioned one yourself Adjust the ShortDateFormat and keep every acces to it synchronized. If you know the format of the strings you are recieving (somehow, you'll have to), just do some string juggling to first get your strings in your current shortdateformat. After that, convert the (juggled) string to a TDateTime. I do wonder how you are going to determine the format for say 04/05/2010. program DateTimeConvert; {$APPTYPE CONSOLE} uses SysUtils; function GetPart(const part, input, format: string): string; var I: Integer; begin for I := 1 to Length(format) do if Uppercase(format[I]) = Uppercase(part) then Result := Result + input[I]; end; function GetDay(const input, format: string): string; begin Result := GetPart('d', input, format); if Length(Result) = 1 then Result := SysUtils.Format('0%0:s', [Result]); end; function GetMonth(const input, format: string): string; begin Result := GetPart('m', input, format); if Length(Result) = 1 then Result := SysUtils.Format('0%0:s', [Result]); end; function GetYear(const input, format: string): string; begin Result := GetPart('y', input, format); end; function ConvertToMyLocalSettings(const input, format: string): string; begin Result := SysUtils.Format('%0:s/%1:s/%2:s', [GetDay(input, format), GetMonth(input, format), GetYear(input, format)]); end; begin Writeln(ConvertToMyLocalSettings('05/04/2010', 'dd/mm/yyyy')); Writeln(ConvertToMyLocalSettings('05-04-2010', 'dd-mm-yyyy')); Writeln(ConvertToMyLocalSettings('5-4-2010', 'd-m-yyyy')); Writeln(ConvertToMyLocalSettings('4-5-2010', 'm-d-yyyy')); Writeln(ConvertToMyLocalSettings('4-05-2010', 'M-dd-yyyy')); Writeln(ConvertToMyLocalSettings('05/04/2010', 'dd/MM/yyyy')); Readln; end.
Delphi - Random Combination (Math)
I have a BIG problem here and do not even know how to start... In short explanation, I need to know if a number is in a set of results from a random combination... Let me explain better: I created a random "number" with 3 integer chars from 1 to 8, like this: procedure TForm1.btn1Click(Sender: TObject); var cTmp: Char; sTmp: String[3]; begin sTmp := ''; While (Length(sTmp) < 3) Do Begin Randomize; cTmp := IntToStr(Random(7) + 1)[1]; If (Pos(cTmp, sTmp) = 0) Then sTmp := sTmp + cTmp; end; edt1.Text := sTmp; end; Now I need to know is some other random number, let's say "324" (example), is in the set of results of that random combination. Please, someone can help? A link to get the equations to solve this problem will be enough... Ok, let me try to add some useful information: Please, first check this link https://en.wikipedia.org/wiki/Combination Once I get some number typed by user, in an editbox, I need to check if it is in the set of this random combination: S = (1..8) and k = 3 Tricky, hum? Here is what I got. Maybe it be usefull for someone in the future. Thank you for all people that tried to help! Function IsNumOnSet(const Min, Max, Num: Integer): Boolean; var X, Y, Z: Integer; Begin Result := False; For X := Min to Max Do For Y := Min to Max Do For Z := Min to Max Do If (X <> Y) and (X <> Z) and (Y <> Z) Then If (X * 100 + Y * 10 + Z = Num) Then Begin Result := True; Exit; end; end;
You want to test whether something is a combination. To do this you need to verify that the putative combination satisfies the following conditions: Each element is in the range 1..N and No element appears more than once. So, implement it like this. Declare an array of counts, say array [1..N] of Integer. If N varies at runtime you will need a dynamic array. Initialise all members of the array to zero. Loop through each element of the putative combination. Check that the element is in the range 1..N. And increment the count for that element. If any element has a count greater than 1 then this is not a valid combination. Now you can simplify by replacing the array of integers with an array of booleans but that should be self evident.
You have your generator. Once your value is built, do something like function isValidCode( Digits : Array of Char; Value : String ) : Boolean; var nI : Integer; begin for nI := 0 to High(Digits) do begin result := Pos(Digits[nI], Value ) > 0; if not result then break; end; end; Call like this... isValidCode(["3","2","4"], RandomValue); Note : it works only because you have unique digits, the digit 3 is only once in you final number. For something more generic, you'll have to tweak this function. (testing "3","3","2" would return true but it would be false !) UPDATED : I dislike the nested loop ^^. Here is a function that return the nTh digit of an integer. It will return -1 if the digits do not exists. : function TForm1.getDigits(value : integer; ndigits : Integer ) : Integer; var base : Integer; begin base := Round(IntPower( 10, ndigits-1 )); result := Trunc( value / BASE ) mod 10; end; nDigits is the digits number from right to left starting at 1. It will return the value of the digit. GetDigits( 234, 1) returns 4 GetDigits( 234, 2) returns 3 GetDigits( 234, 3) returns 2. GetDigits( 234, 4) returns 0. Now this last function checks if a value is a good combination, specifying the maxdigits you're looking for : function isValidCombination( value : integer; MinVal, MaxVal : Integer; MaxDigits : Integer ) : Boolean; var Buff : Array[0..9] of Integer; nI, digit: Integer; begin ZeroMemory( #Buff, 10*4); // Store the count of digits for for nI := 1 to MaxDigits do begin digit := getDigits(value, nI); Buff[digit] := Buff[digit] + 1; end; // Check if the value is more than the number of digits. if Value >= Round(IntPower( 10, MaxDigits )) then begin result := False; exit; end; // Check if the value has less than MaxDigits. if Value < Round(IntPower( 10, MaxDigits-1 )) then begin result := False; exit; end; result := true; for nI := 0 to 9 do begin // Exit if more than One occurence of digit. result := Buff[nI] < 2 ; if not result then break; // Check if digit is present and valid. result := (Buff[nI] = 0) or InRange( nI, MinVal, MaxVal ); if not result then break; end; end;
Question does not seem too vague to me, Maybe a bit poorly stated. From what I understand you want to check if a string is in a set of randomly generated characters. Here is how that would work fastest, keep a sorted array of all letters and how many times you have each letter. Subtract each letter from the target string If any value in the sorted int array goes under 0 then that means the string can not be made from those characters. I made it just work with case insensitive strings but it can easily be made to work with any string by making the alphabet array 255 characters long and not starting from A. This will not allow you to use characters twice like the other example so 'boom' is not in 'b' 'o' 'm' Hope this helps you. function TForm1.isWordInArray(word: string; arr: array of Char):Boolean; var alphabetCount: array[0..25] of Integer; i, baseval, position : Integer; s: String; c: Char; begin for i := 0 to 25 do alphabetCount[i] := 0; // init alphabet s := UpperCase(word); // make string uppercase baseval := Ord('A'); // count A as the 0th letter for i := 0 to Length(arr)-1 do begin // disect array and build alhabet c := UpCase(arr[i]); // get current letter inc(alphabetCount[(Ord(c)-baseval)]); // add 1 to the letter count for that letter end; for i := 1 to Length(s) do begin // disect string c := s[i]; // get current letter position := (Ord(c)-baseval); if(alphabetCount[position]>0) then // if there is still latters of that kind left dec(alphabetCount[position]) // delete 1 to the letter count for that letter else begin // letternot there!, exit with a negative result Result := False; Exit; end; end; Result := True; // all tests where passed, the string is in the array end; implemented like so: if isWordInArray('Delphi',['d','l','e','P','i','h']) then Caption := 'Yup' else Caption := 'Nope'; //yup if isWordInArray('boom',['b','o','m']) then Caption := 'Yup' else Caption := 'Nope'; //nope, a char can only be used once Delphi rocks!
begin Randomize; //only need to execute this once. sTmp := ''; While (Length(sTmp) < 3) Do Begin cTmp := IntToStr(Random(7) + 1)[1]; // RANDOM(7) produces # from 0..6 // so result will be '1'..'7', not '8' // Alternative: clmp := chr(48 + random(8)); If (Pos(cTmp, sTmp) = 0) Then sTmp := sTmp + cTmp; IF SLMP = '324' THEN DOSOMETHING; // don't know what you actually want to do // Perhaps SET SLMP=''; to make sure '324' // isn't generated? end; edt1.Text := sTmp; end;
Elapsed/time interval in Delphi?
I need to calculate the elapsed time (nicely formatted) between now and a file's last modification date/time, ie. something like this, only in my case, the difference can be in days, months or even years. I tried this: var TimeDiff : Double; begin TimeDiff := Now - FileAgeEx('C:\my-file.txt'); if (TimeDiff >= 1) then Caption := FormatDateTime('dd hh:nn:ss', TimeDiff) else Caption := FormatDateTime('hh:nn:ss', TimeDiff); end; But (1) it doesn't work and (2) I'd like a better formatting. Ultimately my goal is to have something like this: Time Diff < 1 day ==> display this: 12:00:01 Time Diff >= 1 day ==> display this: 25 days, 12:00:01 Time Diff >= 1 year ==> display this: 2 years, 3 months, 10 days, 12:00:01 Anyone knows how can I do that? Thanks!
The main problem would appear to be getting hold of the last modified time of the file. I use the following code: function LastWriteTime(const FileName: string): TFileTime; var AttributeData: TWin32FileAttributeData; begin if not GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, #AttributeData) then RaiseLastOSError; Result := AttributeData.ftLastWriteTime; end; function UTCFileTimeToSystemTime(const FileTime: TFileTime): TSystemTime; //returns equivalent time in current locality, taking account of daylight saving var LocalFileTime: Windows.TFileTime; begin Windows.FileTimeToLocalFileTime(FileTime, LocalFileTime); Windows.FileTimeToSystemTime(LocalFileTime, Result); end; function UTCFileTimeToDateTime(const FileTime: TFileTime): TDateTime; begin Result := SystemTimeToDateTime(UTCFileTimeToSystemTime(FileTime)); end; You call LastWriteTime to get the last modified time in file time format. Then call UTCFileTimeToDateTime to convert into TDateTime accounting for the prevailing local time zone of the machine. You can then compare that value with Now. As regards the formatting, you already appear to know how to do that. You basic approach will work and you just need to flesh out the details. In the comments you say that FormatDateTime('dd hh:nn:ss', 2.9); shows a 1 for the day when you would expect a 2. The problem is that this function formats dates rather than time intervals. The value 2.9 is not treated as an elapsed time, rather it is treated as an absolute date/time, 2.9 days after the Delphi epoch. I would use Trunc and Frac to obtain number of days, and the part of days respectively, and work from there. Days := Trunc(TimeDiff); Time := Frac(TimeDiff); The following code, extracted directly from my codebase, may give you some pointers. Note that its input is in seconds, but it should set you on the right path. function CorrectPlural(const s: string; Count: Integer): string; begin Result := IntToStr(Count) + ' ' + s; if Count<>1 then begin Result := Result + 's'; end; end; function HumanReadableTime(Time: Double): string; //Time is in seconds const SecondsPerMinute = 60; SecondsPerHour = 60*SecondsPerMinute; SecondsPerDay = 24*SecondsPerHour; SecondsPerWeek = 7*SecondsPerDay; SecondsPerYear = 365*SecondsPerDay; var Years, Weeks, Days, Hours, Minutes, Seconds: Int64; begin Try Years := Trunc(Time/SecondsPerYear); Time := Time - Years*SecondsPerYear; Weeks := Trunc(Time/SecondsPerWeek); Time := Time - Weeks*SecondsPerWeek; Days := Trunc(Time/SecondsPerDay); Time := Time - Days*SecondsPerDay; Hours := Trunc(Time/SecondsPerHour); Time := Time - Hours*SecondsPerHour; Minutes := Trunc(Time/SecondsPerMinute); Time := Time - Minutes*SecondsPerMinute; Seconds := Trunc(Time); if Years>5000 then begin Result := IntToStr(Round(Years/1000))+' millennia'; end else if Years>500 then begin Result := IntToStr(Round(Years/100))+' centuries'; end else if Years>0 then begin Result := CorrectPlural('year', Years) + ' ' + CorrectPlural('week', Weeks); end else if Weeks>0 then begin Result := CorrectPlural('week', Weeks) + ' ' + CorrectPlural('day', Days); end else if Days>0 then begin Result := CorrectPlural('day', Days) + ' ' + CorrectPlural('hour', Hours); end else if Hours>0 then begin Result := CorrectPlural('hour', Hours) + ' ' + CorrectPlural('minute', Minutes); end else if Minutes>0 then begin Result := CorrectPlural('minute', Minutes); end else begin Result := CorrectPlural('second', Seconds); end; Except Result := 'an eternity'; End; end;
Validate a real date
Is there some function that can validate a date in format aaaa/mm/gg (ccyy/mm/dd) returning True if it is valid or False otherwise? I mean about really valid date, not only to level to syntax.
Is 'aaaa' year and 'gg' day? var MyString: string; MyDate: TDateTime; settings: TFormatSettings; begin settings.ShortDateFormat := 'yyyy/mm/dd'; settings.DateSeparator := '/'; MyString := '2011/15/15'; if TryStrToDateTime(MyString, MyDate, settings) then Label1.Caption := 'correct date' else Label1.Caption := 'incorrect'; end;
This is very fast, because the most simple errors are caught first. function IsValidDate(const S: string): boolean; var y, m, d: Integer; const DAYS_OF_MONTH: array[1..12] of integer = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); begin result := false; if length(S) <> 10 then Exit; if (S[5] <> '/') or (S[8] <> '/') then Exit; if not TryStrToInt(Copy(S, 1, 4), y) then Exit; if not TryStrToInt(Copy(S, 6, 2), m) then Exit; if not InRange(m, 1, 12) then Exit; if not TryStrToInt(Copy(S, 9, 2), d) then Exit; if not InRange(d, 1, DAYS_OF_MONTH[m]) then Exit; if (not IsLeapYear(y)) and (m = 2) and (d = 29) then Exit; result := true; end;
Use the overloaded version of StrToDate() that has a TFormatSettings parameter. Then you can pass in the desired format string for parsing, and it will return a TDateTime after validating the parsed values.
Trying to do the same thing and ran across this old thread. I ended up writing my own function and thought that I'd post it. How about this? function IsValidDate(const S: string): boolean; var TestDate : tdatetime; begin Result := False; if (LastDelimiter('/',S) >= 4) and (Length(S)-LastDelimiter('/',S) >= 4) then Result := TryStrToDate(S,TestDate); end; First off, I check to see if the second delimiter (/) is at least out there far enough to represent both a day and a month (4th position). I then force a 4 digit year on them with the next line. Change this second test to >=2 for a two digit, but I just figure that it's not so bad to force a four digit year - it's only two more strokes. Finally, I test with TryStrToDate(). If there is only one delimiter, or if it's not a valid date, it will be caught here. If you wanted to get fancy, you could check if the year was within the last 10 years or something as well. Just add: Result := Result and (Now - TestDate < 3650); Dave