Nutzungsvorgaben Datum & Zeit
Java Bibliothek / IT-System
| Name | Art | Version |
|---|---|---|
|
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:
| 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.
| 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:
| 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.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.
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.
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?"
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.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:
@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
}