W3docs

Java Instant

Einen Zeitpunkt auf der globalen Zeitlinie in UTC mit Instant darstellen – ideal für Zeitstempel und maschinelle Zeit.

Instant ist ein einzelner Moment auf der globalen Zeitlinie, gespeichert als Nanosekunden seit dem Unix-Epoch (1970-01-01T00:00:00Z). Er hat keine Zeitzone, keinen Kalender, kein Konzept von „welcher Tag ist heute" — nur eine Zahl. Diese Zahl ist per Konstruktion in UTC, sodass zwei Instant-Werte von verschiedenen Maschinen in verschiedenen Zeitzonen direkt verglichen werden können: der mit der kleineren Zahl liegt früher.

Dies ist der Typ für maschinelle Zeitstempel. Logzeilen. Nachrichtenzeitstempel. „Wann hat der Server die Anfrage empfangen." Audit-Trails. Alles, was global sortierbar sein muss und niemals zweideutig darüber sein darf, welchen Tag es darstellt — denn es gibt gar keinen „Tag", nur Sekunden.

Erstellen

Instant now    = Instant.now();                              // current moment, from System clock
Instant epoch  = Instant.EPOCH;                              // 1970-01-01T00:00:00Z
Instant max    = Instant.MAX;                                 // year +1000000000
Instant min    = Instant.MIN;                                 // year -1000000000

Instant fromS   = Instant.ofEpochSecond(1_700_000_000L);
Instant fromMs  = Instant.ofEpochMilli(1_700_000_000_000L);
Instant fromIso = Instant.parse("2025-11-04T19:30:00Z");     // ISO-8601, trailing Z is mandatory

Die Zeichenkettendarstellung endet mit einem buchstäblichen Z (für „Zulu time", militärisch für UTC). Instant.parse("2025-11-04T19:30:00") (ohne Z) ist ein Parse-Fehler — der Typ weigert sich zu raten, welche Zeitzone gemeint ist.

Zwei Fabrikmethoden, die man häufig verwendet:

Instant.ofEpochSecond(epochSec);                              // long seconds, no nanos
Instant.ofEpochSecond(epochSec, nanos);                       // with sub-second resolution

Die meisten externen Zeitstempelformate (Unix time(2), syslog, JSON-created_at-Integer) sind Sekunden oder Millisekunden seit dem Epoch. Die Fabrikmethoden ofEpochSecond / ofEpochMilli sind die Standardbrücke.

Auflösung

Instant hat Nanosekunden-Präzision (1 Sekunde = 1.000.000.000 ns). Auf den meisten Systemen hat die zugrunde liegende Uhr eine geringere Auflösung — typischerweise Millisekunden, auf modernem Linux Mikrosekunden. Instant.now() gibt einen Wert mit der verfügbaren Auflösung zurück; die nicht genutzten Nanosekunden sind null.

Zugriffsmethoden:

long seconds = inst.getEpochSecond();                         // long; can go past 2038
int nanos    = inst.getNano();                                // 0-999_999_999
long milli   = inst.toEpochMilli();                           // throws if out of long range

toEpochMilli ist die verlustbehaftete Konvertierung: Nanosekunden werden auf Millisekunden gekürzt. Für Logzeilen und JSON-Zeitstempel ist das meist akzeptabel; für hochfrequente Ereignisaufzeichnungen sollte man getEpochSecond + getNano separat verwenden.

Kein Kalender

Instant.getDayOfMonth() existiert nicht. Ebenso wenig getYear, getHour oder andere Kalender-Accessoren. Der Typ weiß es schlicht nicht — Kalenderinformationen erfordern eine Zeitzone, und Instant hat keine. Wenn man wissen möchte „Welche Stunde war es in New York, als das passierte", muss man zuerst eine Zeitzone anhängen:

ZonedDateTime zdt = inst.atZone(ZoneId.of("America/New_York"));
int hour = zdt.getHour();
LocalDate date = zdt.toLocalDate();

atZone(zone) ist die Brücke in die andere Richtung von ZonedDateTime.toInstant(). Zusammen ergeben sie den vollständigen Roundtrip: Moment ↔ zeitzonenbeschrifteter Wert. Siehe Java ZonedDateTime für die Kalenderseite dieser Paarung.

Arithmetik

Gleiche fließende Schreibweise:

inst.plusSeconds(60);
inst.plusMillis(500);
inst.plusNanos(1_000_000);
inst.plus(Duration.ofMinutes(15));                            // any Duration

inst.minus(Duration.ofDays(1));                                // exactly 24h * 3600s

Kein plusDays auf Instant (im Kalendersinne). Es gibt plus(amount, ChronoUnit), und ChronoUnit.DAYS funktioniert, weil das JDK einen Day als genau 24 Stunden Sekunden für Instant definiert. Das entspricht nicht einem Kalendertag bei Sommerzeit, weshalb Instant auch nicht vorgibt, einer zu sein.

inst.plus(1, ChronoUnit.DAYS);                                // exactly 86_400 seconds
inst.plus(7, ChronoUnit.DAYS);                                // exactly 604_800 seconds

Für kalenderförmige Operationen („einen Monat später in der Zeitzone des Nutzers") geht man über ZonedDateTime:

Instant later = inst.atZone(zone).plusMonths(1).toInstant();

Vergleichen

inst1.isBefore(inst2);
inst1.isAfter(inst2);
inst1.equals(inst2);
inst1.compareTo(inst2);

Instant implementiert Comparable<Instant> mit natürlicher Reihenfolge nach Epoch-Sekunde und dann Nanosekunden. equals ist unkompliziert: gleiche Sekunde und gleiche Nanosekunden.

Abstände

Duration d = Duration.between(start, end);                     // a Duration
long millis = ChronoUnit.MILLIS.between(start, end);
long days = ChronoUnit.DAYS.between(start, end);               // 24h-equivalent days

Für maschinelle Zeitstempel sind all das exakte Berechnungen — es gibt keine Kalenderuneindeutigkeit. ChronoUnit.MONTHS.between(start, end) auf Instant-Werten wirft eine Ausnahme, weil Monate keine konstante Länge in Sekunden haben: ohne Zeitzone hat der Rechner keine Möglichkeit zu wissen, welcher Monat diese Sekunden enthält. Das ist das richtige Fehlverhalten.

java.util.Date-Brücke

Alter Code verwendet java.util.Date. Die Konvertierungen sind direkt:

Date legacy = Date.from(inst);                                 // Instant -> Date
Instant back = legacy.toInstant();                              // Date -> Instant

Date ist intern ein Wrapper um ein Epoch-Millisekunden-long, sodass der Roundtrip verlustlos abzüglich Nanosekunden ist (Date hat Millisekunden-Präzision, Instant Nanosekunden-Präzision). Das Kapitel Legacy Date behandelt die Migration im Detail.

Warum alles Interne Instant sein sollte

Die Empfehlung, die sich aus zehn Jahren java.time in der Produktion ergeben hat:

  • Intern Instant verwenden. Speicherung, Vergleich, Logging, Nachrichtenzeitstempel, überall wo der Wert zwischen Maschinen fließt.
  • An der Grenze — bei der Anzeige für Nutzer, bei der Entgegennahme von Nutzereingaben — in ZonedDateTime oder LocalDateTime mit der richtigen Zeitzone für den Kontext umwandeln.

Das trennt „was wirklich passiert ist" von „wie es beschriftet ist". Ein Fehler an der Grenze (falsche Zeitzone) lässt die internen Werte korrekt; ein Fehler im Typsystem, der LocalDateTime intern fließen lässt, führt zu Zeitstempeln, die stillschweigend in verschiedenen Zeitzonen liegen.

Ein durchgearbeitetes Beispiel: ein kleines Ereignisprotokoll

Das folgende Programm zeichnet eine Folge von Ereignissen als Instant-Werte auf, berechnet Abstände zwischen Ereignissen, demonstriert die Kalendergrenze durch das Anhängen einer Zeitzone zur Anzeige, zeigt die Legacy-Date-Brücke und veranschaulicht schließlich die „kein Kalender"-Regel, indem gezeigt wird, dass ChronoUnit.MONTHS.between auf zwei Instant-Werten eine Ausnahme wirft.

java— editable, runs on the server

Was aus dem Lauf mitzunehmen ist:

  • Instant.parse("2025-11-04T19:30:00Z") wurde nur geparst, weil das Z am Ende vorhanden war. Lässt man das Z weg, schlägt das Parsen fehl — der Typ besteht darauf zu wissen, dass die Zeichenkette UTC ist. Andere Zeitzonen müssen über ZonedDateTime.parse (oder per atZone) angegeben werden.
  • Die Ereignisfolge verwendete Duration.between(...). Jedes Ergebnis war eine saubere ganzzahlige Millisekundenanzahl — keine Zeitzonenverwirrung, keine Sommerzeit, keine Kalenderarithmetik. Daher gehören serverseitige Zeitangaben in Instant: die Arithmetik ist unter der Haube nur eine Subtraktion von long-Werten.
  • Der Block „gleicher Moment, zwei Zonenbezeichnungen" druckte ZonedDateTime-Werte, die verschieden aussahen, aber derselbe Instant waren. atZone(...) ist für Instant rein eine Anzeigeoperration. Wenn man Kalenderrechnung durchführen möchte (nächster Monat, Wochenende), tut man das auf dem ZonedDateTime, ruft dann .toInstant() auf, um zurückzukehren.
  • Date.from(inst) und legacy.toInstant() waren verlustlos abzüglich Nanosekunden. Date trägt nur Millisekunden, sodass ein Roundtrip über Date die Sub-ms-Präzision kürzt. Für die meisten Protokollierungen ist das in Ordnung; für hochpräzise Ereignisaufzeichnungen bleibt man durchgängig bei Instant und macht keinen Roundtrip über Date.
  • ChronoUnit.MONTHS.between(a, b) warf UnsupportedTemporalTypeException. Das ist das richtige Fehlverhalten: Monate sind keine konstanten Sekundenmengen, und das JDK weigert sich, eine Antwort zu erfinden. Der Wechsel zu ZonedDateTime lieferte die fehlende Zeitzone, und derselbe Aufruf funktionierte. Das Muster ist allgemein: kalenderförmige Operationen benötigen eine Zeitzone, und das Typsystem zwingt einen, diese explizit anzugeben.

Was als Nächstes kommt

Instant ist der Moment. Die nächsten zwei Kapitel behandeln die Zeitlängen zwischen Momenten: Java Duration für „X Sekunden, Y Nanosekunden"-Messungen und Java Period für „X Jahre, Y Monate, Z Tage" als Kalenderlängen. Zusammen bilden sie die Grundlage dafür, „eine Stunde später" vs. „einen Monat später" ohne Verlust auszudrücken.

Übungsaufgaben

Übung
Eine Logzeile enthält `created_at: 1700000000` (eine Unix-Epochensekunde). Du brauchst einen `java.time`-Wert, den du mit `Instant.now()` vergleichen und an eine `Duration.between(...)`-Berechnung übergeben kannst. Welche Konvertierung ist richtig?
Eine Logzeile enthält `created_at: 1700000000` (eine Unix-Epochensekunde). Du brauchst einen `java.time`-Wert, den du mit `Instant.now()` vergleichen und an eine `Duration.between(...)`-Berechnung übergeben kannst. Welche Konvertierung ist richtig?
Was this page helpful?