JavaScript/Tutorials/Zeit & Datum

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Zeitberechnungen stellen sowohl für den Menschen als auch für den Computer ein Problem dar. Die Ursache liegt darin, dass Zeitangaben von dem uns gewohnten dezimalen Zahlensystem abweichen. Ein Tag besteht aus 24 Stunden, eine Stunde besteht aus 60 Minuten und eine Minute wiederum aus 60 Sekunden. Diese Darstellungsform der Zeit macht es uns gleichzeitig sehr schwer, größere Zahlenangaben wie z. B. 7835 Sekunden gefühlsmäßig zu erfassen und richtig einzuordnen.

Um mit Zeiträumen mathematisch zu rechnen, ist es sehr praktisch, einfach in Sekunden zu rechnen. Andererseits ist es unabdingbar, die Ergebnisse wieder in das gewohnte Format hh:mm:ss zurückzuwandeln, damit der Benutzer die Information schon auf den ersten Blick richtig einordnen kann.[1]

Das Date-Objekt ist für alle Berechnungen mit Datum und Zeit zuständig. Dabei gibt es, wie in der IT üblich, einen fixen historischen Zeitpunkt, der intern als Speicherungs- und Berechnungsbasis dient. In JavaScript ist dies – wie in der C- und Unix-Welt – der 1. Januar 1970, 0:00 Uhr UTC (Universal Coordinated Time). Die Einheit, in der in JavaScript intern Zeit berechnet wird, ist im Gegensatz zur Sekunde der Unix-Welt aber eine Millisekunde.

Allgemeines zur Verwendung

Date-Objekt erzeugen

Bevor Sie über JavaScript Zugriff auf die eingebauten Datums- und Uhrzeitfunktionen haben, müssen Sie ein neues Date-Objekt erzeugen. Dabei gibt es mehrere Varianten.

Beispiel
// Variante 1:
Objektname = new Date();

// Variante 2:
Objektname = new Date("Monat Tag, Jahr Stunden:Minuten:Sekunden");

// Variante 3:
Objektname = new Date(Jahr, Monat, Tag);

// Variante 4:
Objektname = new Date(Jahr, Monat, Tag, Stunden, Minuten, Sekunden);

// Variante 5:
Objektname = new Date(Millisekunden);

Ein neues Date-Objekt speichern Sie in einem selbst vergebenen Objektnamen. Hinter dem Namen folgt ein Gleichheitszeichen. Dahinter folgt das reservierte Wort new und der Aufruf der Objektfunktion Date().

Benutzen Sie Variante 1, wenn Sie eine Objektinstanz erzeugen wollen, in der das zum Zeitpunkt der Programmausführung aktuelle Datum und die aktuelle Uhrzeit gespeichert werden soll.

Benutzen Sie eine der Varianten 2 bis 5, wenn Sie das neue Datumobjekt mit bestimmten Werten (also einem bestimmten Datum und einer bestimmten Uhrzeit) initialisieren wollen. Alle Initialisierungswerte wie Monat oder Minuten müssen Sie in Zahlenform angeben, also etwa 9 für Monat Oktober.

Ausnahme: bei Variante (2) übergeben Sie die Initialisierungsdaten als Zeichenkette. Dabei wird der Monat in englischer Schreibweise angegeben, also beispielsweise october.

Beachten Sie: Wenn Sie einen Monat als Zahlenwert übergeben, so wie in den Varianten 3 und 4, müssen Sie bei 0 zu zählen beginnen. Für Januar müssen Sie also 0 übergeben, für Februar 1, und für Dezember 11. Dies ist auch der Grund, warum für den Monat Oktober nicht 10 sondern 9 übergeben wird. Bei Variante 5 müssen Sie als Zahl die Anzahl der Millisekunden seit dem 01.01.1970, 0:00 Uhr UTC angeben.

Das internationale Date-Time Format

Hierbei handelt es sich um eine auf der Norm ISO8601 basierende Zeichenkettendarstellung von Datums- und Zeitangaben. Dieses Format verwendet einen proleptischen gregorianischen Kalender, das bedeutet, dass auch solche Datumsangaben als gregorianisch gedeutet werden, die vor dem Einführungszeitpunkt dieses Kalenders liegen, und dass es ein Jahr 0 gibt. Datumsangaben der Vergangenheit sind deshalb nicht unbedingt korrekt mit dem in JavaScript eingebauten Date-Objekt verarbeitbar.

ISO8601 definiert das Format als eine Abfolge folgender Zeichengruppen

YYYY Entweder: Das Jahr als vierstellige Angabe, wenn nötig mit führenden Nullen
Oder: Das erweiterte Jahr im Format ±YYYYYY.
'-' Das Zeichen '-' als Trennzeichen
MM Der Monat als zweistellige Angabe
'-' Das Zeichen '-' als Trennzeichen
DD Der Tag als zweistellige Angabe
'T' Das Zeichen 'T' als Trennzeichen zwischen Datum und Uhrzeit
HH Die Stunde als zweistellige Angabe im Bereich 00-23
':' Das Zeichen ':' als Trennzeichen
MM Die Minute als zweistellige Angabe im Bereich 00-60
':' Das Zeichen ':' als Trennzeichen
SS Die Sekunde als zweistellige Angabe im Bereich 00-60
'.' Das Zeichen '.' als Trennzeichen
sss Die Tausendstelsekunden als dreistellige Angabe im Bereich 000 bis 999
Z Entweder: Das Zeichen 'Z', um anzuzeigen, dass es sich um eine UTC-Angabe handelt
Oder: Das Zeichen '+' oder '-', gefolgt von dem Zeitzonenoffset der genutzten Zeitzone. Der Offset ist im Format HH:MM (also Stunden und Minuten) anzugeben.

Hinweise

  • Das JavaScript Zeitformat ermöglicht technisch eine Darstellung von Zeitpunkten im Bereich von 273790 Jahren um den 01.01.1970 herum. Jahresangaben außerhalb der Jahre 0000 bis 9999 können über das erweiterte Format angegeben werden, das ein '+' oder '-' als Vorzeichen und eine sechsstellige Jahreszahl verlangt
  • Die Datumsgruppe kann als YYYY, YYYY-MM oder YYYY-MM-TT angegeben werden. Fehlt der Tag, wird der Monatserste verwendet. Fehlen Tag und Monat, wird der 1. Januar verwendet. Das Jahr darf nicht fehlen.
  • Die Zeitgruppe kann ganz fehlen (das T fehlt dann auch). In diesem Fall wird die Zeit auf Mitternacht UTC gesetzt. new Date("2001-11-09") erzeugt demnach in Deutschland einen Zeitpunkt um 01:00 oder 02:00 nachts, je nachdem, ob die Winter- oder Sommerzeit gilt. Diese Merkwürdigkeit ist ein alter Spezifikationsfehler, der aus Kompatibilitätsgründen nicht mehr behoben werden kann.
  • Wird eine Zeitgruppe angegeben, aber kein Zeitzonenoffset, verwendet JavaScript den lokale Zeitzonenoffset zur Interpretation der Zeitangabe. Die Zeitgruppe kann den Aufbau HH:MM, HH:MM:SS oder HH:MM:SS.sss haben. Fehlende Teile werden auf 0 gesetzt.
  • Die Zeitzone wird als Offset in Stunden und Minuten angegeben. Die ECMAScript-Spezifikation begründet das damit, dass Namensangaben wie "CET" oder "MEZ" nicht genormt seien und man deshalb darauf verzichtet habe.

Datum ausgeben

Ein so erzeugtes Objekt kann zur Ausgabe der aktuellen Zeit verwendet werden:

Beispiel ansehen …
var datum = new Date();
document.getElementById('zeit').innerHTML = datum;

Zuerst wird ein neues Date-Objekt erzeugt und der Variable datum zugewiesen. Diese wird ausgegeben, indem das Element mit der id zeit mit innerHTML gefüllt wird.


HTML: Time-Element ansehen …
<p>
  Das heutige Datum:
  <time aria-current="date" id="zeit"></time>
</p>

Datums- und Zeitangaben können mit dem time-Element ausgezeichnet werden. Zusätzlich hat das time-Element noch ein aria-current-Attribut, um auch Screenreadern anzuzeigen, dass dies das aktuelle Datum ist.

Beachten Sie: Alle Methoden, mit denen Sie einen aktuellen Zeitpunkt oder einen Teil davon, etwa den Wochentag oder den Monat, ermitteln können, beziehen sich auf die Zeit, die auf der Systemzeit des Anwenderrechners basiert. Denn JavaScript wird beim Anwender von dessen Browser ausgeführt. Die aktuelle Systemzeit des Server-Rechners können Sie mit JavaScript nicht ermitteln.

Datumsausgaben formatieren

Wie Sie sehen, ist die normale Datumsausgabe unformatiert.

Beispiel ansehen …
var datum = new Date();
document.getElementById('zeit').innerHTML = datum;
document.getElementById('utc').innerHTML = datum.toUTCString();
document.getElementById('iso').innerHTML = datum.toISOString();
document.getElementById('loc').innerHTML = datum.toLocaleString('de-DE');

Datums- und Zeitangaben können mit verschiedenen Methoden formatiert werden. Die Methode …

  • datum.toUTCString() wandelt die Zeit in UTC (Universal Coordinated Time) um. Dies entspricht der ehemaligen Standardzeit GMT, ohne jedoch auf Sommer- oder Winterzeit einzugehen.
  • datum.toISOString() formatiert die Datumsangabe in eine Zeichenkette nach ISO 8601 um.
  • datum.toLocaleString() wird durch den übergebenen Parameter 'de-DE' in eine deutschen Gewohnheiten entsprechende Formatierung umgewandelt.

Mathematische Grundlagen

Aus einer Tage-Stunden-Minuten-Sekunden-Angabe wie z. B. 2 Tage 04:12:16 die Sekunden auszurechnen ist simpel:

Sekundenzahl = Sekunden + 60 * Minuten + 60 * 60 * Stunden + 60 * 60 * 24 * Tage 
Sekundenzahl = 16 s     + 60 * 12 min  + 60 * 60 * 4 h     + 60 * 60 * 24 * 2 d  = 187936 s

Wie aber erhält man aus 187.936 Sekunden wieder die eindeutig verständlichere Angabe? Die Marschrichtung ist dabei natürlich klar: Beim Weg hin zur Sekundenzahl wurde multipliziert, also wird auf dem Weg zurück dividiert.

Zum Lösen des Problems gehen Sie am besten schrittweise vor. Beginnend von der kleinsten Einheit an, ermitteln Sie die Minuten, Stunden und Tage aus der vorhandenen Sekundenzahl.

Eine Minute dauert 60 Sekunden. Da liegt es nahe, die Anzahl der Sekunden durch 60 zu teilen.

Minutenzahl = Sekundenzahl / 60
Minutenzahl = 187936 s / 60 = 3132,26666666666666666666666666667 min

Die Zahl vor dem Komma entspricht der Minutenzahl. Sie ist einfach zu gewinnen, indem man den Nachkommateil einfach abschneidet. Im Beispiel steht für das Abschneiden der Nachkommastellen jeweils der Ausdruck INT().

Für eine genaue Zeitangabe ist das jedoch nicht ausreichend. Auch die Nachkommastellen, welche die Restsekunden, darstellen müssen verarbeitet werden. Es liegt nahe, die Nachkommastellen mit 60 zu multiplizieren, um die Zahl der Sekunden zu ermitteln.

Sekunden = 0,26666666666666666666666666667 * 60
Sekunden = 16,0000000000000000000000000002 s

Sie sind überrascht? Mit dieser doch scheinbar so einfachen Rechenoperation erhalten Sie kein richtiges Ergebnis. Exakt 16 Sekunden gingen in die Berechnung ein. Beim Rückwärtsrechnen erhielten Sie jedoch eine größere Zahl. Rechnet der Computer falsch?

Eigentlich nicht. Die Ursache liegt darin, dass das Ergebnis der Berechnung ein unendlicher Dezimalbruch ist. Mit solchen Zahlen kann ein Computer jedoch nicht rechnen. Deshalb werden die Ergebnisse gerundet. Dieser Rundungsfehler führt zur oben sichtbaren Abweichung.

Ist es überhaupt erforderlich, mit Nachkommastellen zu arbeiten? Die allermeisten Programmiersprachen kennen einen Operator names "MODULO", wahlweise abgekürzt als "x MOD y" oder "x % y". Das Ergebnis dieses Operators steht für den Rest bei der "Ganzzahl-Division mit Rest". Wer sich an seine Grundschulzeit erinnert, wird noch wissen, wie man Zahlen dividiert, ohne Nachkommastellen zu benutzen. Das Ergebnis des Modulo-Operators ergibt sich aus folgender stark vereinfachten Rechnung:

x MOD y = x - INT(x/y)* y
Beispiel:
23 MOD 5 = 23 - INT(23/5)*5 = 23 - 4 * 5 = 23 - 20 = 3

Der Modulo-Operator ist ideal zur Lösung des Problems. Ohne Rundungsfehler erhalten Sie den ganzzahligen Rest der Division einer Zahl und in diesem Beispiel die Anzahl der Restsekunden. Zusammenfassend erhalten Sie:

Minuten  = INT(Sekundenzahl/60) = INT(187936/60) = 3132 min = Minutenzahl
Sekunden = Sekundenzahl MOD 60  = 187936 MOD 60  = 16 s

Die Anzahl der Minuten ist für den Benutzer nicht sehr viel aufschlussreicher als die Sekundenzahl, da sie immer noch zu groß ist. Jetzt bietet es sich an, zu ermitteln, wieviele Stunden und Restminuten in der ermittelten Minutenzahl enthalten sind. Das Vorgehen ist genau das gleiche: Dividieren Sie die ermittelte Anzahl der Minuten durch die Anzahl der Minuten einer Stunde und schneiden Sie die Nachkommastellen ab. Ermitteln Sie die verbleibenden Restminuten mit Hilfe des Modulo-Operators.

Stunden = INT(Minutenzahl/60) = INT(3132/60)= 52 h   = Stundenzahl
Minuten = Minutenzahl MOD 60  = 3132 MOD 60 = 12 min

Es ist nicht erforderlich, die Minutenzahl wie im Beispiel in einer separaten Variable zu speichern. Stattdessen können Sie einfach die Rechenoperation angeben, welche zur Minutenzahl führte.

Minuten = Sekundenzahl/60 MOD 60  = 187936/60 MOD 60 = 12 min
Stunden = INT(Sekundenzahl/60/60) = INT(187936/60/60)= 52 h   = Stundenzahl

Diese wenigen Umformungsschritte bewirken, dass Sie die Sekundenzahl in der Form hh:mm:ss ausgeben können. Im Beispiel würde ein Benutzer als Ausgabe 52:12:16 erhalten. Auch diese Zeitangabe ist noch nicht ideal und würde vom Benutzer einen weiteren Verarbeitungsprozess erfordern. Er müsste überlegen, wieviele Tage 52h sind. Diese Arbeit können Sie dem Benutzer abnehmen, indem Sie die ermittelte Stundenzahl in Tage umwandeln. Genau wie bei der Umwandlung in Minuten und Stunden verwenden Sie wieder die ganzzahlige Division und den Modulo-Operator. Gerechnet wird jetzt natürlich nicht mehr mit 60 Minuten pro Stunde, sondern mit 24 Stunden pro Tag.

Tage    = INT(Stundenzahl/24) = INT(52/24)= 2 Tage
Stunden = Stundenzahl MOD 24  = 52 MOD 24 = 4 h

Auch hier ist es nicht notwendig, mit dem Zwischenergebnis weiterzurechnen. Sie können stattdessen wiederum für die Stundenzahl die Rechenoperation angeben, die zur Stundenzahl führte.

Tage    = INT(Sekundenzahl/60/60/24) = INT(187936/60/60/24)= 2 Tage 
Stunden = (Sekundenzahl/60/60) MOD 24  = (187936/60/60) MOD 24 = 4 h

Diese letzte Umformung führte zu dem leicht verständlichen Ergebnis von 2 Tagen 4 Stunden 12 Minuten und 16 Sekunden. Diesen Wert kann jeder Anwender sofort interpretieren.

Sollten es Ihre Ergebnisse erfordern, so können Sie dieses Verfahren beliebig erweitern, um zum Beispiel die Anzahl der Wochen oder Jahre zu berechnen. Da es vollkommen unabhängig von vorhandenen Datumsfunktionen arbeitet, ist es nicht nur auf Zeitumwandlungen begrenzt. Sie können es auf jeden ähnlich gelagerten Anwendungsfall anwenden.

Fassen wir zusammen: Die einzelnen Teilwerte für Tage, Stunden, Minuten und Sekunden lassen sich immer mit demselben Schema von Division und Modulo berechnen. Die bei der Division anfallenden Nachkommastellen fallen grundsätzlich unter den Tisch, es kann also auch zu keinen Rundungsfehlern kommen.

Die Formeln noch einmal in der Übersicht:

Sekunden = Sekundenzahl MOD 60
Minuten  = (Sekundenzahl/60) MOD 60
Stunden  = (Sekundenzahl/60/60) MOD 24
Tage     = INT(Sekundenzahl/60/60/24)

Anwendungsbeispiele

Im Folgenden sehen Sie, wie Sie Zeitberechnungen in JavaScript praktisch umsetzen können. Da es nur wenig native Berechnungsfunktionen in JavaScript gibt, müssen Sie teilweise eigene, hier vorgestellte Helferfunktionen verwenden

Zeitspannen addieren und abziehen

Das Verändern von Datums- und Zeitangaben durch das Addieren oder Subtrahieren eines Intervalls ist eine der leichteren Übungen.

Bei Settern wie setDate() sollen Sie laut Spec nur sinnvolle Werte von 1-31 eingeben. Es mag auf den ersten Blick wie ein Bug aussehen, dass man auch höhere Werte verarbeiten kann. Dies ist bei der Berechnung von in der Zukunft liegenden Daten jedoch sehr nützlich, weil man sich bei Berechnungen nicht um einen eventuellen Monatswechsel kümmern muss. Beim Ändern des Datums mit höheren Werten wird das Datum automatisch richtig ermittelt.

Beispiel
const datum = new Date(2017, 1, 11);      // Bezugsdatum ist der 11.02.2017
datum.setDate(datum.getDate() + 50);
console.log(datum.toLocaleDateString('de-DE'));  //angezeigt wird der 02.04.2017

Im Beispiel wird zunächst ein Date-Objekt mit einem festen Datum gesetzt. Aus diesem Objekt wird der Tag im Monat mit getDate() ausgelesen, also die 11. Zum so erhaltenen Tageswert wird 50 addiert und dann mit setDate() im Date-Objekt eingetragen. Das ergibt den 61.02.2017, was von JavaScript intern auf den richtigen Monat korrigiert wird. Dabei werden auch Schalttage berücksichtigt. Das Ergebnis wird mittels toLocaleString('de-DE') nach deutschen Regeln formatiert und in die Konsole geschrieben.

In diesem Live-Beispiel können Sie ausprobieren, wie die eingegebenen Werte automatisch zu einem neuen Datum führen:

Beispiel ansehen …
function changeDate() {
  var datum = new Date(),
      tage = parseInt(this.value);
				
  datum.setDate(datum.getDate() + tage);
  updateResult(datum, tage);
}

function updateResult(datum, tage) {
  if (tage < -1)
    resultat_label.textContent = "vor " + (-tage) + " Tagen";
  else if (tage == -1)
    resultat_label.textContent = "gestern";
  else if (tage == 0)
    resultat_label.textContent = "heute";
  else if (tage == 1)
    resultat_label.textContent = "morgen";
  else
    resultat_label.textContent = "in " + tage + " Tagen";

  resultat_wert.textContent = datum.toLocaleDateString('de-DE');
}

Das Beispiel erzeugt ein neues Datumsobjekt datum mit dem aktuellen Datum, zu dessen mit getDate() ermitteltem Tageswert der Wert des Sliders addiert wird.

Diese Summe wird mit setDate() neu gesetzt. Danach werden der Sliderwert und das neu gesetzte Datum in die entsprechenden HTML Elemente der Testseite ausgegeben.

Analog können Sie auch mit setFullYear(), setMonth(), setHours() und setMinutes() verfahren.

Differenz zwischen zwei Zeitangaben

Schwieriger ist es eine Differenz zwischen zwei gegebenen Zeitangaben zu ermitteln, da date-Objekte nicht einfach voneinander subtrahiert werden können.

Über die Methode getTime() können Sie aber die Anzahl der Millisekunden ausgeben und diese voneinander subtrahieren:

Beispiel
var startDate = new Date(),
    endDate   = new Date(),
    differenz = (endDate.getTime() - startDate.getTime());

Das Beispiel enthält zwei Datums-Objekte, die in Zeile 3 jeweils mit der getTime-Methode in Millisekunden umgewandelt und voneinander subtrahiert werden.

Der so ermittelte Wert ist in Millisekunden und muss nun noch passend formatiert werden.


Formatierung der Ausgabe

In den obigen Berechnungen wurde keine Zeit, sondern nur die Anzahl Millisekunden ermittelt. Sie können dies aber in eine lesbare Zeitangabe formatieren:

Beispiel
function fuehrendeNull(wert) {
  if (wert < 10) return "0" + parseInt(wert);
  else return parseInt(wert);
}

function Sekundenumwandeln(Sekundenzahl) {
  Sekundenzahl = Math.abs(Sekundenzahl)
  return parseInt(Sekundenzahl/60/60/24)+ " Tage " + fuehrendeNull((Sekundenzahl/60/60)%24) + ":" +
                 fuehrendeNull((Sekundenzahl/60)%60) + ":" + fuehrendeNull(Sekundenzahl%60);
}

console.log(Sekundenumwandeln(187936));


Da JavaScript keine speziellen Formatierungsfunktionen für Ausgaben kennt, wurde die Formatierung in die Funktion Sekundenumwandeln() ausgelagert. Der Rückgabewert der Funktion ist ein formatierter String, der dann ausgegeben wird. Der ganzzahlige Anteil einer Zahl wird mit der Methode parseInt() ermittelt. Die Funktion fuehrendeNull() erledigt 2 Aufgaben. Sie fügt gegebenenfalls eine führende Null an und gibt den ganzzahligen Wert des übergebenen Parameters zurück.

Der Tag hat 24 Stunden – oder nicht?

Nein, hat er nicht. Auch der Kalendertag nicht. Die meisten jedoch schon. Derzeit gibt es zwei Gründe für Abweichungen: Die Sommerzeit und Schaltsekunden.

Während der Sommerzeit wird in Mitteleuropa die Zonenzeit um eine Stunde vorgestellt, es gilt dann die Mitteleuropäische Sommerzeit (MESZ) statt der Mitteleuropäischen Zeit (MEZ). Damit ist der letzte Sonntag im März eine Stunde kürzer und der letzte Sonntag im Oktober eine Stunde länger.

Die Erdrotation verlangsamt sich aufgrund der Gezeitenreibung und schwankt zudem. Um diese Unregelmäßigkeiten auszugleichen, werden Schaltsekunden (entweder am 30. Juni oder am 31. Dezember) eingefügt. Der Internationale Dienst für Erdrotation und Referenzsysteme (IERS) beobachtet die Erdrotation und vergleicht diese mit der Internationalen Atomzeit. Wenn sich die Zeitverschiebung zwischen UTC und UT1 an 0,9 Sekunden annähert, wird eine Schaltsekunde angeordnet.[2] Damit können diese beiden Tage 86.401 Sekunden dauern.

Anzahl der Tage im Monat

Beispiel ansehen …
"use strict";
function getDays() {
  const month = document.getElementById('month'),
  m = parseInt(month.value),
  name = month.options[month.selectedIndex].text,
  year = document.getElementById('year').value,
  output = document.getElementById('output'),
  
  array31 = [0, 2, 4, 6, 7, 9, 11],
  array30 = [3, 5, 8, 10];
  
  if (array31.includes(m)) {
    output.innerHTML = '31 Tage. <small>(Der ' + name + ' hat immer 31 Tage.)</small>';
  }
  else if (array30.includes(m)) {
    output.innerHTML = '30 Tage. <small>(Der ' + name + ' hat immer (nur) 30 Tage.)</small>';
  }
  else {
    const date29_2 = new Date(year,1,29);
    if (date29_2.getMonth() == 1) {
      output.innerHTML = '29 Tage. <small>(Der ' + name + ' hat in Schaltjahren immer (nur) 29 Tage.)</small>';
    }
    else {
      output.innerHTML = '28 Tage. <small>(Der ' + name + ' hat in Gemeinjahren immer (nur) 28 Tage.)</small>';
    }
  }
}
document.addEventListener('change',getDays);
document.addEventListener('DOMContentLoaded',getDays);

Spannend ist ja nur der Februar. Hier machen wir uns die eingebaute Fehlerkorrektur zu Nutze, indem wir den 29. Februar erzeugen. Für Gemeinjahre (etwa 2019) macht JavaScript aus dem 29. Februar den 1. März, anhand des Monats können wir also entscheiden, ob das Jahr ein Gemein- oder ein Schaltjahr ist.


Der erste Dienstag im November

Beispiel ansehen …
const k = document.getElementById('k').value,
      day = document.getElementById('day').value,
      month = document.getElementById('month').value,
      year = document.getElementById('year').value,
      output = document.getElementById('output'),
              
      firstDayOfMonth = new Date(year,month,1),
      offset = (day - firstDayOfMonth.getDay() + 7) % 7,
      targetDay = 1 + offset + 7 * k,
      target = new Date(year,month,targetDay);
        
output.innerText = (target.getMonth() == month) ? ' der ' + targetDay + '.' 
                                                : ' eine ungültige Datumsangabe.';

Wir bestimmen zunächst den Wochentag des Monatsersten und ermitteln den Abstand zum gesuchten (ersten) Wochentag (offset). Nun addieren wir die entsprechenden Tage. Falls wir uns nach der Addition schon im nächsten Monat befinden, gibt es das gesuchte Datum nicht (wie z. B. den 6. Dienstag im Februar). Dabei machen wir uns zu Nutze, dass JavaScript ungültige Datumsangaben in gültige unwandelt. So wird etwa aus einem 32. Januar ein 1. Februar.


Prüfen, ob ein Schaltjahr vorliegt

Anhand der Schaltregeln des gregorianischen Kalenders können wir überprüfen, ob ein Jahr ein Schaltjahr ist.

  1. Ein Jahr ist ein Schaltjahr, wenn seine Jahreszahl durch 4 teilbar ist
  2. aber es ist kein Schaltjahr, wenn seine Jahreszahl durch 100 teilbar ist
  3. aber es ist doch ein Schaltjahr, wenn seine Jahreszahl durch 400 teilbar ist

Das lässt sich zusammenfassen zu

  • Ein Jahr ist ein Schaltjahr, wenn seine Jahreszahl durch 4 aber nicht durch 100 teilbar ist.
  • Ein Jahr ist ein Schaltjahr, wenn seine Jahreszahl durch 400 teilbar ist.
Beispiel
function isLeapYear(year) {
  return ((year % 4 == 0 && year % 100 != 0)) || (year % 400 == 0);
}
console.log(isLeapYear(2020)); // true;

Die zweite Variante wurde weiter oben schon beschrieben. Dabei wird ausgenutzt, dass JavaScript versucht, ungültige Datumsangaben in gültige umzuwandeln. So wird aus dem 29.2.2021 der 1.3.2021, der 29.2.2024 bleibt jedoch der 29.2.2024.

Beispiel
function isLeapYear(year) {
  const date29_2 = new Date(year,1,29);
  return (1 == date29_2.getMonth());
}
console.log(isLeapYear(2021)); // false;

Aus der Nummer des laufenden Tages das Datum ermitteln

Beispiel ansehen …
"use strict";
function getDate() {
  const z = document.getElementById('z').value,
        year = document.getElementById('year').value,
        output = document.getElementById('output'),
        target = new Date(year,0,z),
        targetDay = target.getDate(),
        targetMonth = target.getMonth() + 1,
        targetYear = target.getFullYear();
        
  output.innerText = (targetYear == year) ? ' der ' + targetDay + '.' + targetMonth + '.' 
                                          : ' eine ungültige Datumsangabe.';
}
document.addEventListener('change',getDate);
document.addEventListener('DOMContentLoaded',getDate);

Wieder wird die automatische Fehlerkorrektur ausgenutzt. Wir erzeugen einfach n-ten Tag des Jahres als n-ten Tag des Januars und schauen uns an, welches Datum dabei herauskommt.


noch x Tage bis Weihnachten

Beispiel ansehen …
"use strict";
function init() {
  let output = '';
  const outputElement = document.getElementById('output'),
        today = new Date(),
        year = today.getFullYear(),
        thisChristmas = new Date(year, 11, 24),
        nextChristmas = new Date(year + 1, 11, 24);
  today.setHours(0);
  today.setMinutes(0);
  today.setSeconds(0);
  today.setMilliseconds(0);
  const days = Math.round((thisChristmas - today < 0 ? nextChristmas - today : thisChristmas - today) / 86400000);

  switch (days) {
    case 0 : 
      output = 'Heute';
      break;
    case 1 :
      output = 'Morgen';
      break;
    case 2 :
      output = 'Übermorgen';
      break;
    default :
      output = 'In ' + days + ' Tagen';
      break;
  }
  output += ' ist Weihnachten.';
  outputElement.innerText = output;
}
document.addEventListener('DOMContentLoaded', init);

Wir beschaffen uns drei mitternächtliche Zeitstempel: den von heute, den des diesjährigen Heiligen Abends und den des nächstjährigen Heiligen Abends. Wenn die Differenz zwischen thisChristmas und today negativ ist, befinden wir uns zwischen Heilig Abend und Neujahr, wir müssen also schon an nächstes Jahr denken.

Zeitstempel werden in Millisekunden angegeben, die 86.400.000 ist die Anzahl der Millisekunden eines Tages. Wenn wir uns in der Sommerzeit befinden, haben wir aber einen Tag dabei, der eine Stunde länger ist. Deshalb müssen wir das Ergebnis der Division noch runden.


Ostern

Ein Beispiel für eine komplexere Datumsberechnung ist die Osterformel. Wobei - mit Datumsrechnungen hat die Osterformel nicht viel zu tun. Sie verwendet das Jahr als Eingabe, berechnet, wann in diesem Jahr der erste Frühjahrsvollmond ist und legt Ostern dann auf den ersten Sonntag nach diesem Vollmond. Dahinter stecken antike Astronomie-Erkenntnisse, etwas Wochentags- und Schaltsjahrsrechnung, und vor allem eins: die Festlegungen des Konzils von Nizäa.

Entwicklung

Aus Nizäa stammt die Festlegung des 21. März als Frühlingsbeginn. Astromisch betrachtet kann er am 19., 20. oder 21. März sein. Seit 2012 ist er am 20. März, wird es bis 2047 bleiben und bis Ende des 21. Jahrhunderts zwischen 19. und 20. März wechseln. Das war in Nizäa bekannt, aber um die Berechnung nicht zu komplex zu machen, hat man sich auf den 21. März festgelegt. Der Festlegung des „ersten Sonntages nach erstem Frühlingsvollmond“ liegt ein langer Kirchenstreit und einiges Durcheinander beim Festlegen des Ostertermins zugrunde, vor allem deshalb, weil die Abstimmung mit dem jüdischen Kalender nicht so einfach ist und auch keine Einigkeit bestand (und besteht), wann genau in Bezug auf Pessach die Kreuzigung stattfand. Nizäa legte auch fest, dass zur Berechnung der julianische Kalender und nicht der jüdische Mondkalender zu verwenden sei.

Der eigentliche Algorithmus, der „Computus paschalis“, entstand im damaligen Wissenschaftszentrum Alexandria. Im Jahr 530, 205 Jahre nach Nizäa, wurden die ersten Ostertafeln veröffentlicht und es dauerte noch einmal 200 Jahre, bis sie allgemeinverbindlich durchgesetzt waren.

850 Jahre später, mit der gregorianischen Kalenderreform, begann dann das Chaos erneut, weil die orthodoxe Kirche für die Osterberechnung beim julianischen Kalender geblieben ist.

Der Computus paschalis war nicht ganz leicht zu handhaben. Kern der Rechnung ist die babylonische Beobachtung, dass alle 19 Jahre der erste Frühjahrsvollmond auf den gleichen Tag fällt (der Mondzirkel), also 235 Mondmonate 19 Erdenjahren entsprechen. Allerdings passt es nicht ganz. Astronomisch sind 19 Sonnenjahre (365,24219 Tage pro Jahr) 2:05:20 Stunden kürzer als 235 Mondmonate (29,53059 Tage pro Monat), der Julianische Kalender (365,25 Tage pro Jahr) ist 1:28:21 Stunden länger. Die Osterformel verwendet für die Mondberechnung immer noch das Julianische Jahr, fügt aber eine Korrekturgleichung ein, die dafür sorgt, dass in 2500 Jahren insgesamt acht Tage aus der Länge des Mondzyklus herausgenommen werden. Dadurch kommt man auf einen Fehler von nur noch 48 Sekunden in 19 Jahren, oder 1:45:16 Stunden in 2500 Jahren. Ein weiterer Fehler ist, dass die Dauer eines synodischen Monats (von Neumond zu Neumond) als konstant angenommen wird, aber in Wahrheit schwankt die Umlaufzeit des Mondes um mehrere Stunden um einen Mittelwert und ist über die Jahrtausende auch nicht konstant.

Um die Berechnung für jedermann nachvollziehbar zu machen, entwickelte Carl Friedrich Gauß im Jahre 1800 einen Algorithmus, der die Computusregeln in einfache Ganzzahldivisionen abbildet. Und, was ihn für heutige Programmierer sympathisch macht: er baute prompt einen Bug ein, denn er verwendete 2400 statt 2500 Jahre für die Länge des Korrekturzyklus im Mondzyklus. 1816 folge die Osterformel 2.0, die das berichtigte. Und wie in den meisten Programmen gibt es einen weiteren Bug, der per Dienstanweisung „korrigiert“ wird: Ostern wird spätestens am 25. April gefeiert (ein Erbe des julianischen Kalenders, das von Gregor übernommen wurde). Die Formel kann aber auch den 26. April liefern, Ostern wird dann eine Woche vorverlegt.

Gauß hätte das relativ einfach einbauen können, wenn ihm jemand den Trick mit dem ternären Operator ?: verraten hätte. Aber auch mit Integerdivisionen kann man das fixen, was 1870 von Hermann Kinkelin erledigt wurde. 1997 folgte dann noch eine „kommentierte Fassung“ von Heiner Lichtenberg, die Gauß' Zwischenrechnungen Namen gab und damit das Verständnis erleichterte. Allerdings verkomplizierte er die Mondkorrektur von Kinkelin wieder.

Der Algorithmus

Wir zeigen die Rechenschritte hier im Wesentlichen nach Heiner Lichtenberg. Der „26. April“-Fix ist in der Fassung von Kinkelin notiert. Die Rechnung erfolgt auf etwas anderem Weg als bei Gauß, kommt aber zum gleichen Ergebnis. Mit div ist das ganzzahlige Divisionsergebnis gemeint, 10 div 3 wäre beispielsweise 3. a mod b meint den Rest der Division von a durch b, 13 mod 5 ergäbe 3. Quelle ist der Wikipedia-Artikel zur Gaußschen Osterformel[3], die weiteren Erläuterungen sind von uns formuliert, basieren aber ebenfalls auf Wikipedia-Inhalten.

Berechnung Bedeutung
X X sei das Jahr, für das Ostern berechnet werden soll
K = X div 100 Die „Säkularzahl“ (der Jahrhundertteil der Jahreszahl)
S = 2 - (3⋅K+3) div 4 Die 400-Jahre Schaltung (Sonnengleichung).
M = 15 + (3⋅K+3) div 4 - (8⋅K + 13) div 25 Die Mondgleichung
A = X mod 19 Der Mondparameter: Das wievielte Jahr im Mondzyklus
D = (19⋅A + M) mod 30 Vollmondtag im Mondzyklus
R = (D + A div 11) div 29 Der „26. April“-Fix nach Kinkelin
OG = 21 + D - R Die „Ostergrenze“, eigentlich der Tag (als Märzdatum), an dem Vollmond ist
SZ = 7 - (X + X div 4 + S) mod 7 Tageszahl des ersten Sonntags im März
OE = 7 - (OG - SZ) mod 7 Wieviele Tage liegt der Ostersonntag hinter dem Ostervollmond
OS = OG + OE Der Ostertermin als Märzdatum (d.h. der 32. März ist der 1. April, usw.)

Zentral für die Berechnung ist die Ermittlung von D. Dies ist der der Vollmondtag im 19-Jährigen Mondzyklus (auch Lunisolarzyklus genannt). Die Berechnung von A liefert in einem 19-Jahre Rhythmus die Werte von 0 bis 18. Rechnet man nun 19A mod 30, ergibt sich eine Folge, deren Werte entweder um 11 kleiner werden oder um 19 größer. 12 Mondumläufe sind 354,36 Tage, also ca. 11 Tage weniger als ein Jahr mit 365,2425 Tagen. 13 Mondumläufe sind 383,9 Tage, also ca. 19 Tage mehr als ein Jahr. In Summe ergeben sich in 19 Jahren 235 Mondumläufe.

Die Sonnengleichung benutzt den Term (3⋅K+3) div 4. Dieser Ausdruck liefert die Anzahl der ausgefallenen Schaltjahre seit dem Jahr 0 (also die nicht durch 400 teilbaren vollen Jahrhunderte), wenn der gregorianische Kalender seitdem gegolten hätte.

Die Mondgleichung besteht zunächst aus einem konstanten Wert (15), der nichts weiter tut, als die Zahlenfolge des Vollmondtages passend zur Realität und passend zu den weiteren Korrekturwerten zu verschieben. Verwendet man den julianischen Kalender, ist M konstant 15, die übrigen Teile der Mondgleichung entfallen.

Weil sich jedes ausgefallene Schaltjahr durch eine eintägige Verschiebung im Mondzirkel bemerkbar macht, finden wir als nächstes den Korrekturterm aus der Sonnengleichung. Der dritte Formelteil (8⋅K+13) div 25 liefert eine Folge, die in 25 Jahrhunderten jeweils um 8 wächst und damit die 8 Tage ausgleicht, die der Mondzyklus in 2500 Jahren zu lang ist. Diese beiden Korrekturwerte passen den antiken Mondzyklus an die gemessene astronomische Umlaufzeit des Mondes und an die vom gregorianischen Kalender festgelegte Jahreslänge an.

Der Wert R bildet die vom gregorianischen Kalender übernommene Limitierung des Osterdatums auf den 25. April. Wenn der Vollmond laut Mondzyklus am 29. Tag nach Frühlingsbeginn ist, wird der Vollmondtag um einen Tag zurückgenommen. Am 28. Tag des Frühlingsbeginn kann das auch geschehen, aber nur für A≧11. Die Erkenntnis von Kinkelin war, dass das Zurücknehmen von D um nur einen Tag genügt, damit in der nachfolgenden Sonntagsbestimmung der Sonntag der Vorwoche gefunden wird.

Wenn Sie die Formel für R mit unterschiedlichen Werten für D (0..29) und A (0..18) durchspielen, werden Sie sehen, dass in den genannten Fällen 1 herauskommt, sonst 0. Ein typischer Fall von „Nicht Nachmachen, Kinder!“, denn dieser Zeile sieht man spontan nicht mehr an, was sie tut. Die verständlichere Form wäre wohl, R durch eine Abfragebedingung zu ermitteln: R := (D==29 || D==28 && A>=11) ? 1 : 0.

Zum Abschluss muss noch der auf diesen Vollmondtag folgende Sonntag ermittelt werden. Dafür wird zunächst das Datum des ersten Sonntags im März bestimmt. Da ein normales Jahr 365 Tage hat, wird die Tageszahl dieses Sonntags pro Jahr um 1 (normale Jahre) oder 2 (Schaltjahre) geringer, bis sie unter 0 fällt und der Sonntag der nächsten Woche verwendet wird. Das drückt sich in der Berechnung 7 - (X + X div 4) mod 7 aus, die hiermit erzeugte Zahlenfolge entspräche dem Rhythmus der ersten Märzsonntage, wenn nicht die 400-Jahre Komponente der Schaltjahrsregelung wäre. Hierfür dient S: es bestimmt die Anzahl der Hunderterjahre, die seit dem Jahr 0 als Schaltjahr ausgefallen sind (unter der Annahme, dass der gregorianische Kalender da schon gegolten hätte) und gleicht damit die Schaltjahre aus, die X div 4 zuviel berechnet. Die Zahl 2, die in der Sonnengleichung steht, dient zur Einstellung des Märzsonntagzyklus auf den realen Kalender.

Die Berechnung von OE aus Ostergrenze und Sonntagszahl ist eine geschickte Ausnutzung des Divisionsrests bei der Division durch 7. Da der 7.3. und der 21.3. 14 Tage auseinander liegen, ist der Divisionsrest durch 7 gleich, und gibt an, wieviele Tage der 7.3. oder der 21.3. vom letzten Sonntag weg sind. Für jeden Tag, den man vom 21.3. nach vorn geht, entfernt man sich einen weiteren Tag vom Sonntag, so dass (OG-SZ) mod 7 für jeden möglichen Ostertag seine Entfernung vom letzten Sonntag liefert. Zieht man das von 7 ab, erhält man die Tage bis zum nächsten Sonntag.

Ostern - Live

In JavaScript-Code sieht das nun wie folgt aus. Das Beispiel ist live, d.h. Sie können die Jahreszahl verändern und sehen das Ergebnis mit allen Teilschritten. Die Korrekturformel (R) von Kinkelin haben wir durch eine Abfrage ersetzt: Ist D==29, oder D==28 und A>10, ist der Korrekturwert 1, sonst 0.

Osterformel ansehen …

Siehe auch

  • Timer und Countdown



    Zeit hoch und runter zählen
    Ausführungszeiten messen

  • Monatskalender
    Date-Icon.svg
  • input type="date" und mehr
    HTML5 bietet eine Vielzahl von neuen Eingabe-Typen für Datums- und Zeitangaben.

Weblinks

  1. Dieser Artikel ist die umfassende Neubearbeitung eines Selfhtml-aktuell-Artikels von Antje Hofmann aus dem Jahre 2002 mit dem Thema Zeitberechnung.
  2. timeanddate.de: Warum gibt es Schaltsekunden?
  3. Wikipedia: Die Gaußsche Osterformel