Nutzungsvorgaben Datum & Zeit
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 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 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 IT-Systemen, 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:
| 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.
| 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:
| 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.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:
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 keinLocalDatesein soll, sondern einer Objekt des gleichen Typs mit dem Zeitwert auf 0 gesetzt, ist die MethodetruncatedTo(ChronoUnit.DAYS)zu verwenden. -
Berechnung des Abstands (âDauerâ) zwischen zwei Datums- oder Zeitwerten
Es ist die Methode
until()der Datum- und Zeitklassen oder die Methodebetween()des InterfacesTemporalUnitzu verwenden. Beispiele:
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 umfangreicheplus*()undminus*()-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?â).
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
DateTimeUtilstellt hierfĂŒr die MethodegetWerkTag(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
Zeitraumaus isy-datetime stellt dazu die MethodeisInZeitraum()bereit. -
PrĂŒfung, ob sich zwei ZeitrĂ€ume ĂŒberlappen.
Die Klasse
Zeitraumaus isy-datetime stellt dazu die MethodeueberschneidetSichMit()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:
@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...