Menü

Geburtsdatum prüfen: Randfälle, die Formulare zerlegen

Geburtsdatum prüfen wirkt trivial, bis Schaltjahre, Zeitzonen und Altersgrenzen auftauchen. Das sind die Randfälle, die in der Produktion falsche Antworten liefern.

Veröffentlicht am

  • Testdaten
  • Identität
  • Datumsangaben

Das Prüfen eines Geburtsdatums hat den Ruf, einfach zu sein, und dieser Ruf ist unverdient. Das Feld nimmt einen Wert, der Wert ist ein Datum, und ein Datum ist ein gelöstes Problem — außer dass es keines ist, denn ein Geburtsdatum ist zugleich die Eingabe für ein Alter, ein Alter wird gegen eine Grenze verglichen, und sowohl das Alter als auch die Grenze hängen davon ab, welcher Tag dort ist, wo der Vergleich stattfindet.

Dieser Artikel sammelt die Ränder, an denen korrekt aussehende Implementierungen falsche Antworten geben, und erklärt, welche davon vor einer Freigabe in eine Testsuite gehören und nicht nach einer Beschwerde.

Was unterscheidet ein Geburtsdatum von anderen Datumsangaben?

Ein Buchungsdatum, ein Rechnungsdatum und ein Lieferdatum liegen alle nahe der Gegenwart, und keines muss in eine Anzahl von Jahren umgerechnet werden. Ein Geburtsdatum unterscheidet sich in dreierlei Hinsicht gleichzeitig. Es liegt weit in der Vergangenheit, es überspannt also Dutzende Schaltjahrzyklen und mindestens eine Regeländerung. Es dient der Berechnung des Alters, was Arithmetik statt Formatierung bedeutet. Und es wird oft gegen eine gesetzliche Grenze verglichen, was bedeutet, dass die Antwort Folgen hat.

Die drei Unterschiede im Überblick:

  • Es liegt weit in der Vergangenheit und überspannt damit Dutzende Schaltjahrzyklen und mindestens eine Regeländerung
  • Es dient der Berechnung des Alters, was Arithmetik statt Formatierung bedeutet
  • Es wird oft gegen eine gesetzliche Grenze verglichen, was bedeutet, dass die Antwort Folgen hat

Die Folgen eines Fehlers sind asymmetrisch. Ein gültiges Geburtsdatum abzulehnen weist eine legitime Nutzerin ab; ein Geburtsdatum anzunehmen, das hätte scheitern müssen, lässt jemanden an einer Schranke vorbei, die ihn schützen soll. Beides sind echte Fehler, und sie kommen über verschiedene Codepfade herein.

Welche Daten existieren überhaupt nicht?

Jedes Datum, das im Kalender nicht existiert, sollte abgelehnt werden — das klingt selbstverständlich, bis man die Schaltjahrregel ausschreibt. Ein Jahr ist ein Schaltjahr, wenn es durch vier teilbar ist, außer dass Säkularjahre durch vierhundert teilbar sein müssen; ein Säkularjahr, das durch vier teilbar aussieht, ist also kein Schaltjahr, und ein anderes ist eines.

Die praktisch wichtigen Fälle sind die beiden Säkularjahre beiderseits der Gegenwart. Eines davon ist kein Schaltjahr, der neunundzwanzigste Tag seines zweiten Monats ist also kein gültiges Datum; das andere ist ein Schaltjahr, derselbe Tag ist dort gültig. Implementierungen, die nur die Teilbarkeit durch vier anwenden, nehmen ein unmögliches Datum an und weisen ein echtes zurück, und der Fehler bleibt den größten Teil des Jahres unsichtbar.

Dieselbe Überlegung gilt für den Rest des Kalenders: Monate haben verschiedene Längen, manche Eingaben kommen in der Reihenfolge Tag zuerst und manche Monat zuerst, und ein zweistelliges Jahr ist konstruktionsbedingt doppeldeutig. Ein Feld, das ein blankes zweistelliges Jahr annimmt, setzt manche Menschen um ein Jahrhundert neben ihr echtes Alter.

Wie wird das Alter tatsächlich berechnet?

Das Alter ist eine Differenz in vollen Jahren, berechnet durch komponentenweisen Vergleich des Geburtsdatums mit dem aktuellen Datum: Ziehen Sie die Geburtsjahre ab und dann noch eines, wenn der Geburtstag in diesem Jahr noch nicht war. Der Geburtstag selbst ist die Grenze, und die übliche Konvention ist, dass jemand an diesem Tag ein Jahr älter ist, nicht am Tag danach.

Diese Definition hat eine Feinheit, die es wert ist, ausgesprochen zu werden, weil hier die meisten Eins-daneben-Fehler leben. Verglichen werden zwei Kalenderdaten, nicht zwei Zeitpunkte. Zwei Menschen, die am selben Kalendertag geboren sind, sind an diesem Tag gleich alt, auch wenn sie zu verschiedenen Uhrzeiten geboren wurden, und jemand, der spät abends geboren wurde, wird nicht zu dieser Tageszeit ein Jahr älter.

Beide Daten in eine Sekundenzahl umzurechnen und zu teilen ist die übliche falsche Implementierung. Sie driftet über Schaltjahre, sie macht die Antwort von der Tageszeit abhängig, und sie erzeugt ein Alter, das zu einer beliebigen Stunde wechselt statt um Mitternacht.

Welches Heute verwendet die Prüfung?

Diejenige Uhr, auf der der Server zufällig läuft — sofern niemand anders entschieden hat. Das ist die Wurzel einer Klasse von Fehlern, die nur um Mitternacht auftreten und nur für Nutzer in bestimmten Zeitzonen: Ein Geburtsdatum, das die Altersgrenze für den lokalen Tag der Person erfüllt, scheitert für den Tag des Servers, oder umgekehrt.

Sauber gedacht ist das Geburtsdatum ein Datum und trägt überhaupt keine Uhrzeit. Speichern Sie es als reines Datum ohne angehängte Zeitzone, und vergleichen Sie das Alter gegen ein Datum, das aus einer ausdrücklichen Vorgabe abgeleitet wurde — meist das lokale Datum der Person für eine interaktive Prüfung und ein festes Bezugsdatum für alles, was wiederholbar sein muss. Ein zeitzonenfreies Geburtsdatum mit einem zeitzonenbewussten aktuellen Zeitpunkt zu mischen, ist der Weg, auf dem die Doppeldeutigkeit hereinkommt.

Eine verwandte Falle gibt es für Tests. Ein Test, der seine Erwartung aus der Systemuhr berechnet, besteht heute und scheitert an jemandes Geburtstag, in einem Schaltjahr oder nach einer Regeländerung. Tests, die das Alter zusichern, brauchen das aktuelle Datum als Eingabe statt als Uhrablesung.

Sind sich alle Kalender über denselben Tag einig?

Nein. Die Zeitrechnung des globalen Standardkalenders ist nicht die einzige in Gebrauch, und mehrere Regionen pflegen für zivile Zwecke eigene Systeme. Ein Datum, das in einem Kalender geschrieben ist, entspricht nicht demselben geschriebenen Datum in einem anderen, und derselbe Zeitpunkt kann mit abweichendem Jahr, Monat und Tag erscheinen, je nachdem, welches System das Formular erwartet.

Für den Formularbau lautet die praktische Regel, ausdrücklich statt klug zu sein: Sagen Sie, welchen Kalender das Feld erwartet, nehmen Sie die Bestandteile in einer genannten Reihenfolge an, und wenn der lokale Kalender einer Region für Ihre Nutzer zählt, behandeln Sie die Umrechnung als Produktentscheidung mit eigenem Feld statt als automatische Umformung, die niemand sieht. Still umzurechnen ist schlimmer als nicht umzurechnen, weil der falsche Wert von einem eingetippten nicht zu unterscheiden ist.

Wo richten Platzhalterdaten Schaden an?

Drei Voreinstellungen richten messbaren Schaden an. Der erste Januar eines runden Jahres ist der häufigste Platzhalter der Welt, und ein Formular, das ihn still als echtes Geburtsdatum behandelt, hat eine Gruppe von Nutzern, die alle am selben Tag Geburtstag haben. Ein Nullwert oder leerer Wert, der sich als Datum ausgibt, ist schlimmer, weil er zu einem Alter von mehreren Jahrhunderten rechnen und eine Über-dreizehn-Prüfung bestehen kann, während er jede andere Prüfung still scheitert.

Der dritte ist der Platzhalter, der formal gültig und offensichtlich falsch ist, etwa das früheste Datum, das eine Datumsauswahl zulässt. Alle drei teilen ein Symptom: Sie lassen einen schlechten Datensatz vollständig aussehen, sodass nachgelagerter Code nie die Gelegenheit bekommt, ihn abzulehnen.

Das ist das Argument für erzeugte Datensätze in Tests. Werte aus dem Identitätsdatensatz-Erzeuger sind gestreut statt gehäuft, ein Formular im Test sieht also einen Bereich echt geformter Daten statt immer desselben Platzhalters. Es sind synthetische Werte für Softwaretests und nichts weiter — nicht das echte Geburtsdatum von jemandem und kein Dokument, das als Identität einer Person dienen könnte.

Für Entwickler: Grenzen, die sich zu prüfen lohnen

Fixieren Sie das Bezugsdatum im Test und prüfen Sie dann die exakte Grenze statt etwas daneben. Vier Werte decken für jede Grenze fast das gesamte Risiko ab: das Geburtsdatum, das die Person heute genau in der Grenze alt macht, das einen Tag zu kurze, das einen Tag zu lange und eines an einem Schalttag.

Darüber hinaus halten Sie Speicherung und Vergleich eng: Speichern Sie ein Kalenderdatum ohne Zeitzone, leiten Sie das Alter bei Bedarf ab, statt es zu speichern, und lassen Sie nie zu, dass ein Platzhalterdatum von einem echten ununterscheidbar wird. Ein Sentinel-Wert außerhalb des plausiblen Bereichs, von der Prüfung zurückgewiesen, ist einem plausiblen Standardwert vorzuziehen, der durch die Kontrollen rutscht. Und halten Sie Richtwerte in der Konfiguration, denn Grenzen ändern sich und ein fest verdrahteter Wert ist eine Freigabe davon entfernt, falsch zu sein.

Nächste Schritte

Nehmen Sie die Altersprüfung in Ihrem Produkt und lassen Sie diese vier Grenzwerte mit festem Bezugsdatum durchlaufen; gibt einer davon die falsche Antwort, liegt der Fehler im Vergleich und nicht im Formular. Holen Sie dann aus dem Identitätsdatensatz-Erzeuger eine Streuung erzeugter Geburtsdaten und bestätigen Sie, dass das Feld sie unverändert speichert, darunter eines am Monatsanfang und eines in einer nicht standardmäßigen Kalenderreihenfolge, damit ein Sprachwechsel sie nicht still umsortieren kann. Der Artikel über Altersprüfungen im Test behandelt die Grenzen, gegen die diese Daten üblicherweise verglichen werden.

Weiterlesen

Artikel zu Identitäts- und Testdaten-Generator