Nutzungsvorgaben Datum & Zeit

Java Bibliothek / IT-System

Name Art Version

isy-datetime

Bibliothek

5.x

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.

Die Nutzungsvorgaben beschreiben alle Aspekte, die bei der Entwicklung eines IT-Systems 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 dieser Seite sind Vorgaben zur Nutzung der Java 8 Date and Time API des JSR 310 und die Verwendung der zuvor genannten Bibliothek. Die Nutzungsvorgaben richten sich daher vorrangig an Entwickler und Designer von IT-Systemen, die gemäß den Vorgaben der IsyFact umgesetzt werden.

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

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

1. Dauern und Zeitzonen

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

1.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.

1.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.

1.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 ist dies 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.

2. Verwendung der Bibliothek isy-datetime

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

2.1. Inhalt von isy-datetime

2.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 zur ISO 8601 konformen Darstellung wird direkt durch die Klassen der Java 8 Date and Time API unterstützt.

2.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

2.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"

2.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.

2.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.

2.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.

2.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:

2.2.1. Chronologische Sortierung und Vergleiche

Die Klassen LocalDate, LocalTime, LocalDateTime, OffsetTime, OffsetDateTime, ZonedDateTime und Duration implementieren das Interface Comparable, daher ist ein Sortieren mit Bordmitteln möglich (zum Beispiel 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.

Listing 1. Vergleich von Zeiten mit equals() und isEqual()
public void compareDateAndTime() {
    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.isEqual(todayLocal); // true
    todayJapanese.equals(todayLocal); // false
}

2.2.2. Ermittlung des "Tagesdatums"

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

2.2.3. 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.

Listing 2. Berechnung des Abstands zwischen zwei Datums- oder Zeitwerten
public void checkIntervals() {
    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
}

2.2.4. Addition und Subtraktion von Datums- und Zeitwerten

Die Datums- und Zeitklassen von Java 8 stellen umfangreiche plus*() und minus*()-Methoden zur Addition und Subtraktion von Datums- und Zeitwerten bereit.

2.2.5. Prüfung auf direkt aufeinanderfolgende Tage

Das folgende Listing beantwortet die Frage: "Liegt der 28.2.2016 direkt vor dem 1.3.2016?"

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

    date1.plusDays(1).isEqual(date2); // false (leap year)
}

2.2.6. Berechnung des nächsten Werktags

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.

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

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

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

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

2.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 werden 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 bestimmter Zeit

    // Zeit um eine Stunde nach vorne stellen
    DateTimeUtil.getClock().advanceBy(Duration.ofHours(1));

    // Erneuter Test der Anwendung eine Stunde später
}