Instant — una clase inmutable del paquete java.time que representa un punto en la línea de tiempo en UTC con precisión de nanosegundos. A diferencia de LocalDateTime, Instant no contiene fecha y hora en un formato legible para humanos — es una representación mecánica de un momento. Según la especificación de Oracle Java 17 (2024), Instant está diseñado para el intercambio mecánico de marcas de tiempo y es un análogo de System.currentTimeMillis(), pero con precisión de nanosegundos.
Puntos Clave
Instant es una clase que modela un único punto en la línea de tiempo. Su representación interna consta de dos campos: long seconds (la cantidad de segundos desde 1970-01-01T00:00:00Z) e int nanos (nanosegundos dentro del segundo actual, de 0 a 999999999).
El rango de valores de Instant es de -31557014167219200 a 31556889864403199 segundos desde la época, cubriendo aproximadamente 292 millones de años en ambas direcciones. Esto es suficiente para cualquier tarea práctica, incluidos los cálculos astronómicos.
Según Baeldung (2024), Instant es un puente entre los tipos legibles para humanos (LocalDateTime, ZonedDateTime) y los formatos de máquina (timestamp en milisegundos). Instant se utiliza para registro, almacenamiento en caché, sincronización y todas las tareas donde un momento absoluto en el tiempo es importante.
La clase implementa las interfaces Comparable (para comparar momentos) y Temporal (para usar en la API común de java.time). Instant es inmutable — todos los métodos devuelven un nuevo objeto.
Antes de Java 8, se utilizaban java.util.Date y System.currentTimeMillis() para trabajar con momentos en el tiempo. Ambos enfoques tienen inconvenientes. Date es mutable, no es thread-safe, almacena el tiempo en milisegundos desde la época, pero los nombres de sus métodos están desactualizados (getYear() devuelve 116 para 2016).
Long (un timestamp simple) es rápido y compacto, pero no tiene soporte incorporado para nanosegundos, no se muestra en un formato legible y requiere análisis manual durante la depuración. El enfoque Long tampoco distingue tipos de datos — un desarrollador podría pasar un valor incorrecto.
Instant resuelve todos estos problemas. Es inmutable, contiene información explícita de precisión (segundos + nanosegundos), se serializa al formato ISO-8601 “2026-07-21T15:00:00Z” y tiene una API rica para conversiones. Según SonarSource (2024), Instant es el reemplazo recomendado de Date en todos los proyectos nuevos.
El momento actual se obtiene mediante Instant.now(). A diferencia de LocalDateTime.now(), Instant.now() siempre devuelve la hora en UTC, ignorando la zona horaria del dispositivo. Esto lo hace ideal para marcas de tiempo de servidor.
Desde valores existentes: Instant.ofEpochSecond(long epochSecond) — desde segundos desde la época, Instant.ofEpochMilli(long epochMilli) — desde milisegundos, Instant.parse(CharSequence) — desde una cadena ISO-8601 (“2026-07-21T15:00:00Z”).
Para lectura: getEpochSecond() — la cantidad de segundos desde la época, toEpochMilli() — la cantidad de milisegundos, getNano() — nanosegundos. El método toString() devuelve una cadena en 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 se convierte a ZonedDateTime mediante atZone(ZoneId). Por ejemplo, Instant.now().atZone(ZoneId.of(“Europe/Moscow”)) devuelve un ZonedDateTime para Moscú. Sin una zona, la conversión es imposible — Instant no contiene información de calendario.
Para convertir Instant a LocalDateTime: atZone(ZoneId).toLocalDateTime(). Este enfoque es explícito y no pierde información. Conversión inversa: LocalDateTime.atZone(ZoneId).toInstant().
Para compatibilidad con java.util.Date: Date.from(instant) y date.toInstant(). Esta es una conversión bidireccional que conserva la precisión hasta milisegundos (Date no soporta nanosegundos). Para java.sql.Timestamp, use Timestamp.from(instant) con soporte de nanosegundos.
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 característica clave de Instant es que es completamente independiente de las zonas horarias. Instant.now() devuelve el mismo resultado en cualquier dispositivo en cualquier parte del mundo. Esto se logra fijando la hora en UTC.
Una zona horaria solo es necesaria para mostrar Instant a un humano. Para esto, se utiliza atZone(ZoneId). ZoneId.systemDefault() devuelve la zona horaria del dispositivo configurada en el sistema operativo. ZoneOffset.UTC es la constante para UTC.
En sistemas distribuidos, se recomienda almacenar y transmitir todas las marcas de tiempo en Instant (o OffsetDateTime con ZoneOffset.UTC). La conversión a hora local se realiza solo en el cliente antes de mostrar al usuario. Esto evita confusiones con las zonas horarias.
En aplicaciones de Android distribuidas, la sincronización de tiempo es crítica para el correcto funcionamiento del caché, las notificaciones y la edición colaborativa. Instant es la opción natural para esta tarea gracias a su anclaje en UTC.
Al comparar marcas de tiempo de diferentes dispositivos, hay que considerar que los relojes del sistema pueden divergir. Se recomienda usar la hora del servidor como referencia. El servidor devuelve Instant en UTC, y el cliente lo compara con el Instant local solo para cálculos relativos.
Para calcular la diferencia entre dos momentos, use Duration.between(Instant start, Instant end). Este método devuelve una Duration que se puede convertir a horas, minutos, segundos. Los métodos isAfter() e isBefore() permiten comparar momentos.
fun isCacheExpired(
cachedAt: Instant,
ttlMinutes: Long
): Boolean {
val elapsed = Duration.between(cachedAt, Instant.now())
return elapsed.toMinutes() >= ttlMinutes
}
El primer ejemplo es el registro de eventos con una marca de tiempo. Instant se guarda en la base de datos Room y se envía al servidor. La marca de tiempo se registra en UTC para una interpretación inequívoca.
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) }
}
}
El segundo ejemplo es determinar el tiempo transcurrido desde un evento. Usamos Duration.between para mostrar “hace 5 minutos”, “hace 2 horas” — un formato común en mensajeros y redes sociales.
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"
}
}
El tercer ejemplo es la sincronización de datos entre el servidor y el cliente. Usamos Instant para rastrear la hora de la última actualización.
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
}
}
El primer error es usar Instant.now().toString() para mostrar al usuario. Instant genera el formato UTC “2026-07-21T15:00:00Z”, que es ilegible para los humanos. Siempre convierta Instant mediante atZone() a la zona horaria local antes de mostrarlo.
El segundo error es perder nanosegundos al convertir a java.util.Date. Date solo admite milisegundos. Si Instant tiene nanosegundos, se descartarán en Date.from(instant). Use Instant.truncatedTo(ChronoUnit.MILLIS) para especificar explícitamente la precisión.
El tercer error es la confusión entre toEpochMilli() y getEpochSecond(). toEpochMilli() devuelve la cantidad de milisegundos desde la época (long), mientras que getEpochSecond() devuelve la cantidad de segundos (long). Confundir estos métodos puede resultar en un error de 1000x.
El cuarto error es asumir que Instant.now() está sincronizado en todos los dispositivos. Los relojes del sistema pueden diferir por minutos o incluso horas. Para operaciones críticas en el tiempo (autenticación, pagos), use Instant del servidor como fuente de verdad.
Preguntas Frecuentes
System.currentTimeMillis() devuelve un long — la cantidad de milisegundos desde la época sin vinculación de zona horaria. Instant proporciona la misma funcionalidad pero con precisión de nanosegundos y una API rica para conversiones, comparaciones y compatibilidad con java.time.
Room no admite Instant directamente. Use TypeConverter que convierta Instant a Long (toEpochMilli) y viceversa (Instant.ofEpochMilli). Para precisión de nanosegundos, guarde dos campos: época-segundos y nanosegundos.
Sí, Instant es inmutable e implementa correctamente equals() y hashCode(). Dos Instant con el mismo valor serán iguales. Esto lo convierte en una clave confiable para HashMap y otras colecciones, a diferencia de java.util.Date que es mutable.
Use Duration.between(start, end) para obtener una Duration o ChronoUnit.SECONDS.between(start, end) para la diferencia en segundos (long). Duration proporciona los métodos toMinutes(), toHours(), toDays() y toNanos().
Instant está diseñado como un punto absoluto en la línea de tiempo. Sin especificar una zona horaria o UTC, el análisis es imposible porque Instant no contiene información de calendario. El sufijo “Z” denota desplazamiento cero (UTC) y es obligatorio para el formato ISO-8601.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también