Win32: Comment convertir une chaîne en date?

Sous Windows, je veux parsingr une ssortingng tant que date utilisant une chaîne de format exacte.

Par exemple, compte tenu de la chaîne

 "6/12/2010" 

et le format:

 "M/d/yyyy" 

Je veux convertir la chaîne à une date, tout en m’assurant que la date correspond au format.

Je dois aussi pouvoir spécifier la fenêtre coulissante Y2K, pivot. Cela signifie que si une année à deux chiffres est (correctement) entrée, je spécifierai ce nombre d’années que je considérerais comme l’année à venir. par exemple:

 Two-digit Year Pivot Four-digit year ============== ===== =============== 30 +0 1929 30 +18 1929 30 +19 1929 30 +20 2029 30 +21 2029 30 +100 2029 

.NET fournit déjà une fonction DateTime.ParseExact qui effectue exactement ce dont j’ai besoin:

 date = DateTime.ParseExact("6/12/2010", DateTimeFormatInfo.ShortDatePattern, Thread.CurrentThread.CurrentCulture); 

Sauf que je ne peux pas lui dire la valeur pivot de 100 ans.

Plus d’exemples:

 Ssortingng Format Specifier Date "6/7/2029" "M/d/yyyy" 6/7/2029 "6/7/29" "M/d/yyyy" (invalid, year too short) "6/7/29" "M/d/yy" 6/7/1929 (+0 pivot) "6/7/29" "M/d/yy" 6/7/2029 (+100 pivot "6/7/29" "M/d/yy" 6/7/2029 (+50 pivot) "6/7/29" "M/d/yy" 6/7/2029 "6/7/2029" "Mdyyyy" (invalid, incorrect separators) "6.7.2029" "Mdyyyy" 6/7/2029 "6.7.2029" "Md-yyyy" (invalid, incorrect separators) "6/7/2029" "M/dd/yyyy" (invalid, days requires leading zero) "6/07/2029" "M/dd/yyyy" (invalid, days requires leading zero) "6/07/2029" "MM/dd/yyyy" (invalid, months requires leading zero) "06/07/2029 "MM/dd/yyyy" 6/7/2029 "06/07/2029" "MM/d/yyyy" (invalid, days should not have leading zero) "06/7/2029" "MM/d/yyyy" 6/7/2029 

Je sais que Windows n’a pas d’API native pour convertir une chaîne à une date.

Existe-t-il un code établi pour convertir une ssortingng en date aide d’un spécificateur de format? Les ordinateurs existent depuis un certain temps maintenant; Quelqu’un doit avoir déjà résolu ce problème.

Voici une liste de certains spécificateurs de format que vous pouvez vous attendre à voir dans Windows:

  • M/d/yyyy
  • M/d/yy
  • M/dd/yyyy
  • M/dd/yy
  • MM/d/yyyy
  • MM/d/yy
  • MM/dd/yyyy
  • MM/dd/yy
  • d/M/yyyy
  • d/M/yyy
  • d/MM/yyyy
  • d/MM/yy
  • dd/M/yyyy
  • dd/M/yy
  • dd/MM/yyyy
  • dd/MM/yy
  • yyyy/M/d
  • yy/M/d
  • yyyy/MM/d
  • yy/MM/d
  • yyyy/MM/dd
  • yy/MM/dd
  • dd MM yyyy
  • dd.MM.yyyy

Voir également

  • MSDN: Images au format Jour, Mois, Année et Era
  • MSDN: LOCALE_SSHORTDATE
  • Stackoverflow: Format de chaîne en tant que date
  • Stackoverflow: parsing une date d’une chaîne dans Win32
  • MSDN: DateTime.ParseExact, méthode (Ssortingng, Ssortingng, IFormatProvider)
  • Stackoverflow: Convert Ssortingng À ce jour en PHP

J’ai finalement dû céder et en écrire un. Pas sortingvial pour parsingr une date

 class function TDateTimeUtils.TryStrToDateExact(const S, DateFormat: ssortingng; out Value: TDateTime): Boolean; begin { Assume Microsoft's de-facto standard for y2k fixup: 2029 1930-2029 } Result := TDateTimeUtils.TryStrToDateExact(S, DateFormat, 2029, {out}Value); end; class function TDateTimeUtils.TryStrToDateExact(const S, DateFormat: ssortingng; PivotYear: Integer; out Value: TDateTime): Boolean; var Month, Day, Year: Integer; Tokens: TSsortingngDynArray; CurrentToken: ssortingng; i, n: Integer; Partial: ssortingng; 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 ssortingng formatted as specified by the CAL_SERASTRING value. The "g" and "gg" format pictures in a date ssortingng are ignored if there is no associated era or period ssortingng. 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. eg 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. eg (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 ssortingng 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 ReadDigitSsortingng(S, n, 2{MinDigits}, 2{MaxDigits}, 1{MinValue}, 12{MaxValue}, {var}Month) then Exit; end else if CurrentToken = 'M' then begin //months if not ReadDigitSsortingng(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 ReadDigitSsortingng(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 ReadDigitSsortingng(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 ReadDigitSsortingng(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 ReadDigitSsortingng(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 ReadDigitSsortingng(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 ssortingng 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: ssortingng): TSsortingngDynArray; var i: Integer; partial: ssortingng; 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 les parameters de votre projet vous permettent d’inclure des en-têtes ATL, vous pouvez utiliser l’object COleDateTime (#include atlcomtime.h) et utiliser sa méthode pratique ParseDateTime. Ce n’est pas infaillible, mais il gère beaucoup de formats OK.

Calendar.TwoDigitYearMax vous permettra de définir l’année pivot .NET.

http://msdn.microsoft.com/en-us/library/system.globalization.calendar.twodigityearmax.aspx