Java Duration
Zeitbasierte Mengen (Stunden, Minuten, Sekunden) in Java mit Duration darstellen.
Duration repräsentiert eine Zeitspanne, gemessen in Sekunden und Nanosekunden. "Zwei Stunden." "Fünfhundert Millisekunden." "Fünfundvierzig Minuten." Intern sind es zwei Zahlen — ein long seconds und ein int nanos — und jede Operation darauf ist ganzzahlige Arithmetik. Sie kennt weder Kalender noch Monate noch Sommerzeit; wenn Sie ihr 24 Stunden mitteilen, bedeutet das exakt 86.400 Sekunden, unabhängig davon, ob diese 24 Stunden einen Sommerzeitübergang umfassen.
Dies ist der richtige Typ für: Zeitmessungen, Timeouts, Wiederholungsversuche mit exponentiellem Backoff, "maximal X Sekunden warten", alles, bei dem die Antwort ein Echtzeit-Intervall ist. Der Begleittyp für Kalender-Längen ("ein Monat", "zwei Jahre") ist Period, der im nächsten Kapitel behandelt wird.
Erstellen
Drei Gruppen von Fabrikmethoden:
Duration.ofNanos(500_000_000);
Duration.ofMillis(500);
Duration.ofSeconds(45);
Duration.ofSeconds(60, 500_000_000); // with nanos
Duration.ofMinutes(2);
Duration.ofHours(1);
Duration.ofDays(7); // exactly 7 * 24 hours
Duration.between(start, end); // from two Temporals
Duration.parse("PT1H30M"); // ISO-8601: PT[hours]H[minutes]M[seconds]SDuration.ofDays(n) ist die Fabrikmethode, die die meisten Menschen verwenden und die meisten falsch verstehen. Sie erzeugt exakt n * 24 * 3600 Sekunden. Bei einem Sommerzeitübergang ist das nicht dasselbe wie "am nächsten Tag zur gleichen Uhrzeit". Für kalenderartige "einen Tag später" sind LocalDate.plusDays(1) oder Period.ofDays(1) die richtigen Werkzeuge.
Das String-Format ist ISO-8601-Dauer: PT1H30M45S entspricht einer Stunde, dreißig Minuten und fünfundvierzig Sekunden. Das führende PT ist obligatorisch (P = Period, T = Time). Für Sekundenbruchteile lautet die Form PT0.5S oder PT0.000000001S. Duration deckt alles unterhalb des Tages ab; Perioden (Jahr, Monat, Tag) gehören zu Period.
Arithmetik
d.plus(Duration.ofSeconds(15));
d.plusSeconds(60);
d.plusMinutes(5);
d.plusHours(2);
d.minus(Duration.ofMillis(100));
d.multipliedBy(3);
d.dividedBy(2);
d.negated(); // -d
d.abs();Jede Methode gibt eine neue Duration zurück (das Unveränderlichkeitsprinzip aus LocalDate und darüber hinaus).
Die Konvertierungsmethoden:
d.toNanos(); // long
d.toMillis(); // long; throws if out of long range
d.toSeconds(); // since Java 9
d.toMinutes();
d.toHours();
d.toDays(); // assumes 24h days
d.getSeconds(); // raw seconds component
d.getNano(); // raw nanos component (0-999_999_999)Die toX()-Methoden liefern ein einzelnes long der Gesamtsumme in der jeweiligen Einheit, abgeschnitten. Die getX()-Methoden liefern die rohe Aufschlüsselung. Das sind unterschiedliche Dinge; ihre Verwechslung ist der häufigste Duration-Fehler.
Duration d = Duration.ofSeconds(125);
d.toMinutes(); // 2 (125 / 60)
d.getSeconds(); // 125 (raw)Für die Formatierung "X Minuten Y Sekunden" verwendet man beide:
long mins = d.toMinutes();
long secs = d.minusMinutes(mins).getSeconds();
System.out.printf("%d:%02d%n", mins, secs); // 2:05Oder seit Java 9 die dedizierten Aufschlüsselungs-Hilfsmethoden:
d.toHoursPart(); // hours within the duration (0-23-ish on positive durations)
d.toMinutesPart(); // minutes within the duration (0-59)
d.toSecondsPart(); // seconds within the duration (0-59)
d.toMillisPart(); // millis within the secondDiese sind das Richtige, wenn man "1h 23m 45s darstellen" möchte, ohne die Modulo-Arithmetik selbst durchführen zu müssen.
Vergleichen
d1.isZero(); // d == 0
d1.isNegative(); // d < 0
d1.compareTo(d2);
d1.equals(d2);Duration ist Comparable<Duration>. Die Ordnung ist vorzeichenbehaftet — eine negative Duration ist kleiner als null, die kleiner ist als eine positive Duration.
Abstand zwischen zwei Temporals
Die Fabrikmethode Duration.between(start, end) ist die, die man am häufigsten verwenden wird:
Duration d = Duration.between(start, end); // works on Instant, LocalDateTime, ZonedDateTime, LocalTimeSie akzeptiert beliebige Paare von Temporal-Werten, die zeitbasierte Einheiten unterstützen. LocalDate (nur Datum) tut dies nicht — Duration.between(date1, date2) wirft eine DateTimeException, da ein Datum keine Uhrzeitkomponente hat. Für den Kalenderabstand verwendet man Period.between(date1, date2) oder ChronoUnit.DAYS.between(date1, date2).
Die Vorzeichenkonvention: positiv, wenn end nach start liegt, andernfalls negativ.
Zu anderen Temporals hinzufügen
Duration ist ein TemporalAmount, daher akzeptiert jedes Temporal es über plus/minus:
Instant later = Instant.now().plus(Duration.ofMinutes(30));
LocalDateTime then = LocalDateTime.now().plus(Duration.ofHours(8));Das Hinzufügen einer Duration zu einem LocalDate wirft eine Ausnahme — aus demselben Grund wie oben: keine Uhrzeitkomponente. Der Compiler erkennt dies nicht; der Aufruf schlägt zur Laufzeit fehl. Wenn man sich dabei ertappt, dies zu wollen, meint man fast sicher Period.
Ein praktisches Beispiel: Zeitmessung, Formatierung, Backoff
Das folgende Programm misst die verstrichene Zeit eines kleinen Arbeitsstücks, gibt sie in menschenlesbarer Form mithilfe der toXxxPart-Hilfsmethoden aus, berechnet einen exponentiellen Backoff-Zeitplan und demonstriert die Grenze zwischen Duration und Period, indem es sowohl Duration.between auf Instants als auch Period.between auf LocalDates für die gleiche konzeptionelle Zeitspanne zeigt.
Was man aus dem Ablauf mitnehmen sollte:
- Der
Thread.sleep(125)-Block wurde alsPT0.125-something-Sgemessen — der tatsächlichesleep-Überschuss ist reale Maschinenvariabilität.Duration.between(t0, t1)war der richtige Aufruf: zweiInstants, keine Zeitzone erforderlich, genaue Antwort mit der Auflösung der Systemuhr. - Die
toXxxPart()-Hilfsmethoden lieferten saubere Integer für "den Stundenanteil", "den Minutenanteil", "den Sekundenanteil", "den Millisekunden-Anteil". Ohne sie müsste mantotal / 3600und(total % 3600) / 60modulare Arithmetik von Hand durchführen. Man sollte sie immer dann verwenden, wenn eine Duration für einen Menschen formatiert werden soll. - Die exponentielle Backoff-Schleife skalierte die Duration mit
multipliedBy(2)und gab jedes Ergebnis aus. Die ISO-8601-Zeichenkettenform (PT2S,PT4S,PT8S, ...) ist das, wasDuration.toStringausgibt; sie ist knapp und eindeutig, ideal für Protokolle. - Der Block "Jan 1 → Apr 1 in drei Formen" zeigte die Grenze zwischen
DurationundPeriod.Period.between(start, end)gabP3Mzurück — drei Kalendermonate.Duration.betweenauf den äquivalenten UTC-Zeitpunkten gab eine Echtzeit-Zählung zurück (90 Tage). Beide sind korrekt; sie beantworten verschiedene Fragen. Man wählt diejenige, deren Bedeutung zur Absicht des Codes passt. - Der letzte Block versuchte
Duration.between(LocalDate, LocalDate)und erhielt eineDateTimeException. Das Typsystem weiß, dass ein Datum keine Uhrzeitkomponente hat, und lehnt daher die Berechnung einer uhrzeitbasierten Duration ab. Die Lösung besteht darin, entweder (1) Uhrzeiten anzuhängen (startDate.atStartOfDay(zone)), oder (2)Period.betweenzu verwenden, wenn eine Kalenderanzahl das eigentlich Gemeinte ist. Die Ausnahme ist das richtige Design: stille Antworten auf diese Frage wären irreführend.
Was kommt als nächstes
Duration ist die Länge in Sekunden und Nanosekunden. Das nächste Kapitel, Java Period, ist die Länge in Jahren, Monaten und Tagen — das kalenderartige Gegenstück. Die beiden vermischen sich nie: Duration kennt keine Monate, Period kennt keine Stunden, und jeder Code, der mit beiden zu tun hat, greift je nachdem, ob die Antwort den Kalender oder die Wanduhr verfolgen soll, auf das eine oder das andere zurück.