Instant — una classe immutabile del pacchetto java.time, che rappresenta un punto sulla linea temporale in UTC con precisione al nanosecondo. A differenza di LocalDateTime, Instant non contiene data e ora in un formato leggibile dall'uomo — è una rappresentazione macchina di un istante. Secondo la specifica Oracle Java 17 (2024), Instant è progettato per lo scambio macchina di timestamp ed è un analogo di System.currentTimeMillis(), ma con precisione al nanosecondo.
Punti Chiave
Instant è una classe che modella un singolo punto sulla linea temporale. La sua rappresentazione interna consiste in due campi: long seconds (il numero di secondi dal 1970-01-01T00:00:00Z) e int nanos (nanosecondi all'interno del secondo corrente, da 0 a 999999999).
L'intervallo di valori di Instant va da -31557014167219200 a 31556889864403199 secondi dall'epoca, coprendo circa 292 milioni di anni in entrambe le direzioni. Questo è sufficiente per qualsiasi compito pratico, inclusi i calcoli astronomici.
Secondo Baeldung (2024), Instant è un ponte tra i tipi leggibili dall'uomo (LocalDateTime, ZonedDateTime) e i formati macchina (timestamp in millisecondi). Instant viene utilizzato per logging, caching, sincronizzazione e tutti i compiti in cui un momento assoluto nel tempo è importante.
La classe implementa le interfacce Comparable (per confrontare istanti) e Temporal (per l'uso nell'API comune java.time). Instant è immutabile — tutti i metodi restituiscono un nuovo oggetto.
Prima di Java 8, per lavorare con istanti temporali si usavano java.util.Date e System.currentTimeMillis(). Entrambi gli approcci hanno svantaggi. Date è mutabile, non thread-safe, memorizza il tempo in millisecondi dall'epoca, ma i nomi dei suoi metodi sono obsoleti (getYear() restituisce 116 per il 2016).
Long (un timestamp semplice) è veloce e compatto, ma non ha supporto integrato per i nanosecondi, non viene visualizzato in un formato leggibile e richiede analisi manuale durante il debug. L'approccio Long inoltre non distingue i tipi di dati — uno sviluppatore potrebbe passare un valore errato.
Instant risolve tutti questi problemi. È immutabile, contiene informazioni esplicite sulla precisione (secondi + nanosecondi), si serializza nel formato ISO-8601 “2026-07-21T15:00:00Z” e ha una ricca API per le conversioni. Secondo SonarSource (2024), Instant è la sostituzione raccomandata per Date in tutti i nuovi progetti.
Il momento corrente si ottiene tramite Instant.now(). A differenza di LocalDateTime.now(), Instant.now() restituisce sempre l'ora in UTC, ignorando il fuso orario del dispositivo. Questo lo rende ideale per i timestamp del server.
Da valori esistenti: Instant.ofEpochSecond(long epochSecond) — da secondi dall'epoca, Instant.ofEpochMilli(long epochMilli) — da millisecondi, Instant.parse(CharSequence) — da una stringa ISO-8601 (“2026-07-21T15:00:00Z”).
Per la lettura: getEpochSecond() — il numero di secondi dall'epoca, toEpochMilli() — il numero di millisecondi, getNano() — nanosecondi. Il metodo toString() restituisce una stringa in formato 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 viene convertito in ZonedDateTime tramite atZone(ZoneId). Ad esempio, Instant.now().atZone(ZoneId.of(“Europe/Moscow”)) restituisce un ZonedDateTime per Mosca. Senza un fuso orario, la conversione è impossibile — Instant non contiene informazioni calendario.
Per convertire Instant in LocalDateTime: atZone(ZoneId).toLocalDateTime(). Questo approccio è esplicito e non perde informazioni. Conversione inversa: LocalDateTime.atZone(ZoneId).toInstant().
Per la compatibilità con java.util.Date: Date.from(instant) e date.toInstant(). Questa è una conversione bidirezionale che preserva la precisione fino ai millisecondi (Date non supporta i nanosecondi). Per java.sql.Timestamp, usa Timestamp.from(instant) con supporto ai nanosecondi.
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()
La caratteristica principale di Instant è che è completamente indipendente dai fusi orari. Instant.now() restituisce lo stesso risultato su qualsiasi dispositivo in qualsiasi parte del mondo. Ciò si ottiene fissando il tempo in UTC.
Un fuso orario è necessario solo per mostrare Instant a un umano. Per questo, si usa atZone(ZoneId). ZoneId.systemDefault() restituisce il fuso orario del dispositivo impostato nel sistema operativo. ZoneOffset.UTC è la costante per UTC.
Nei sistemi distribuiti, si raccomanda di memorizzare e trasmettere tutti i timestamp in Instant (o OffsetDateTime con ZoneOffset.UTC). La conversione all'ora locale viene eseguita solo sul client prima della visualizzazione all'utente. Ciò previene confusione con i fusi orari.
Nelle applicazioni Android distribuite, la sincronizzazione temporale è critica per un corretto caching, notifiche e modifica collaborativa. Instant è la scelta naturale per questo compito grazie al suo ancoraggio in UTC.
Quando si confrontano timestamp da dispositivi diversi, bisogna considerare che gli orologi di sistema possono divergere. Si raccomanda di usare l'ora del server come riferimento. Il server restituisce Instant in UTC, e il client lo confronta con Instant locale solo per calcoli relativi.
Per calcolare la differenza tra due istanti, usa Duration.between(Instant start, Instant end). Questo metodo restituisce una Duration che può essere convertita in ore, minuti, secondi. I metodi isAfter() e isBefore() permettono di confrontare istanti.
fun isCacheExpired(
cachedAt: Instant,
ttlMinutes: Long
): Boolean {
val elapsed = Duration.between(cachedAt, Instant.now())
return elapsed.toMinutes() >= ttlMinutes
}
Il primo esempio è la registrazione di eventi con un timestamp. Instant viene salvato nel database Room e inviato al server. Il timestamp viene registrato in UTC per un'interpretazione univoca.
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) }
}
}
Il secondo esempio è determinare il tempo trascorso da un evento. Usiamo Duration.between per mostrare “5 minuti fa”, “2 ore fa” — un formato comune nei messenger e nei social network.
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"
}
}
Il terzo esempio è la sincronizzazione dei dati tra server e client. Usiamo Instant per tracciare l'ora dell'ultimo aggiornamento.
class SyncManager {
private var lastSyncAt: Instant? = null
fun sync() {
val syncStart = Instant.now()
// server request with lastSyncAt
lastSyncAt = syncStart
}
fun shouldSync(intervalMinutes: Long): Boolean {
val last = lastSyncAt ?: return true
return Duration.between(last, Instant.now())
.toMinutes() >= intervalMinutes
}
}
Il primo errore è usare Instant.now().toString() per la visualizzazione all'utente. Instant produce nel formato UTC “2026-07-21T15:00:00Z”, che è illeggibile per gli umani. Converti sempre Instant tramite atZone() nel fuso orario locale prima di visualizzarlo.
Il secondo errore è perdere nanosecondi durante la conversione in java.util.Date. Date supporta solo millisecondi. Se Instant ha nanosecondi, verranno scartati in Date.from(instant). Usa Instant.truncatedTo(ChronoUnit.MILLIS) per specificare esplicitamente la precisione.
Il terzo errore è la confusione tra toEpochMilli() e getEpochSecond(). toEpochMilli() restituisce il numero di millisecondi dall'epoca (long), mentre getEpochSecond() restituisce il numero di secondi (long). Confondere questi metodi può causare un errore di 1000x.
Il quarto errore è presumere che Instant.now() sia sincronizzato su tutti i dispositivi. Gli orologi di sistema possono differire di minuti o addirittura ore. Per operazioni critiche in termini di tempo (autenticazione, pagamenti), usa Instant del server come fonte di verità.
Domande Frequenti
System.currentTimeMillis() restituisce un long — il numero di millisecondi dall'epoca senza legame con il fuso orario. Instant fornisce la stessa funzionalità ma con precisione al nanosecondo e una ricca API per conversioni, confronti e compatibilità con java.time.
Room non supporta Instant direttamente. Usa TypeConverter che converte Instant in Long (toEpochMilli) e viceversa (Instant.ofEpochMilli). Per la precisione al nanosecondo, salva due campi: epoca-secondi e nanosecondi.
Sì, Instant è immutabile e implementa correttamente equals() e hashCode(). Due Instant con lo stesso valore saranno uguali. Questo lo rende una chiave affidabile per HashMap e altre collezioni, a differenza del mutabile java.util.Date.
Usa Duration.between(start, end) per ottenere una Duration o ChronoUnit.SECONDS.between(start, end) per la differenza in secondi (long). Duration fornisce i metodi toMinutes(), toHours(), toDays() e toNanos().
Instant è progettato come un punto assoluto sulla linea temporale. Senza specificare un fuso orario o UTC, l'analisi è impossibile perché Instant non contiene informazioni calendario. Il suffisso “Z” denota offset zero (UTC) ed è obbligatorio per il formato ISO-8601.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche