Autor |
Beitrag |
baka0815
      
Beiträge: 489
Erhaltene Danke: 14
Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
|
Verfasst: Di 02.09.08 16:32
Wie in einem anderen Topic angegeben hatte ich ja mit den Delphi-eigenen Methoden zur Datumskonvertierung so meine Probleme und habe mir deswegen einen eigenen, kleinen Parser gebaut, der ein Datum und das Format angegebene bekommt und dann das entsprechende Datum als TDateTime Objekt zurück gibt.
Ich stelle das Ergebnis mal hier rein in der Hoffnung, dass mal wer drüber guckt, ob ich irgendwas übersehen habe bzw. wo noch (größerer oder kleinerer) Optimierungsbedarf besteht:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154:
| function DatumParsen(Datum, Format: string): TDateTime; var I: Integer; TagFertig, MonatFertig, JahrFertig: Boolean; Tag, Monat: Byte; Jahr: Word; TagHeute, MonatHeute, JahrHeute: Word; Temp: string; Von, Laenge: Integer; begin if (Length(Datum) <> Length(Format)) then raise EParserError.Create('Der angegebene Text ("' + Datum + '") ist kein Datum im Format "' + Format + '")');
TagFertig := False; MonatFertig := False; JahrFertig := False; Tag := $FF; Monat := $FF; Jahr := $FFFF;
I := 1; while (I <= Length(Format)) do begin case Format[I] of 'D': begin if (TagFertig) then begin raise EParserError.Create('Das angegebene Format ist ungültig: ' + Format); end; Von := I; Laenge := 1; if (Format[I+1] = 'D') then begin Laenge := 2; Inc(I); end;
try Temp := Copy(Datum, Von, Laenge); Tag := StrToInt(Temp) and $FF; except raise EParserError.Create('Der Tag "' + Temp + '" ist keine gültige Zahl!'); end;
if (Tag <= 0) or (Tag > 31) then raise EParserError.Create('Der Tag ' + IntToStr(Tag) + ' ist ungültig!');
TagFertig := True; end; 'M': begin if (MonatFertig) then begin raise EParserError.Create('Das angegebene Format ist ungültig: ' + Format); end;
Von := I; Laenge := 1; if (Format[I+1] = 'M') then begin Laenge := 2; Inc(I); end;
try Temp := Copy(Datum, Von, Laenge); Monat := StrToInt(Temp) and $FF; except raise EParserError.Create('Der Monat "' + Temp + '" ist keine gültige Zahl!'); end;
if (Monat <= 0) or (Monat > 12) then raise EParserError.Create('Der Monat ' + IntToStr(Monat) + ' ist ungültig!');
MonatFertig := True; end; 'Y': begin if (JahrFertig) then begin raise EParserError.Create('Das angegebene Format ist ungültig: ' + Format); end;
Von := I; while (Format[I] = 'Y') do begin Inc(I); end; Laenge := I-Von;
try Temp := Copy(Datum, Von, Laenge); Jahr := StrToInt(Temp) and $FFFF; except raise EParserError.Create('Das Jahr "' + Temp + '" ist keine gültige Zahl!'); end;
if (Jahr < 100) then begin Inc(Jahr, (SysUtils.CurrentYear - TwoDigitYearCenturyWindow) div 100 * 100); if (TwoDigitYearCenturyWindow > 0) and (Jahr < (SysUtils.CurrentYear - TwoDigitYearCenturyWindow)) then Inc(Jahr, 100); end;
JahrFertig := True; Dec(I); end; else begin if (Format[I] <> Datum[I]) then raise EParserError.Create('Das angegebene Datum ("' + Datum + '") ist nicht im angegebenen Format ("' + Format + '")'); end; end; Inc(I); end;
DecodeDate(Now, JahrHeute, MonatHeute, TagHeute); if (Tag = $FF) then Tag := TagHeute; if (Monat = $FF) then Monat := MonatHeute; if (Jahr = $FFFF) then Jahr := JahrHeute;
if (Tag > DaysInAMonth(Jahr, Monat)) then raise EParserError.Create('Das angegebene Datum ("' + Datum + '") ist ungültig, es gibt nur ' + IntToStr(DaysInAMonth(Jahr, Monat)) + ' Tage im Monat ' + IntToStr(Monat) + ' des Jahres ' + IntToStr(Jahr) + '!');
Result := EncodeDate(Jahr, Monat, Tag); end; |
Gültige Aufrufe wären z.B.
Delphi-Quelltext 1: 2: 3: 4:
| DatumParsen('10.11.2008', 'DD.MM.YYYY'); DatumParsen('101108', 'DDMMYY'); DatumParsen('10.11.99', 'DD.MM.YY'); DatumParsen('3000-02-01', 'YYYY-MM-DD'); |
Hier noch der DUnit-Testfall:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105:
| procedure TestAZeile.TestDatumParsen; var Expected, Actual: TDateTime; begin Expected := EncodeDate(2008, 11, 10); Actual := DatumParsen('10.11.2008', 'DD.MM.YYYY'); CheckEquals(Expected, Actual, 'Das Datum 10.11.2008 wurde nicht korrekt geparsed!');
Actual := DatumParsen('10.11.08', 'DD.MM.YY'); CheckEquals(Expected, Actual, 'Das Datum 10.11.08 wurde nicht korrekt geparsed!');
Actual := DatumParsen('101108', 'DDMMYY'); CheckEquals(Expected, Actual, 'Das Datum 101108 wurde nicht korrekt geparsed!');
Actual := DatumParsen('10112008', 'DDMMYYYY'); CheckEquals(Expected, Actual, 'Das Datum 10112008 wurde nicht korrekt geparsed!');
Actual := DatumParsen('10/11/2008', 'DD/MM/YYYY'); CheckEquals(Expected, Actual, 'Das Datum 10/11/2008 wurde nicht korrekt geparsed!');
Expected := EncodeDate(1999, 11, 10); Actual := DatumParsen('10.11.99', 'DD.MM.YY'); CheckEquals(Expected, Actual, 'Das Jahr 99 wurde nicht als 1999 aufgefasst.');
try DatumParsen('01/13/2008', 'DD/MM/YYYY'); Fail('Das Datum 01/13/2008 ist nicht als falsch erkannt worden!'); except end;
try DatumParsen('80/80/2008', 'DD/MM/YYYY'); Fail('Das Datum 80/80/2008 ist nicht als falsch erkannt worden!'); except end;
try DatumParsen('01.01.2008', 'DD.MM.YYYY'); Fail('Das Datum 01.01.2008 (beim Format DD/MM/YYYY) ist nicht als falsch erkannt worden!'); except end;
try DatumParsen('31.02.2008', 'DD.MM.YYYY'); Fail('Das Datum 31.02.2008 ist nicht als falsch erkannt worden!'); except end;
Expected := EncodeDate(2008, 02, 29); Actual := DatumParsen('29.02.2008', 'DD.MM.YYYY'); CheckEquals(Expected, Actual, 'Das Datum 29.02.2008 wurde nicht korrekt geparsed!');
try DatumParsen('29.02.2006', 'DD.MM.YYYY'); Fail('Das Datum 29.02.2006 ist nicht als falsch erkannt worden!'); except end;
try DatumParsen('29.02.1900', 'DD.MM.YYYY'); Fail('Das Datum 29.02.1900 ist nicht als falsch erkannt worden!'); except end;
Expected := EncodeDate(2008, 02, 29); Actual := DatumParsen('2008-02-29', 'YYYY-MM-DD'); CheckEquals(Expected, Actual, 'Das Datum 2008-02-29 wurde nicht korrekt geparsed!');
Actual := DatumParsen('08-02-29', 'YY-MM-DD'); CheckEquals(Expected, Actual, 'Das Datum 08-02-29 wurde nicht korrekt geparsed!');
try DatumParsen('1900.02.01', 'YYYY-MM.DD'); Fail('Der falsche Trenner wurde nicht erkannt!'); except end;
try DatumParsen('12.12', 'DD.MM.YYYY'); Fail('Die unterschiedliche Länge wurde nicht erkannt!'); except end;
try DatumParsen('12.12.1900 1212', 'DD.MM.YYYY MMDD'); Fail('Die wiederkehrenden Bezeichner wurden nicht korrekt erkannt!'); except end;
Expected := Date; Actual := DatumParsen('', ''); CheckEquals(Expected, Actual, 'Das Datum <Heute> wurde nicht korrekt erkannt!');
Expected := EncodeDate(3000, 02, 01); Actual := DatumParsen('3000-02-01', 'YYYY-MM-DD'); CheckEquals(Expected, Actual, 'Das Datum 3000-02-01 wurde nicht korrekt erkannt!');
Expected := EncodeDate(9999, 02, 01); Actual := DatumParsen('9999-02-01', 'YYYY-MM-DD'); CheckEquals(Expected, Actual, 'Das Datum 9999-02-01 wurde nicht korrekt erkannt!');
Expected := EncodeDate(2008, 01, 31); Actual := DatumParsen('31-01-2008', 'DD-MM-YYYY'); CheckEquals(Expected, Actual, 'Das Datum 31-01-2008 wurde nicht korrekt erkannt!'); end; |
Schon mal im Voraus Danke für Tipps und Kritik!
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Mi 03.09.08 01:30
Sieht schon ganz gut aus, Was noch wünschenswert wäre, wäre das Parsing der Uhrzeit auch gleich mit.
Bug (Index out of Bounds):
Wenn man den Format-Modifier für eine beliebige Angabe einstellig am Ende wählt, tritt bei aktivierten Bound Checks eine Index Out Of Bounds Exception auf. (Gefunden durch pures hingucken  )
Betroffen: Zeilen 33, 67 UND 97.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
baka0815 
      
Beiträge: 489
Erhaltene Danke: 14
Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
|
Verfasst: Mi 03.09.08 09:18
BenBE hat folgendes geschrieben: | Sieht schon ganz gut aus, Was noch wünschenswert wäre, wäre das Parsing der Uhrzeit auch gleich mit. |
Ich parse bewusst nur das Datum, da es bei der Uhrzeit zu Problemen wegen der Format-Angabe führen könnte.
Delphi nimmt ja das Format DD.MM.YYYY HH:MM:SS, wobei die Folge MM 2x vorkommt.
Bei meiner aktuellen Methode wäre das ein Fehler, da er 2x versuchen würde den Monat zu parsen. Dies könnte man lösen indem man sagt, dass Monat = MM und Minuten = mm ist oder so ähnlich (dabei fällt mir auf, dass ich vergessen habe das auf Case-Insensitive zu stellen).
Mein aktuelles Projekt erfordert jedoch nur Datums-Parsen, deswegen weiß ich nicht, ob ich die Zeit noch einbaue.
Achso, Formate wie "DDD" ("Di" bzw. "Tue"), "MMM" ("Jan", "Feb") oder "MMMM" ("Januar", "Februar") habe ich bewusst nicht eingebaut, da ich dann auch die Locale benötigen würde um zu entscheiden was genau erwartet würde und die Routine eh nur innerhalb Deutschlands benötigt wird. Ausserdem sind dann die einzelnen Werte unterschiedlich lang und sowas wie "01Januar2008" bzw. "01März2008" zu parsen ist mit meinem aktuellen Ansatz nicht möglich (unterschiedliche Länge von Format und Datums String).
BenBE hat folgendes geschrieben: | Bug (Index out of Bounds):
Wenn man den Format-Modifier für eine beliebige Angabe einstellig am Ende wählt, tritt bei aktivierten Bound Checks eine Index Out Of Bounds Exception auf. (Gefunden durch pures hingucken )
Betroffen: Zeilen 33, 67 UND 97. |
Eigentlich würde ich dir Recht geben, tritt jedoch nicht auf, da Format[I+1] bei mir #0 ist, ist also ein nullterminierter String.
Kann das zu Problemen führen bzw. kann man das irgendwie forcieren?
Übergeben habe ich die Daten jetzt sowohl als string-Variable als auch als fester String ( '2008-1-1') und beide sind #0-terminiert und laufen somit nicht auf das Problem.
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Mi 03.09.08 10:25
Ältere Delphi-Versionen (D4 und D5) haben bei den Strings eine leicht andere Behandlung gehabt. Abfangen kann man das, indem man If (Length(DeinString) >= Index) and (DeinString[Index] = '?') then als seine Bedingung hernimmt.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
baka0815 
      
Beiträge: 489
Erhaltene Danke: 14
Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
|
Verfasst: Mi 03.09.08 10:38
BenBE hat folgendes geschrieben: | Ältere Delphi-Versionen (D4 und D5) haben bei den Strings eine leicht andere Behandlung gehabt. |
Gut zu wissen. Kann man das mit neueren Versionen irgendwie forcieren (ich benutze Delphi 2007)?
BenBE hat folgendes geschrieben: | Abfangen kann man das, indem man If (Length(DeinString) >= Index) and (DeinString[Index] = '?') then als seine Bedingung hernimmt. |
Habe ich bei mir geändert, allerdings stehe ich vor einem Problem, bzw. der Frage ob es überhaupt ein Problem ist.
Nun ist es ja möglich als Format YYYY-M-D zu verwenden. " M" und "[delphi]D[/delphi}" stehen hierbei (eigentlich) für Monat und Tag ohne führende 0.
Von mir wird es aktuell jedoch als "genau ein Zeichen" interpretiert, was ich aus folgendem Grund nicht ändern möchte: Wenn das Format " YYYYMD" ist, weiß ich nicht, wie mit dem Wert " 2008112" umgegangen werden soll - ist es nun der 12.01.2008 oder der 02.11.2008?
Dann müsste ich hergehen und festlegen, dass einstellige Bezeichner ("M" bzw. "D") nur möglich sind, wenn auch ein Trenner angegeben wird.
Kommentare dazu?
|
|
Yogu
      
Beiträge: 2598
Erhaltene Danke: 156
Ubuntu 13.04, Win 7
C# (VS 2013)
|
Verfasst: Mi 03.09.08 13:59
baka0815 hat folgendes geschrieben: | Delphi nimmt ja das Format DD.MM.YYYY HH:MM:SS, wobei die Folge MM 2x vorkommt. |
Genau das Problem hatten die Delphi-Programmierer wohl auch, deshalb haben sie dann für die Minuten ein NN gewählt. 
|
|
|