Die Temporal API – besseres Datehandling für JavaScript

Der ECMAScript-Standard wird stetig weiterentwickelt und JavaScript damit stetig verbessert. Eine der größten Baustellen war bisher der Umgang mit Datum und Zeit. Das ändert sich jedoch mit der neuen Temporal API.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 11 Min.
Von
  • Sebastian Springer
Inhaltsverzeichnis

Der ECMAScript-Standard wird stetig weiterentwickelt und JavaScript damit stetig verbessert. Eine der größten Baustellen war bisher der Umgang mit Datum und Zeit. Das ändert sich jedoch mit der neuen Temporal API.

JavaScript, Datum und Zeit, das ist so eine Geschichte für sich und sie nimmt leider kein gutes Ende. Jedes Mal, wenn ich ein neues Date-Objekt erzeuge oder noch schlimmer mit Datum und Zeit rechnen muss, frage ich mich, wie es eine Sprache mit einer so schlechten Datums- und Zeitimplementierung so weit bringen konnte. Aber seien wir mal ehrlich: Bei diesem Thema bekleckern sich auch andere Sprachen nicht mit Ruhm. Aber jetzt sprechen wir erstmal von JavaScript und einer sehr erfreulichen Entwicklung.

JavaScript erhält eine Spracherweiterung mit dem Namen Temporal, die sich mit Datum und Zeit beschäftigt und eine deutlich angenehmere Schnittstelle als die bisherige Date-API hat, die sich übrigens an einer der ersten Date-APIs von Java orientiert. Die neue Schnittstelle behebt nicht nur einige Schwächen der bisherigen, sondern gestaltet den Umgang mit Zeit und Datum deutlich moderner und greift einige Aspekte von modernen Bibliotheken wie beispielsweise die Unveränderbarkeit von Objekten auf. Aktuell befindet sich die API zwar noch als ECMAScript Proposal in der Stage 3. Das ist jedoch auch beinahe das Ende des Standardisierungsprozesses mit seinen insgesamt vier Stufen, die ein Feature durchlaufen muss, bevor es in den Standard aufgenommen wird. Üblicherweise gibt es in der dritten Stufe keine größeren Änderungen mehr an der Schnittstelle, sodass das, was wir jetzt in der Temporal API finden, mit großer Wahrscheinlichkeit auch das sein wird, was schließlich die Browserhersteller in ihre JavaScript Engines aufnehmen.

Für alle Ungeduldigen, die der Temporal API bereits jetzt eine Chance geben möchten, existiert ein Polyfill, das die Schnittstelle in aktuellen Browsern nachbildet. Das NPM-Paket mit dem Namen proposal-temporal enthält es und wird mit dem Kommando npm install proposal-temporal installiert. Die Temporal-Schnittstelle ist, wie alle nativen Sprachfeatures von JavaScript, plattformunabhängig. Das bedeutet, dass es sowohl clientseitig im Browser als auch serverseitig in Node.js zum Einsatz kommen kann. Das gleiche gilt für das Polyfill, es ist außerdem kompatibel mit allen gängigen Modulsystemen, sodass es in allen Umgebungen getestet werden kann.

Es gibt mehrere Anwendungsfälle, denen man in JavaScript-Applikationen immer wieder begegnet und diese werden wir uns als Erstes ansehen. Beginnen wir mit dem aktuellen Zeitpunkt. Das Temporal-Objekt bietet für diesen Zweck das now-Objekt mit der plainDateTime-ISO-Methode. Gerade für den Austausch zwischen Systemen arbeiten Entwickler nicht nur mit einer Datumszeichenkette, sondern mit UNIX-Zeitstempeln, also den Sekunden, die seit dem 1.1.1970 00:00 vergangen sind. Hierfür bietet das now-Objekt die Methode instant, deren Rückgabewert die epochSeconds-Eigenschaft aufweist, die wiederum den UNIX-Zeitstempel enthält. Die letzte Aufgabe im Schnellstart mit der neuen Temporal-Schnittstelle ist das Erzeugen eines beliebigen Zeitpunkts. Dies wird über die PlainDateTime.from-Methode erreicht. Die from-Methode akzeptiert sowohl ein Konfigurationsobjekt als auch eine Zeichenkette, die das gewünschte Datum angibt. Das nachfolgende Listing zeigt die verschiedenen Szenarien.

import { Temporal } from "proposal-temporal"; 

const currentTime = Temporal.now.plainDateTimeISO();
console.log(currentTime.toString()); // Ausgabe:
2021-06-03T08:22:18.381338378

const timeStamp = Temporal.now.instant().epochSeconds;
console.log(timeStamp); // Ausgabe: 1622701338
const christmas = Temporal.PlainDateTime.from({
day: 24,
month: 12,
year: 2021,
hour: 20,
minute: 15,
second: 32
});
console.log(christmas.toString()); // Ausgabe: 2021-12-24T20:15:32

Bestehende Applikationen werden es bei der Umstellung von der Date API auf die neue Temporal-Schnittstelle erforderlich machen, dass die Datums- und Zeitobjekte zwischen beiden Schnittstellen konvertiert werden. Listing 2 zeigt, wie eine solche Umwandlung funktionieren kann.

import { Temporal } from 'proposal-temporal';

const date = new Date('2020-12-24T20:15:32');
const temporal = new Temporal.ZonedDateTime(
BigInt(date.getTime() * 10 ** 6),
'Europe/Berlin');
console.log(temporal.toString());
//2020-12-24T20:15:32+01:00[Europe/Berlin]

const temporal2 = Temporal.ZonedDateTime.from({
year: 2020,
month: 12,
day: 24,
hour: 20,
minute: 15,
second: 32,
timeZone: 'Europe/Berlin'
});
const date2 = new Date(temporal2.epochMilliseconds);
console.log(date2.toLocaleString()); // Ausgabe: 24/12/2020, 20:15:32

Dieses Codebeispiel zeigt ein grundlegendes Problem der Date API von JavaScript sehr schön: Es ist nicht möglich, ein Date-Objekt zu erzeugen, das unabhängig von einer Zeitzone ist. Das Date-Objekt hat die lokale Zeitzone der Umgebung, in meinem Fall ist das die mitteleuropäische Zeit. Die Entsprechung dieses Konzepts in Temporal sind Objekte vom Typ ZonedDateTime. In einigen Fällen ist jedoch die Information über die Zeitzone in einem Datumsobjekt gar nicht notwendig. Dieses Problem löst die Temporal-Schnittstelle, indem es verschiedene Objekte gibt, um eine Datums- und Zeit-Kombination zu repräsentieren. Die folgende Abbildung aus dem Temporal Proposal fasst diesen Sachverhalt grafisch zusammen.

Die Klassen der Temporal API

(Bild: https://tc39.es/proposal-temporal/docs/object-model.svg)

Die umfassendste Klasse der Schnittstelle ist ZonedDateTime, sie enthält die Information über den Zeitpunkt sowie die Zeitzone. Im Gegensatz dazu haben die Plain-Typen wie PlainDateTime oder PlainTime keine Information über Zeitzonen und stellen lediglich einen generischen Zeitpunkt dar. Alle Typen, außer Instant, arbeiten außerdem auf einem bestimmten Kalender. Standardmäßig nutzt Temporal den Gregorianischen Kalender, der in der ISO 8601-Norm standardisiert ist. Weitere Beispiele für Kalender, die an dieser Stelle zum Einsatz kommen können, sind der Buddhistische, der Persische oder der Japanische Kalender.

Ein großer Vorteil der Temporal API gegenüber der bisherigen Implementierung ist, dass sie in der Lage ist, mit Datumswerten zu rechnen und mit Zeitspannen umzugehen. Die Grundlage hierfür bildet die Duration-Klasse beziehungsweise Objekte, die der Struktur entsprechen, die diese Klasse vorgibt. Beim Rechnen mit Datumswerten kommt eine weitere Besonderheit der Temporal Schnittstelle zum Tragen: Die Schnittstelle arbeitet mit immutable Objekten, modifiziert das Ausgangsobjekt beispielsweise bei einer Addition nicht direkt, sondern gibt ein neues Objekt zurück. Wie das im Code funktioniert, zeigt folgendes Beispiel:

import { Temporal } from 'proposal-temporal'; 

const now = Temporal.now.plainDateTimeISO();
console.log(now.toString()); // Ausgabe: 2021-06-03T19:26:42.949202945
const future = now.add({days: 30});
console.log(future.toString()); // Ausgabe: 2021-07-03T19:26:42.949202945
console.log(now.toString()); // Ausgabe: 2021-06-03T19:26:42.949202945

Alle Zoned- und Plain-Klassen der Temporal API unterstützen die add- und subtract-Methoden. Diese ermöglichen, wie die Namen schon verraten, das Hinzufügen beziehungsweise Abziehen von Zeitspannen zu einem bestimmten Zeitpunkt. Beide Methoden akzeptieren einfache JavaScript-Objekte mit den Angaben, wie viele Zeiteinheiten, also beispielsweise Monate, Tage oder Stunden, abgezogen oder addiert werden sollen. Alternativ kann auch ein Duration-Objekt eingesetzt werden.

Ein weiterer Einsatzzweck für das Duration-Objekt ist die Bestimmung der Zeitspanne, die zwischen zwei Datumswerten liegt. Soll beispielsweise herausgefunden werden, wie viele Tage zwischen dem Beginn und dem Ende der Weihnachtsferien liegen, kann der folgende Quellcode eingesetzt werden.

import { Temporal } from 'proposal-temporal'; 

const start = Temporal.PlainDate.from({
day: 24,
month: 12,
year: 2021
});

const end = Temporal.PlainDate.from({
day: 8,
month: 1,
year: 2022
});

const diff = start.until(end);
const diff2 = end.since(start);
console.log(diff.toString()); // Ausgabe: P15D
console.log(diff2.toString()); // Ausgabe: P15D

Sowohl der Start- als auch der Endpunkt werden durch ein PlainDate-Objekt repräsentiert, da in diesem Fall weder Zeitzone noch Uhrzeit benötigt wird. Neben der hier verwendeten until-Methode gibt es mit der since-Methode auch noch das Gegenstück, das die Zeitspanne vom Ende bis zum Start misst. Das Ergebnis ist in beiden Fällen ein Duration-Objekt, das 15 Tage umfasst. Ein solches Objekt kann dann wieder in Kombination mit weiteren Temporal-Objekten verwendet werden, um beispielsweise eine Addition oder Subtraktion durchzuführen. Die Formatierung der Ausgabe, P15D, erscheint auf den ersten Blick etwas gewöhnungsbedürftig, folgt jedoch einem klaren Format. Die Zeichenkette wird durch das Zeichen P eingeleitet, danach folgen Jahre, Monate, Wochen und Tage. Anschließend werden Datum und Uhrzeit durch das Zeichen T getrennt gefolgt von Stunden, Minuten und Sekunden.

Wären in diesem Beispiel nicht nur Datumswerte, sondern auch Uhrzeiten beteiligt, gibt es mit der round-Methode die Möglichkeit, eine Zeitspanne auf ganze Tage zu runden. Die Methode akzeptiert ein Konfigurationsobjekt, dessen wichtigste Eigenschaften largestUnit, smallestUnit und roundingMode sind. Die ersten beiden geben die kleinste beziehungsweise größte Einheit an, auf die gerundet werden soll. Mit der roundingMode-Eigenschaft können Entwickler festlegen, wie gerundet wird. Der Code in folgendem Listing rundet beispielsweise auf volle Tage ab:

import { Temporal } from 'proposal-temporal'; 

const start = Temporal.PlainDateTime.from({
day: 24,
month: 12,
year: 2021,
hour: 7,
minute: 30
});

const end = Temporal.PlainDateTime.from({
day: 8,
month: 1,
year: 2022,
hour: 20,
minute: 15
});

console.log(start.until(end).toString()); // Ausgabe: P15DT12H45M
const diff = start.until(end).round({
smallestUnit: 'day',
roundingMode: 'floor'
});
console.log(diff.toString()); // Ausgabe: P15D

Das Ergebnis der round-Methode ist wiederum ein Duration-Objekt, das die Applikation weiterverwenden kann. Das Prinzip der Immutability bleibt also auch in diesem Fall gewahrt.

Mit der Temporal API trägt das TC39 der Tatsache Rechnung, dass die Date-Funktionalität von JavaScript einige Schwächen aufweist und für die Umsetzung moderner Applikationen nicht die beste Wahl ist. Rund um den Umgang mit Datum und Zeit sind im Laufe der Zeit eine Vielzahl von Bibliotheken entstanden. Das Temporal Proposal greift viele der Ideen auf, die diese Bibliotheken verfolgen und integriert sie in den Sprachkern von JavaScript. Das hat den Vorteil, dass diese Features, sobald die Temporal API in den Standard aufgenommen wurde, nativ vom Browser unterstützt werden. Das bedeutet, dass es keinen zusätzlichen Overhead durch Bibliotheken mehr gibt und die Funktionalität potenziell performanter ist, da sie direkt im Browser umgesetzt ist. Diese Schnittstelle ist ein sehr gutes Beispiel dafür, dass sich JavaScript stetig weiterentwickelt und dabei auf die Anforderungen der Entwickler eingeht.

()