Logo
Code,  JavaScript,  TypeScript

Warum 86400 keine gute Zeitangabe in deinem JavaScript Code ist.

Von Nikolas Meyer am 29.12.2023

300, 1800, 3600, 86400, 604800: Als erfahrener Entwickler weißt du natürlich sofort, wofür diese Zahlen stehen, oder? Klingt "Duration.seconds(10)" besser für dich?

@nimey/units Beitragsbild

Zeitangaben in JavaScript

300, 1800, 3600, 86400, 604800: Als erfahrener Entwickler weißt du natürlich sofort, wofür diese Zahlen stehen, oder? Richtig; es sind Zeitangaben, die zum Beispiel in JavaScript in einem Timeout, Interval oder aber als TTL für eine Cache Klasse verwendet werden.

Aber mal Hand aufs Herz: Wie oft hast du schon Code gelesen und musstest erstmal deinen Taschenrechner bemühen, um herauszufinden, wie viele Minuten, Stunden oder Tage jetzt tatsächlich gemeint sind? In JavaScript kommt hinzu, dass solche Angaben in vielen Fällen in Millisekunden gemacht werden. Das hält einerseits mehr Informationen für dich bereit, macht es andererseits aber noch schwerer zu lesen.  Eine Woche in Millisekunden wären zum Beispiel 604800000.

Ein Kompromiss ist die Angabe als Multiplikation wie zum Beispiel “24 * 60 * 60 * 1000” für einen Tag. Definitiv einfacher, aber wenn ich erst darüber nachdenken muss, ist es für mich zu kompliziert.

Lesbare Angaben von Dauern

In vielen Konfigurationsdateien hat sich die Angabe als String mit Einheiten durchgesetzt. Die folgende Schreibweise für 1,5 Stunden hast du bestimmt schon einmal gesehen: “1h 30m”. Auf den ersten Blick ist ohne große Überlegungen direkt erkennbar, wofür dieser Wert steht.

Damit war diese Schreibweise für mich die erste Inspiration, wie ich zukünftig Dauern in meinen Projekten angeben möchte. Das Schöne ist, dass ich diese auch in Konfigurationsdateien angeben kann, da wir kein Javascript für die Notation benötigen. Ich wollte die gesamte Funktionalität dennoch gerne in einer Klasse kapseln. Der Konstruktor soll als ersten Parameter eine Dauer als String übergeben bekommen, diesen parsen und in numerischer Form speichern. Die Methode toString soll hingegen wieder einen entsprechend berechneten String erzeugen.

Als Erweiterung der Erzeugung von Dauern aus einem String heraus, soll es auch möglich sein, über statische Methoden entsprechende Objekte zu erzeugen. Besonders, wenn du eine Einstellung lediglich im Code definierst, kann das für die Lesbarkeit sehr hilfreich sein.

Schau dir folgendes Beispiel an, um eine Dauer von 5 Minuten zu definieren:

Die folgenden statischen Methoden stehen dir zur Initialisierung zur Verfügung:

Umrechnung in die Zieleinheit

Da du mit dem String aber nicht weiter arbeiten kannst, brauchen wir noch die Möglichkeit, unsere Werte in die entsprechende Zieleinheit umzurechnen. Dazu gibt es diverse toX Methoden. Zum Beispiel toMinutes.

In diesem Beispiel wird dir sicherlich auffallen, dass 90 Sekunden nicht 2 Minuten sind. An dieser Stelle muss die Klasse runden. Ohne weitere Angaben erfolgt die Rundung immer auf eine Ganzzahl. Allerdings kannst du als Parameter an alle toX Methoden übergeben, auf wie viele Nachkommastellen gerundet werden soll:

Die folgenden Methoden stehen dir zur Verfügung:

Wenn du also eine Funktion hast, die einen Timeout setzt, könnte die Verwendung mit TypeScript wie folgt aussehen:

Stell dir aber mal folgendes Szenario vor: Du möchtest die Duration Klasse in einer Bibliothek verwenden, die flexibel sein soll. Benutzer, die die Duration Klasse nicht kennen, sollen die Möglichkeit haben, auch klassisch eine Zahl anzugeben. Dazu kannst du folgendes Konstrukt verwenden:

Die statischen Methoden zum Initialisieren erlauben dir immer einen Typ Number oder eine Instanz vom Typ Duration zu übergeben. Die Initialisierung erfolgt dann passend zum übergebenden Parameter. Alternativ kannst du in JavaScript mit dem “+” Operator eine Umwandlung in einen Number Typ bewirken. Dabei wird der Wert aus der Duration Instanz in eine Zahl in der Basiseinheit umgewandelt. Sinnvoll ist diese Methode dementsprechend nur, wenn du die Zahl in der entsprechenden Basiseinheit benötigst. Da wir in der Regel Millisekunden benötigen, wäre folgende Schreibweise für einen Timeout möglich:

Abstraktion und mögliche Erweiterungen

Bei der Implementierung von Code solltest du immer Probleme oder Herausforderungen berücksichtigen, von denen du noch nicht weißt, dass sie existieren. Du musst nicht alle Probleme auf einmal lösen, aber du solltest eine Implementierung so wählen, dass sie erweiterbar ist.

Für unsere aktuelle Klasse Duration bedeutet das, dass wir die eigentliche Umrechnung und das Parsen des Strings nicht in der Duration Klasse umsetzen, sondern eine abstrakte Klasse definieren, die die gesamte Logik beinhaltet. Im konkreten Fall kann die Bibliothek zum Beispiel auf dieselbe Art wie bei Duration auch mit einer Klasse FileSize arbeiten.

Deshalb habe ich eine Klasse UnitConverter entworfen, die wir erweitern können. Diese wird auch aus der Bibliothek exportiert, damit du sie selbst erweitern kannst. Bevor wir aber mit der eigentlichen Implementierung der Klasse beginnen, müssen wir noch ein paar Definitionen angeben.

Wir beginnen mit den Einheiten und dem Festlegen der Basiseinheit. In unserem Fall wird die Basiseinheit Millisekunden. Wir unterstützen zusätzlich die Einheiten Sekunden, Minuten, Stunden, Tage, Wochen und Jahre. Die Umrechnungsfaktoren definiere ich über ein enum (ohne TypeScript kannst du ein Objekt verwenden).

Dabei erhält die Basiseinheit den Faktor 1 und alle anderen den Faktor, der angewendet werden soll, um von dieser Einheit auf die Basiseinheit zu kommen. Konkret: 1 Sekunde = 1000 Millisekunden bzw. 1 Minute = 60000 Millisekunden

Für das Parsen und das Generieren des Strings müssen wir eine UnitMap definieren, die wie folgt aussehen kann:

Der Key gibt dabei an, welcher Buchstabe im String verwendet wird, mit “factor” wird der Umrechnungsfaktor angegeben und “jsonKey” gibt an, welcher Schlüssel für die Umwandlung nach JSON verwendet werden soll.

In der eigentlichen Duration Klasse implementierst du nur noch den Constructor (Übergabe von Value und der UnitMap an UnitConverter) und die einzelnen Methoden für deine Einheiten.

Die Methoden für die restlichen Einheiten sind mit Sicherheit selbsterklärend.

Fazit

Mit der Bibliothek @nimey/units kannst du innerhalb von Einheiten umrechnen und auf einfache Weise statische Angaben in deinem Code oder in Config Dateien angeben. Durch die Abstraktion kannst du auf einfachste Weise Erweiterungen selbst implementieren und in deinem Code verwenden.

Schreib mir gerne, was du davon hältst oder wenn du Kritik loswerden möchtest.

Repo: https://git.nimey.de/nimey/units

Paket: https://www.npmjs.com/package/@nimey/units

Kommentare

Als angemeldeter Benutzer kannst du hier Kommentare hinterlassen.



Anmelden