Instant — niemutowalna klasa z pakietu java.time, reprezentująca punkt na osi czasu w UTC z dokładnością do nanosekundy. W przeciwieństwie do LocalDateTime, Instant nie zawiera daty i czasu w formacie czytelnym dla człowieka — to maszynowa reprezentacja momentu. Według specyfikacji Oracle Java 17 (2024), Instant został zaprojektowany do maszynowej wymiany znaczników czasu i jest odpowiednikiem System.currentTimeMillis(), ale z dokładnością nanosekundową.
Najważniejsze
Instant — to klasa modelująca pojedynczy punkt na osi czasu. Jej wewnętrzna reprezentacja składa się z dwóch pól: long seconds (liczba sekund od 1970-01-01T00:00:00Z) i int nanos (nanosekundy w bieżącej sekundzie, od 0 do 999999999).
Zakres wartości Instant — od -31557014167219200 do 31556889864403199 sekund od epoki, co obejmuje około 292 miliony lat w obie strony. To wystarcza do wszelkich praktycznych zadań, w tym obliczeń astronomicznych.
Według Baeldung (2024), Instant jest mostem między typami czytelnymi dla człowieka (LocalDateTime, ZonedDateTime) a formatami maszynowymi (timestamp w milisekundach). Instant jest używany do logowania, buforowania, synchronizacji i wszystkich zadań, gdzie liczy się absolutny moment czasu.
Klasa implementuje interfejsy Comparable (do porównywania momentów) i Temporal (do używania w ogólnym API java.time). Instant jest niemutowalny — wszystkie metody zwracają nowy obiekt.
Przed Javą 8 do pracy z momentami czasu używano java.util.Date i System.currentTimeMillis(). Oba podejścia mają wady. Date jest mutowalny, nie jest bezpieczny wątkowo, przechowuje czas w milisekundach od epoki, ale nazwy metod są przestarzałe (getYear() zwraca 116 dla 2016).
Long (prosty timestamp) jest szybki i kompaktowy, ale nie ma wbudowanej obsługi nanosekund, nie wyświetla się w czytelnej formie i wymaga ręcznego parsowania przy debugowaniu. Podejście Long nie rozróżnia również typu danych — programista może przekazać nieprawidłową wartość.
Instant rozwiązuje wszystkie te problemy. Jest niemutowalny, zawiera jawną informację o dokładności (sekundy + nanosekundy), serializuje się do formatu ISO-8601 „2026-07-21T15:00:00Z” i ma bogate API do konwersji. Według SonarSource (2024), Instant jest zalecanym zamiennikiem Date we wszystkich nowych projektach.
Bieżący moment uzyskuje się przez Instant.now(). W przeciwieństwie do LocalDateTime.now(), Instant.now() zawsze zwraca czas w UTC, ignorując strefę czasową urządzenia. To czyni go idealnym do serwerowych znaczników czasu.
Z istniejących wartości: Instant.ofEpochSecond(long epochSecond) — z sekund od epoki, Instant.ofEpochMilli(long epochMilli) — z milisekund, Instant.parse(CharSequence) — z ciągu ISO-8601 („2026-07-21T15:00:00Z”).
Do odczytu używa się getEpochSecond() — liczba sekund od epoki, toEpochMilli() — liczba milisekund, getNano() — nanosekundy. Metoda toString() zwraca ciąg w formacie ISO-8601.
val now = Instant.now()
val fromSeconds = Instant.ofEpochSecond(1784700000)
val fromMillis = Instant.ofEpochMilli(1784700000000)
val parsed = Instant.parse("2026-07-21T15:00:00Z")
val epochSecond = now.getEpochSecond()
val epochMilli = now.toEpochMilli()
val nanos = now.getNano()
Instant konwertuje się na ZonedDateTime przez atZone(ZoneId). Na przykład Instant.now().atZone(ZoneId.of("Europe/Moscow")) zwróci ZonedDateTime dla Moskwy. Bez strefy konwersja jest niemożliwa — Instant nie zawiera informacji kalendarzowej.
Na LocalDateTime Instant konwertuje się przez atZone(ZoneId).toLocalDateTime(). Ten sposób jest jawny i nie traci informacji. Odwrotna konwersja — LocalDateTime.atZone(ZoneId).toInstant().
Dla zgodności z java.util.Date: Date.from(instant) i date.toInstant(). To dwukierunkowa konwersja zachowująca dokładność do milisekund (Date nie obsługuje nanosekund). Do pracy z java.sql.Timestamp używa się Timestamp.from(instant) z obsługą nanosekund.
val instant = Instant.now()
val zoned = instant.atZone(ZoneId.of("Europe/Moscow"))
val localDateTime = instant
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
val oldDate = Date.from(instant)
val backToInstant = oldDate.toInstant()
Kluczowa cecha Instant — jest całkowicie niezależny od stref czasowych. Instant.now() zwraca ten sam wynik na każdym urządzeniu w dowolnym miejscu na świecie. Osiąga się to poprzez ustalenie czasu w UTC.
Strefa czasowa jest potrzebna tylko do wyświetlenia Instant człowiekowi. Służy do tego atZone(ZoneId). ZoneId.systemDefault() zwraca strefę czasową urządzenia ustawioną w systemie operacyjnym. ZoneOffset.UTC — stała dla UTC.
W systemach rozproszonych zaleca się przechowywanie i przesyłanie wszystkich znaczników czasu w Instant (lub OffsetDateTime z ZoneOffset.UTC). Konwersja na czas lokalny wykonywana jest tylko na kliencie przed wyświetleniem użytkownikowi. Zapobiega to pomyłkom związanym ze strefami czasowymi.
W rozproszonych aplikacjach Android synchronizacja czasu jest krytyczna dla poprawnego działania buforowania, powiadomień i wspólnego edytowania. Instant — naturalny wybór do tego zadania dzięki powiązaniu z UTC.
Przy porównywaniu znaczników czasu z różnych urządzeń należy uwzględnić, że zegary systemowe mogą się różnić. Zaleca się używanie czasu serwera jako wzorca. Serwer zwraca Instant w UTC, klient porównuje z lokalnym Instant tylko do obliczeń względnych.
Do obliczenia różnicy między dwoma momentami używa się Duration.between(Instant start, Instant end). Ta metoda zwraca Duration — okres, który można konwertować na godziny, minuty, sekundy. Metody isAfter() i isBefore() pozwalają porównywać momenty.
fun isCacheExpired(
cachedAt: Instant,
ttlMinutes: Long
): Boolean {
val elapsed = Duration.between(cachedAt, Instant.now())
return elapsed.toMinutes() >= ttlMinutes
}
Pierwszy przykład — logowanie zdarzeń ze znacznikiem czasu. Instant jest zapisywany w bazie danych Room i przesyłany na serwer. Znacznik czasu jest logowany w UTC dla jednoznacznej interpretacji.
data class EventLog(
val id: Long = 0,
val eventName: String,
val timestamp: Instant
)
class Converters {
@TypeConverter
fun fromInstant(value: Instant?): Long? {
return value?.toEpochMilli()
}
@TypeConverter
fun toInstant(value: Long?): Instant? {
return value?.let { Instant.ofEpochMilli(it) }
}
}
Drugi przykład — określanie czasu, jaki upłynął od zdarzenia. Używamy Duration.between do wyświetlania „5 minut temu”, „2 godziny temu” — formatu powszechnego w komunikatorach i mediach społecznościowych.
fun timeAgo(instant: Instant): String {
val duration = Duration.between(instant, Instant.now())
return when {
duration.toMinutes() < 1 -> "just now"
duration.toHours() < 1 -> "${duration.toMinutes()} min ago"
duration.toDays() < 1 -> "${duration.toHours()} h ago"
else -> "${duration.toDays()} d ago"
}
}
Trzeci przykład — synchronizacja danych między serwerem a klientem. Używamy Instant do śledzenia czasu ostatniej aktualizacji.
class SyncManager {
private var lastSyncAt: Instant? = null
fun sync() {
val syncStart = Instant.now()
// żądanie serwera z lastSyncAt
lastSyncAt = syncStart
}
fun shouldSync(intervalMinutes: Long): Boolean {
val last = lastSyncAt ?: return true
return Duration.between(last, Instant.now())
.toMinutes() >= intervalMinutes
}
}
Pierwszy błąd — używanie Instant.now().toString() do wyświetlania użytkownikowi. Instant wyświetla się w formacie UTC „2026-07-21T15:00:00Z”, który jest nieczytelny dla człowieka. Zawsze konwertuj Instant przez atZone() do lokalnej strefy czasowej przed wyświetleniem.
Drugi błąd — utrata nanosekund przy konwersji na java.util.Date. Date obsługuje tylko milisekundy. Jeśli Instant ma nanosekundy, zostaną one utracone przy Date.from(instant). Używaj Instant.truncatedTo(ChronoUnit.MILLIS) do jawnego określenia dokładności.
Trzeci błąd — pomylenie toEpochMilli() z getEpochSecond(). toEpochMilli() zwraca liczbę milisekund od epoki (long), a getEpochSecond() — liczbę sekund (long). Pomylenie tych metod może dać błąd 1000 razy.
Czwarty błąd — założenie, że Instant.now() na wszystkich urządzeniach jest zsynchronizowany. Zegary systemowe mogą różnić się o minuty, a nawet godziny. Dla operacji krytycznych czasowo (uwierzytelnianie, płatności) używaj serwerowego Instant jako źródła prawdy.
Często zadawane pytania
System.currentTimeMillis() zwraca long — liczbę milisekund od epoki bez powiązania ze strefą czasową. Instant zapewnia tę samą funkcjonalność, ale z dokładnością nanosekundową i bogatym API do konwersji, porównań i zgodności z java.time.
Room nie obsługuje Instant bezpośrednio. Użyj TypeConverter, który konwertuje Instant na Long (toEpochMilli) i z powrotem (Instant.ofEpochMilli). Dla dokładności nanosekundowej zapisuj dwa pola: epoka-sekundy i nanosekundy.
Tak, Instant jest niemutowalny i poprawnie implementuje equals() i hashCode(). Dwa Instant o tej samej wartości będą równe. To czyni go niezawodnym kluczem dla HashMap i innych kolekcji, w przeciwieństwie do mutowalnego java.util.Date.
Użyj Duration.between(start, end) aby uzyskać Duration lub ChronoUnit.SECONDS.between(start, end) dla różnicy w sekundach (long). Duration udostępnia metody toMinutes(), toHours(), toDays() i toNanos().
Instant został zaprojektowany jako absolutny punkt na osi czasu. Bez określenia strefy czasowej lub UTC parsowanie jest niemożliwe, ponieważ Instant nie zawiera informacji kalendarzowej. Sufiks „Z” oznacza zerowe przesunięcie (UTC) i jest obowiązkowy dla formatu ISO-8601.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również