Nutzungsvorgaben Datum & Zeit

IFS-Logo Diese Seite ist ein Teil der IsyFact-Standards. Alle Inhalte der Seite, insbesondere Texte und Grafiken, sind urheberrechtlich geschützt. Alle urheberrechtlichen Nutzungs- und Verwertungsrechte liegen beim Bundesverwaltungsamt.

Creative Commons Namensnennung Die Nutzung ist unter den Lizenzbedingungen der Creative Commons Namensnennung 4.0 International gestattet.

1. Einleitung

Im Konzept Datum & Zeit werden die Anforderungen und Vorgaben zu Datum und Zeit der IsyFact definiert, um eine einheitliche Umsetzung in der gesamten Anwendungslandschaft zu gewährleisten.

Dieses Dokument beschreibt alle Aspekte, die bei der Entwicklung einer Anwendung zu berücksichtigen sind, um die definierten Anforderungen zu erfüllen. Die IsyFact stellt eine eigene Bibliothek (isy-datetime) bereit, um die Anforderungen einheitlich umsetzen zu können. Kern dieses Dokuments sind Vorgaben zur Nutzung der Java 8 Date and Time API des JSR 310 und die Verwendung der zuvor genannten Bibliothek. Das Dokument richtet sich daher vorrangig an Entwickler, Konstrukteure und technische Chefdesigner von Anwendungssystemen, die gemäß den Vorgaben der IsyFact umgesetzt werden.

2. Aufbau und Zweck des Dokuments

Der Zweck dieses Dokuments ist Vorgaben zur Nutzung der Java 8 Date and Time API zu machen und die Verwendung der Bibliothek isy-datetime zu beschreiben.

Das Dokument ist entsprechend dieser Zielsetzungen in die folgenden Kapitel untergliedert:

Das Kapitel Dauern und Zeitzonen enthält grundlegende Informationen zum Umgang mit Dauern und Zeitzonen.

Im Kapitel Verwendung der Bibliothek wird der Inhalt und die Verwendung der Bibliothek isy-datetime beschrieben.

3. Dauern und Zeitzonen

Dieses Kapitel enthält einige grundlegende Informationen zum Umgang Dauern und Zeitzonen.

3.1. Zeitdauer mit Period und Duration

Zur Darstellung einer Dauer sind die Java 8 Typen Period und Duration zu verwenden.

Eine Period ist eine datumsbasierte Menge von Zeit, wie z.B. „2 Jahre, 3 Monate und 4 Tage“. Eine Dauer wird also auf der Basis von Jahren, Monaten und Tagen modelliert.

Eine Duration ist eine zeitbasierte Menge von Zeit, wie z.B. „34,5 Sekunden“. Eine Dauer wird hier auf der Basis von Sekunden und Nanosekunden modelliert. Der Zugriff ist auch über andere Zeiteinheiten, wie z.B. Minute, Stunde oder Tag, möglich.

Period und Duration unterscheiden sich bei Berechnungen mit einer ZonedDateTime im Zusammenhang mit der Sommerzeit. Mit einer Duration wird eine exakte Anzahl von Sekunden hinzuaddiert und die Dauer eines Tages ist somit immer 24 Stunden. Im Gegensatz dazu wird bei einer Period ein „konzeptioneller“ Tag hinzuaddiert. Wenn also am Tag vor der Umstellung auf die Sommerzeit um 18:00 eine Duration von einem Tag addiert wird, wird die resultierende ZonedDateTime am nächsten Tag auf 19:00 stehen. Bei der Addition einer Period wird dagegen die Uhrzeit auf 18:00 stehen, obwohl das nur 23 Stunden sind.

Das Mischen der beiden Darstellungen (2 Monaten, 3 Stunden) ist nicht möglich.

3.2. Zeitzonen und Offsets

Zum allgemeinen Verständnis folgen ein paar Erläuterungen zum Thema Offsets (Zeitverschiebung) und Zeitzonen.

Eine Zeitzone kann nicht nur durch einen Offset von UTC repräsentiert werden. Viele Zeitzonen haben mehr als einen Offset aufgrund der Sommerzeitumstellung. Das Datum, an dem die Umstellung von Normal- auf Sommerzeit und wieder zurück erfolgt, ist ebenfalls Teil der Zeitzonenregeln. Sollten sich diese Datumsangaben im Laufe der Geschichte geändert haben, sind auch diese Daten in den Regeln für die jeweilige Zeitzone enthalten.

  • Ein Offset / Zeitverschiebung ist nur eine Zahl die besagt, wie weit bestimmte Datums- und Zeitangabe hinter oder vor der UTC-Zeit ist.

    • Die meisten Offsets sind in ganzen Stunden angegeben.

    • Es existieren aber auch Offsets mit einem 30-minütigen Offset.

    • Einige wenige verwenden 45-minütigen Offset.

  • Eine Zeitzone enthält deutlich mehr:

    • Ein Name oder ID, mit der die Zone eindeutig identifiziert werden kann.

    • Einer oder mehrere Offsets von UTC

    • Die Daten und Zeiten, an denen zwischen den Offsets gewechselt wird.

    • Optional ein sprachspezifischer Anzeigename für die Anzeige beim Nutzer.

3.3. Abkürzungen für Zeitzonen / Offsets

Häufig werden Zeitzonen oder Offsets mit drei oder mehr Buchstaben abgekürzt, wie z.B. MEZ für „Mitteleuropäische Zeit“ oder PST für "Pacific Standard Time". Die Verwendung solcher Abkürzungen kann aber zu Problemen führen:

  • Häufig sind Abkürzungen nicht eindeutig. Es gibt fünf Interpretationen für “CST”:

    • Central Standard Time (Nord Amerika)

    • Central Standard Time (Australien)

    • Central Summer Time (Australien)

    • China Standard Time

    • Cuba Standard Time

  • Bestenfalls wird durch eine Abkürzung ein Offset, aber keine Zeitzone repräsentiert. In Deutschland wird beispielsweise die Mitteleuropäische Zeit (MEZ) zur Normalzeit, aber die Mitteleuropäische Sommerzeit (MESZ) während der Sommerzeit.

  • Ein großer Teil der Abkürzungen kommt aus dem Englischen. Manchmal existiert eine gültige Abkürzung auf Deutsch (z.B. MEZ für CET) und es kann zu Verwirrung kommen, welche Abkürzung genutzt werden soll.

Wenn solche Abkürzungen genutzt werden, sollte diese nur zur Anzeige für den Nutzer verwendet werden. Abkürzungen sollten nicht geparst oder anderweitig technisch verarbeitet werden. Die Abkürzungen "UTC" und "GMT" sind die einzigen, die davon explizit ausgenommen sind.

4. Verwendung der Bibliothek isy-datetime

In diesem Abschnitt werden Inhalt und Einsatz der Bibliothek isy-datetime beschrieben.

4.1. Inhalt von isy-datetime

4.1.1. Formatierung

Zur formatierten Ein- und Ausgabe stellt isy-datetime die Klassen InFormat und OutFormat bereit. Diese beinhalten vorkonfigurierte DateTimeFormatter für die im Konzept beschriebene Formate. Die Verarbeitung in der ISO-8601-konformen Darstellung wird direkt durch die Klassen der Java 8 Date and Time API unterstützt.

4.1.1.1. Ausgabeformate für Datum- und Zeitangaben

Folgende Formate und deren zugehörige DateTimeFormatter werden von isy-datetime in der Klasse InFormat bereitgestellt:

Tabelle 1. Datum Zeit Format
Format (Beispiel) Formatter in OutFormat

Montag, 17. Juli 2017 14:35:19 MESZ

DATUM_ZEIT_LANG_TAG_ZONE

17. Juli 2017 14:35:19 MESZ

DATUM_ZEIT_LANG_ZONE

Montag, 17.Juli 2017 14:35:19

DATUM_ZEIT_LANG_TAG

17.07.2017 14:35:19 +02:00

DATUM_ZEIT_ZONE

17.07.2017 14:35:19

DATUM_ZEIT

17.07.2017

DATUM

14:35:19

ZEIT

14:35

ZEIT_KURZ

4.1.1.2. Eingabeformate für Datum- und Zeitangaben

Für die Eingabe werden DateTimeFormatter bereitgestellt. Diese sind nach Datum und Zeit getrennt. Zur direkten Verwendung dieser Formate stellt die Klasse InFormat verschiedene Methoden zum Parsen in verschiedene Typen bereit.

Tabelle 2. Datum Zeit Eingabeformate
Methode Mögliche Eingabe (Beispiel)

parseToLocalTime()

“15:24“

parseToLocalDate()

“30.08.2017“

parseToLocalDateTime()

“17.07.2017 14:35:19“

parseToOffsetTime()

“14:35:19 +02:00“

parseToOffsetDateTime()

“17.07.2017 14:35:19 +02:00“

parseToZonedDateTime()

“17.07.2017 14:35:19 Europe/Berlin“

4.1.1.3. Formate für Dauer

Zur Eingabe von Dauern werden folgende Methoden von InFormat bereitgestellt:

Tabelle 3. Formate für Dauer
Methode Mögliche Eingabe (Beispiel)

parseToDuration()

4h 3min 2s 1ms

parseToPeriod()

7a 6M 5d

Hierbei ist zu beachten, dass nicht alle Zeiteinheiten von Period bzw. Duration unterstützt werden. Enthält die Zeichenkette Zeiteinheiten, die nicht unterstützt werden, wird eine Exception geworfen.

4.1.1.4. Formate für Zeitraum

Zur Eingabe von Zeiträumen stellt die Klasse Zeitraum die Methode parse() bereit. Unterstützt werden die Eingabe von Anfang und Ende oder von Anfang und Dauer.

4.1.2. Zeitraum

Die Repräsentation eines Zeitraums im Sinne des Konzepts wird nicht durch die Java 8 Date and Time API bereitgestellt. Für diesen Zweck stellt isy-datetime die Klasse Zeitraum bereit.

Ein Zeitraum kann aus einem zwei Datumsangaben, zwei Datums- und Zeitangaben oder nur aus Zeitangaben erstellt werden. Das angegebene Ende ist immer exklusive und nicht Teil des Zeitraums. Ein Zeitraum, der nur aus Zeiten besteht, kann nicht länger als 24 Stunden sein, aber über einen Tageswechsel (22:00 – 06:00) gehen. Bei Zeiträumen die nur aus Datumsangaben bestehen, ist die Anfangszeit 00:00 des Anfangstages und die Endzeit 00:00 des Endtages.

Intern werden Anfang und Ende mit Angabe der Zeitzone gespeichert, um die Dauer bei Zeitumstellungen korrekt berechnen zu können. Wird bei der Erstellung keine Zeitzone angegeben, wird Standard-Zeitzone der JVM verwendet.

4.2. Berechnungen

Wenn möglich, sollten Berechnungen mit Datums- und Zeitangaben mit den Bordmitteln der Java 8 Date and Time API umgesetzt werden. Für die im Konzept geforderten Berechnungen gibt es folgende Möglichkeiten zur Umsetzung:

  • Chronologische Sortierung einer Menge von Datums- und Zeitwerten und chronologische Vergleiche zwischen Datums- und Zeitwerten („früher als“, „später als“, „gleichzeitig“).

Die Klassen LocalDate, LocalTime, LocalDateTime, OffsetTime, OffsetDateTime, ZonedDateTime und Duration implementieren das Interface Comparable, daher ist ein Sortieren mit Bordmitteln möglich (bspw. mit Collections.sort()).

Für chronologische Vergleiche werden die Methoden isBefore(), isAfter(), isEqual() bereitgestellt. Bei LocalTime ist die Methode equals() zu verwenden. Der Unterschied zwischen equals() und isEqual() besteht darin, dass isEqual() einen chronologischen Vergleich und equals() einen „technischen“ Vergleich durchführt. Dazu ein Beispiel:

Listing 1. Vergleich von Zeiten mit equals() und isEqual()
OffsetTime offsetTime1 = OffsetTime.of(15, 0, 0, 0, ZoneOffset.ofHours(2));
OffsetTime offsetTime2 = OffsetTime.of(13, 0, 0, 0, ZoneOffset.UTC);
offsetTime1.isEqual(offsetTime2); // true
offsetTime1.equals(offsetTime2); // false

JapaneseDate todayJapanese = JapaneseDate.now();
LocalDate todayLocal = LocalDate.now();
todayJapanese.equals(todayLocal); // false
todayJapanese.isEqual(todayLocal); // true
  • Ermittlung des „Tagesdatums“ (der zeitliche Anteil eines Datumswerts wird auf 0, d.h. auf Mitternacht, gestellt)

    Dafür sollte die Methode toLocalDate() verwendet werden. Wenn das Ergebnis kein LocalDate sein soll, sondern einer Objekt des gleichen Typs mit dem Zeitwert auf 0 gesetzt, ist die Methode truncatedTo(ChronoUnit.DAYS) zu verwenden.

  • Berechnung des Abstands („Dauer“) zwischen zwei Datums- oder Zeitwerten

    Es ist die Methode until() der Datum- und Zeitklassen oder die Methode between() des Interfaces TemporalUnit zu verwenden. Beispiele:

Listing 2. Berechnung des Abstands zwischen zwei Datums- oder Zeitwerten
LocalTime time1 = LocalTime.of(15, 0);
LocalTime time2 = LocalTime.of(16, 30);

time1.until(time2, ChronoUnit.MINUTES); // 90
ChronoUnit.MINUTES.between(time1, time2); // 90

LocalDate date1 = LocalDate.of(2017, 8, 1);
LocalDate date2 = LocalDate.of(2017, 9, 1);

date1.until(date2); // Period von 1 Monat
ChronoUnit.DAYS.between(date1, date2); // 31

ZonedDateTime zonedDateTime1 = ZonedDateTime.of(
  LocalDateTime.of(2017, 3, 25, 18, 0),
  ZoneId.of("Europe/Berlin"));
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(
  LocalDateTime.of(2017, 3, 26, 18, 0),
  ZoneId.of("Europe/Berlin"));
zonedDateTime1.until(zonedDateTime2, ChronoUnit.DAYS); // 1
ChronoUnit.HOURS.between(zonedDateTime1, zonedDateTime2); // 23
  • Addition und Subtraktion von Datums- und Zeitwerten mit einer Dauer.
    Die Datums- und Zeitklassen von Java 8 stellen umfangreiche plus*() und minus*()-Methoden zur Addition/Subtraktion von Datums- und Zeitwerten bereit.

  • Prüfung, ob zwei Datumswerte für zwei direkt aufeinanderfolgende Tage stehen ("Liegt der 28.2.2016 direkt vor dem 1.3.2016?“).

Listing 3. Berechnung mit aufeinanderfolgenden Tagen
LocalDate date1 = LocalDate.of(2016, 2, 28);
LocalDate date2 = LocalDate.of(2016, 3, 1);

date1.plusDays(1).isEqual(date2)); // false
  • Berechnung des nächsten Werktags nach einem übergebenen Datumswert.

    Die Klasse DateTimeUtil stellt hierfür die Methode getWerkTag(LocalDate datum) bereit.

    Liegt das übergebene Datum auf einem Sonntag, wird das Datum des folgenden Montags zurückgegeben, sonst das gleiche Datum. Feiertage werden nicht berücksichtigt.

  • Prüfung, ob ein Datum in einem Zeitraum liegt.

    Die Klasse Zeitraum aus isy-datetime stellt dazu die Methode isInZeitraum() bereit.

  • Prüfung, ob sich zwei Zeiträume überlappen.

    Die Klasse Zeitraum aus isy-datetime stellt dazu die Methode ueberschneidetSichMit() bereit.

4.3. Testunterstützung

Für die Umsetzung der Testunterstützung stellt isy-datetime die Klasse TestClock bereit. Sie erweitert die Klasse java.time.Clock und wird an Stelle dieser verwendet.

Damit diese Klasse bei Tests verwendet kann, dürfen in der zu testenden Anwendung keine Aufrufe der now()-Methoden Java 8 Date and Time API Klassen benutzt werden. Stattdessen müssen die Methoden *now() aus der Klasse DateTimeUtil zum Erzeugen von Datums- und Zeitobjekten verwendet werden. Diese rufen intern die Methode now(Clock) der Java 8 Klassen auf. Als Clock wird standardmäßig Clock.systemDefaultZone() verwendet.

Zum Test wird als Clock eine TestClock gesetzt. Der Ablauf eines Tests mit der TestClock sieht dann schematisch folgendermaßen aus:

Listing 4. Verwendung der Klasse TestClock
@Test
public void someTest() {
    TestClock testClock = TestClock.at(LocalDateTime.of(2017, 9, 1, 15, 0));
    DateTimeUtil.setClock(testClock);

    // Test der Anwendung zu bestimmer Zeit

    // Zeit um eine Stunde nach vorne Stellen
    DateTimeUtil.getClock().advanceBy(Duration.ofHours(1));
    // Erneuter Test der Anwendung eine Stunde später...