基于任意格式将字符串转换为TDateTime

Wil*_*mpt 21 delphi delphi-5

在Delphi 5中是否有任何方法可以将字符串转换为TDateTime,您可以在其中指定要使用的实际格式?

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).

我可以将SysUtils单元中的库代码复制到我自己的方法中,并调整它们以使用指定的格式而不是全局变量,但我只是想知道是否有更多的东西我错过了.

亲切的问候,并提前感谢,

威廉

UPDATE

更简洁地说:

我需要类似StrToDate(或StrToDateTime)的东西,添加选项指定它应该用于将字符串转换为TDateTime的确切格式.

Ken*_*ite 30

请改用VarToDateTime.它支持字符串中的更多日期格式并自动转换它们.

var
  DateVal: TDateTime;
begin
  DateVal := VarToDateTime('23 Sep 2010');
  ShowMessage(DateToStr(DateVal));
end;
Run Code Online (Sandbox Code Playgroud)

我看到你正在使用Delphi 5.某些版本的Delphi需要在uses子句中添加Variants; 大多数更高版本为您添加它.我不记得Delphi 5属于哪个类别.


Mar*_*ort 8

我为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(1之后2分钟)提供12,其被解析为12:00,然后错过了"n"部分的字符.

    • 尾随字符被忽略.
    • 不支持东亚格式字符,因为它们只是Windows.
    • 没有MBCS支持.
  • 扩展

    • #9吃空白.
    • 模式末尾的空格是可选的.
    • ?匹配任何char.
    • 引用上面的字符来真正匹配char.

(我相信这些评论在某种程度上已经过时了一些亚洲支持,但我不确定)


Ken*_*ssa 7

更高版本的Delphi可以为字符串转换函数提供额外的TFormatSettings参数.TFormatSettings是一个包含各种格式全局变量(ShortDateFormat,LongDateFormat等)的结构.因此,您可以以线程安全的方式覆盖这些值,甚至是单个调用.

我不记得引入了哪个版本的Delphi,但我很确定它是在Delphi 5之后.

所以是的,据我所知,您需要同步每次访问ShortDateFormat,或使用不同的函数.


Ian*_*oyd 6

这是函数及其两个帮助程序,我编写了使用精确的日期时间格式解析字符串.而且由于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'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;
Run Code Online (Sandbox Code Playgroud)