Преобразование строки в TDateTime на основе произвольного формата
Есть ли способ в Delphi 5 преобразовать строку в TDateTime, где вы можете указать фактический формат для использования?
Я работаю над jobprocessor, который принимает задачи с разных рабочих станций. Задачи имеют ряд параметров, некоторые из которых являются датами, но (к сожалению, и вне моего контроля) они передаются как строки. Поскольку рабочие места могут выполняться с разных рабочих станций, фактический формат даты и времени, используемый для форматирования дат в виде строки, может (и, конечно, фактический) различаться.
Поиск в Google, единственные быстрые решения, которые я нашел, заключались в том, чтобы с легкостью изменить переменную ShortDateFormat
и впоследствии восстановить ее исходное значение. Поскольку ShortDateFormat
- глобальная переменная, и я работаю в потоковой среде, единственным способом это будет работать, синхронизируя каждый доступ к ней, что совершенно неприемлемо (и отменено).
Я мог бы скопировать библиотечный код из модуля SysUtils
в свои собственные методы и настроить их для работы с указанным форматом вместо глобальных переменных, но мне просто интересно, есть ли там что-то более адекватное, чтобы я пропущенный.
С уважением, и спасибо заранее,
Виллем
UPDATE
Чтобы сделать это более кратко:
Мне нужно что-то вроде StrToDate
(или StrToDateTime
) с добавленной опцией указания точного формата, который он должен использовать для преобразования строки в TDateTime.
Ответы
Ответ 1
Я создал такую процедуру для FreePascal dateutils, и ее легко переносить, если вообще требуется перенос.
код:
http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/packages/rtl-objpas/src/inc/dateutil.inc?revision=30628&view=co
(код - последняя (огромная) процедура в конце файла)
Документация:
http://www.freepascal.org/docs-html/rtl/dateutils/scandatetime.html
Обратите внимание, что это не полный обратный формат formatdatetime, и он имеет некоторые расширения:
-
Обратный формат FormatDateTime не является на 100% обратным, просто потому, что можно положить, например. временные токены дважды в строке формата, а scandatetime не знает, какое время выбрать.
-
Строки, такие как hn, нельзя безопасно отменить. Например. 1: 2 (через 2 минуты после 1) доставляет 12, которые анализируются как 12:00, а затем пропускает символы для "n" части.
- завершающие символы игнорируются.
- нет поддержки восточноазиатских символов форматирования, поскольку они являются только окнами.
- нет поддержки MBCS.
-
Расширения
- # 9 ест пробел.
- пробел в конце шаблона является необязательным.
- ? соответствует любому char.
- Введите приведенные выше символы, чтобы действительно соответствовать char.
(Я считаю, что эти комментарии немного устарели в том смысле, что какая-то азиатская поддержка была добавлена позже, но я не уверен)
Ответ 2
Вместо этого используйте VarToDateTime. Он поддерживает еще много форматов даты в строке и автоматически преобразует их.
var
DateVal: TDateTime;
begin
DateVal := VarToDateTime('23 Sep 2010');
ShowMessage(DateToStr(DateVal));
end;
Я вижу, что вы используете Delphi 5. Некоторым версиям Delphi необходимо будет добавить Variants к предложению uses; более поздние версии добавят его для вас. Я не помню, в какую категорию попала Delphi 5.
Ответ 3
Более поздние версии Delphi могут принимать дополнительный аргумент TFormatSettings для функций преобразования строк. TFormatSettings - это структура, содержащая глобальные переменные различных форматов (ShortDateFormat, LongDateFormat и т.д.). Таким образом, вы можете переопределить это значение поточно-безопасным способом и даже для одного вызова.
Я не помню, в какой версии Delphi это было введено, но я уверен, что это было после Delphi 5.
Итак, да, насколько мне известно, вам нужно либо синхронизировать каждый доступ к ShortDateFormat, либо использовать другую функцию.
Ответ 4
Здесь функция и ее два помощника я написал для синтаксического анализа строки, используя формат точный datetime. А так как Stackoverflow также является кодово-вики: здесь весь код:
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 a leap year to be more generous
end
else
MaxValue := 31; //we don't know the month, so assume it 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 a leap year to be more generous
end
else
MaxValue := 31; //we don't know the month, so assume it 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 still stuff left over in the string, then it 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 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 still a number
if not TryStrToInt(Partial, Value) then
Break;
//Check that it 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;
Ответ 5
Если вы хотите знать, как это было решено в более поздних версиях Delphi, вы можете взглянуть на источник немного более современного (выглядит как Delphi 6) sysutils.pas здесь:
http://anygen.googlecome.com/.../SysUtils.pas
Проверьте перегруженные версии StrToDateTime
, которые принимают параметр TFormatSettings
.
function StrToDateTime(const S: string;
const FormatSettings: TFormatSettings): TDateTime; overload;
Ответ 6
Я не уверен, чего вы хотите. Я больше не использую Delphi 5, но я уверен, что в нем существует функция StrToDateTime. Используя его, вы можете преобразовать строку в TDateTime с настройками формата. Затем вы можете преобразовать такой TDateTime в любой формат с помощью FormatDateTime, который позволяет вам использовать любой формат даты, который вы хотите.
Ответ 7
Я бы пошел по этому пути. Как я вижу, у вас есть около двух вариантов того, что вы упомянули сами.
- Откорректируйте ShortDateFormat и сохраните синхронизацию каждого доступа к нему.
- Если вы знаете формат строк, которые вы получаете (каким-то образом, вам нужно будет), просто выполните некоторые жонглирование строк, чтобы сначала получить ваши строки в текущем shortdateformat. После этого конвертируйте (жонглированную) строку в
TDateTime
.
Мне интересно, как вы собираетесь определять формат, например, 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.
Ответ 8
Использовать библиотеку TRegExpr:
var
RE: TRegExpr;
begin
RE:= TRegExpr.Create;
RE.Expression:= '(\d\d\d\d)/(\d\d)/(\d\d)T(\d\d):(\d\d):(\d\d)';
if RE.Exec( Value) then
begin
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 );
end;
end;