Question

Est-il possible de Delphi 5 pour convertir une chaîne à un TDateTime où vous pouvez spécifier le format réel à utiliser?

Je travaille sur un jobprocessor qui accepte les tâches de différents postes de travail. Les tâches ont une gamme de paramètres, dont certains sont les dates, mais (malheureusement, et hors de mon contrôle) ils sont passés sous forme de chaînes. Étant donné que les emplois peuvent provenir de différents postes de travail, le format datetime réel utilisé pour formater les dates en tant que puissance de chaîne (et, bien sûr, réelle faire ) diffèrent.

googler autour, les seules solutions rapides que j'ai trouvé changer sneakily la variable ShortDateFormat et restaurer à sa valeur initiale après. Depuis ShortDateFormat est une variable globale, et je travaille dans un environnement enfila la seule façon cela fonctionnerait est en synchronisant tous les accès, ce qui est tout à fait inacceptable (et annulable).

Je pourrais copier le code bibliothèque de l'unité de SysUtils dans mes propres méthodes, et les modifier au travail avec un format spécifié au lieu des variables globales, mais je me demande s'il y a quelque chose de plus approprié là que j'ai raté.

Sincères salutations, et merci à l'avance,

Willem

UPDATE

Pour mettre plus succinctement:

Je besoin de quelque chose comme StrToDate (ou StrToDateTime), avec l'option supplémentaire de spécifier le format exact qu'il doit utiliser pour convertir la chaîne en un TDateTime.

Était-ce utile?

La solution

J'ai créé cette routine pour l'unité de DateUtils de FreePascal, et il devrait facilement au port, si le portage est nécessaire du tout.

Code:

http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/packages/rtl-objpas/src/inc/dateutil.inc?revision=30628&view=co

(code est la dernière procédure (énorme) à la fin du fichier)

Documentation:

http://www.freepascal.org/docs-html/ rtl / DateUtils / scandatetime.html

Notez que ce n'est pas une inverse complète de FormatDateTime, et il a quelques extensions:

  • Un inverse de FormatDateTime est pas 100% l'inverse, simplement parce que l'on peut mettre par exemple temps jetons deux fois dans la chaîne de format, et scandatetime ne sais pas quoi choisir.

  • Les chaînes comme hn ne peut pas être inversée en toute sécurité. Par exemple. 1: 2 (2 minutes après 1 livre) 12 qui est analysé comme 12:00, puis    Misses caractères pour la partie "n".

    • caractères de fin sont ignorés.
    • pas de support pour les caractères asiatiques formatage de l'Est, car ils sont Windows.
    • pas de support MBCS.
  • Extensions

    • # 9 Eats des espaces blancs.
    • espaces à la fin d'un motif est facultative.
    • ? correspond à tout omble chevalier.
    • Citer les caractères ci-dessus pour correspondre vraiment l'omble chevalier.

(Je crois que ces commentaires sont légèrement dépassées dans le chapeau sens un soutien asiatique a été ajouté plus tard, mais je ne suis pas sûr)

Autres conseils

Utilisez VarToDateTime à la place. Il supporte de nombreux formats plus de date dans la chaîne et convertit eux automatiquement.

var
  DateVal: TDateTime;
begin
  DateVal := VarToDateTime('23 Sep 2010');
  ShowMessage(DateToStr(DateVal));
end;

Je vois que vous utilisez Delphi 5. Certaines versions de Delphi devront ajouter des variantes à la clause uses; la plupart des versions ultérieures ajouter pour vous. Je ne me souviens pas de quelle catégorie Delphi 5 est tombé dans.

Les versions ultérieures de Delphi peut prendre un argument TFormatSettings supplémentaire pour les fonctions de conversion de chaînes. TFormatSettings est une structure contenant les divers formats variables globales (de ShortDateFormat, LongDateFormat, etc.). Ainsi, vous pouvez passer outre les valeur d'une manière thread-safe et même pour un seul appel.

Je ne me souviens pas dans quelle version de Delphi cela a été introduit, mais je suis sûr qu'il était après Delphi 5.

Alors oui, pour autant que je sache, vous devez soit de synchroniser tous les accès à ShortDateFormat, ou utiliser une autre fonction.

Voici la fonction, et ses deux aides, je l'ai écrit pour analyser une chaîne en utilisant un exactement datetime format. Et puisque Stackoverflow est également un code-wiki: voici tous le code:

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;

Si vous voulez savoir comment cela a été résolu dans Delphi plus tard ce, vous pouvez jeter un oeil à la source d'un peu plus moderne (qui ressemble à Delphi 6) SysUtils.pas ici

http://anygen.googlecome.com/.../SysUtils.pas

Découvrez les versions surchargées de StrToDateTime qui prennent un paramètre TFormatSettings.

function StrToDateTime(const S: string;
  const FormatSettings: TFormatSettings): TDateTime; overload;

Je ne suis pas sûr de ce que vous voulez. Je ne l'utilise Delphi 5 plus mais je suis assez sûr que la fonction existe StrToDateTime en elle. Son utilisation, vous pouvez convertir une chaîne en TDateTime avec les paramètres de format. Ensuite, vous pouvez convertir ces TDateTime en tout format à l'aide FormatDateTime qui vous permet d'utiliser un format de date que vous souhaitez.

Utilisation bibliothèque RegExpr ( 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;

J'aller dans l'autre autour d'elle. Comme je le vois, vous avez environ deux options de Wich que vous avez mentionné un vous-même

  • Régler la ShortDateFormat et de garder tous les accès à ce synchronisé.
  • Si vous connaissez le format des chaînes que vous RECEVOIR (en quelque sorte, vous devrez), juste jongler chaîne d'abord obtenir vos chaînes dans votre ShortDateFormat actuelle . Après cela, convertir la chaîne (jonglé) à un TDateTime.

Je me demande comment vous allez déterminer le format pour dire 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.
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top