Rolf B: Sommerzeit, input type="date" und JavaScript

Aus der Rubrik „Das Nähkästchen des alten Programmierers“.
Oder: Wie dumm man sein kann…

input type="date" Elemente stellen ihr Datum in einer valueAsDate Eigenschaft bereit. Wehe dem, der das Date-Objekt darin falsch behandelt.

Ich wollte ja eigentlich was ganz einfaches machen. Da war ein Edit-Formular, mit dem Tagesdatum darauf, und ich wollte zwei Buttons "Gestern" und "Morgen" hinzufügen, um zum Wechseln des Tages nicht erst das Formular schließen und für den neuen Tag öffnen zu müssen.

<label for="editDatum">Datum:</label>
<input id="editDatum" type="date" name="datum"></label>
<button type="button" name="morgen">Nächster Tag</button>

Das sollte doch einfach sein. Dachte ich.

document
   .querySelector("button[name=morgen]")
   .addEventListener("click", nachMorgen);

const editDateField = document.querySelector("#editDatum");

function nachMorgen() {

   editDateField.valueAsDate.setDate(
      editDateField.valueAsDate.getDate() + 1
   );

   reloadData();
}

Das Thema „Was mache ich mit ggf. ungespeicherten Daten im aktuellen Form“ habe ich für den Blogartikel ausgelassen.

Ja. Denkste. Es passierte überhaupt nichts. Was auch logisch ist – woher soll das DOM mitbekommen, dass das Date-Objekt aktualisiert wurde.

Man muss es schon so machen, dass die Änderung bemerkt wird:

function morgen() {

   const datum = editDateField.valueAsDate;

   datum.setDate(datum.getDate() + 1);
   editDateField.valueAsDate = datum;

   reloadData();
}

Immer noch schlecht? Ja, tatsächlich, immer noch schlecht. Die Partnerfunktion gestern() hatte ich auch gebaut und bin dann zum Testen lustig hin und her gewechselt.

Bis ich auf den 30. März zurückgegangen bin, zurück in die Zukunft wollte und mir überraschend etwas an den nötigen 1,21 Gigawatt fehlte. Das Datumsfeld blieb auf dem 30. März stehen.

Der Grund steht natürlich in der HTML Spezifikation, und genauso natürlich übersieht man ihn, wenn man nicht ganz genau hinguckt.

Beim Zuweisen an valueAsDate wird das Date-Objekt in einen String umgewandelt. Und zwar für Zeitzone 0, alias UTC. Schauen wir uns an, was am 30. März, dem Sonntag, an dem 2025 die Sommerzeit beginnt, geschieht.

const tag = new Date('2025-03-30');
console.log(tag.toString());
// Sun Mar 30 2025 01:00:00 GMT+0100 

tag.setDate(tag.getDate()+1)
console.log(tag.toString());
// Mon Mar 31 2025 01:00:00 GMT+0200

Das ist das Problem: setDate() erhöht zwar den Tag, belässt die Zeit aber exakt wie sie ist, auf ein Uhr. Nur kommt leider die Sommerzeit dazwischen, das heißt: ein Uhr ist jetzt nicht mehr null Uhr UTC, sondern 23 Uhr UTC des Vortages.

Mozilla schreibt es ganz klar:[1]

The date returned should always be interpreted as a UTC time—for example, using methods like getUTCDate() instead of getDate(). If you are not careful, the result may be off by 1

Das zurückgegebene Date sollte immer als eine UTC-Zeit interpretiert werden – zum Beispiel dadurch, dass man Methoden wie getUTCDate() verwendet statt getDate(). Wenn Sie nicht achtgeben, kann das Ergebnis um 1 daneben liegen.

Und genauso war es bei mir.

function morgen() {

   const datum = editDateField.valueAsDate;

   datum.setUTCDate(datum.getUTCDate() + 1);
   editDateField.valueAsDate = datum;

   reloadData();
}

Kaum macht man es richtig, so funktioniert es auch schon.

Der Übergang vom März zum April ist übrigens gar kein Problem. Setzt man im März den Tag auf 32, springt er automatisch in den Folgemonat.

Rolf


  1. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/valueAsDate#value ↩︎