W3docs

Java Instant

Representa un momento en la línea de tiempo en UTC con Instant en Java: ideal para marcas de tiempo y tiempo de máquina.

Instant es un único momento en la línea de tiempo global, almacenado como nanosegundos desde la época Unix (1970-01-01T00:00:00Z). No tiene zona, ni calendario, ni noción de "qué día es" — solo un contador. Ese contador está en UTC por construcción, por lo que dos Instants de máquinas distintas en zonas diferentes se comparan directamente: el que tiene el número más bajo es el anterior.

Este es el tipo para marcas de tiempo de máquina. Líneas de registro. Marcas de tiempo de mensajes. "Cuándo recibió el servidor la solicitud". Trazas de auditoría. Cualquier cosa que deba ser ordenable globalmente y que nunca deba ser ambigua sobre qué día representa — porque no hay ningún "día" en absoluto, solo segundos.

Creación

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

La forma de cadena termina con una Z literal (de "Zulu time," militar para UTC). Instant.parse("2025-11-04T19:30:00") (sin Z) es un error de análisis — el tipo se niega a adivinar qué zona quisiste indicar.

Dos métodos de fábrica que usarás con frecuencia:

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

La mayoría de los formatos de marca de tiempo externos (Unix time(2), syslog, enteros JSON created_at) son segundos o milisegundos desde la época. Las fábricas ofEpochSecond / ofEpochMilli son el puente estándar.

Resolución

Instant tiene precisión de nanosegundos (1 segundo = 1.000.000.000 ns). En la mayoría de los sistemas, el reloj subyacente tiene menor resolución — la de milisegundos es típica, la de microsegundos en Linux moderno. Instant.now() devuelve un valor en la resolución disponible; los nanosegundos no utilizados son cero.

Accesores:

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 es la conversión con pérdida: los nanosegundos se truncan a milisegundos. Para líneas de registro y marcas de tiempo JSON esto suele estar bien; para registros de eventos de alta frecuencia, usa getEpochSecond + getNano por separado.

Sin calendario

Instant.getDayOfMonth() no existe. Tampoco getYear, getHour, ni ninguno de los accesores de calendario. El tipo genuinamente no lo sabe — la información del calendario requiere una zona, y Instant no tiene ninguna. Si quieres preguntar "¿qué hora era en Nueva York cuando ocurrió esto?", debes adjuntar primero una zona:

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

atZone(zone) es el puente en la dirección contraria a ZonedDateTime.toInstant(). Los dos juntos te ofrecen el viaje de ida y vuelta completo: momento ↔ etiqueta con zona. Consulta Java ZonedDateTime para el lado del calendario de ese emparejamiento.

Aritmética

Misma forma fluida:

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

No hay plusDays en Instant (en el sentido del calendario). Tiene plus(amount, ChronoUnit), y ChronoUnit.DAYS funciona porque el JDK define un Day como exactamente 24 horas de segundos para Instant. Eso no es lo que es un día del calendario cuando el horario de verano está en juego, que es la razón por la que Instant no pretende serlo.

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

Para operaciones con forma de calendario ("un mes después en la zona del usuario"), utiliza ZonedDateTime:

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

Comparación

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

Instant implementa Comparable<Instant> con ordenación natural por segundo de época y luego nanosegundos. equals es directo: mismo segundo y mismos nanosegundos.

Distancia

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

Para marcas de tiempo de máquina, todas son exactas — no hay ambigüedad de calendario. ChronoUnit.MONTHS.between(start, end) sobre Instants lanza una excepción porque los meses no tienen longitud constante cuando se miden en segundos: no hay zona, por lo que el calculador no tiene forma de saber qué mes contiene esos segundos. Ese es el modo de fallo correcto.

Puente con java.util.Date

El código antiguo usa java.util.Date. Las conversiones son directas:

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

Date es internamente un envoltorio alrededor de un long de época en milisegundos, por lo que el viaje de ida y vuelta no tiene pérdidas salvo en nanosegundos (Date tiene precisión de milisegundos, Instant de nanosegundos). El capítulo Legacy Date cubre la migración en detalle.

Por qué todo lo interno debería ser Instant

La recomendación que ha surgido de ejecutar java.time en producción durante diez años:

  • Internamente, usa Instant. Almacenamiento, comparación, registro, marcas de tiempo de mensajes, en cualquier lugar donde el valor fluya de máquina a máquina.
  • En el límite — al mostrar al usuario, al aceptar entrada del usuario — convierte a ZonedDateTime o LocalDateTime usando la zona correcta para el contexto.

Esto separa "lo que realmente ocurrió" de "cómo está etiquetado". Un error en el límite (zona incorrecta) deja tus valores internos correctos; un error en el sistema de tipos que permite que LocalDateTime fluya internamente tiende a dejarte con marcas de tiempo que silenciosamente están en zonas diferentes.

Un ejemplo práctico: un pequeño registro de eventos

El programa siguiente registra una secuencia de eventos como Instants, calcula duraciones entre eventos, demuestra el límite del calendario adjuntando una zona para la visualización, muestra el puente con Date heredado, y finalmente ilustra la regla de "sin calendario" mostrando que ChronoUnit.MONTHS.between sobre dos Instants lanza una excepción.

java— editable, runs on the server

Qué extraer de la ejecución:

  • Instant.parse("2025-11-04T19:30:00Z") se analizó solo por la Z al final. Quita la Z y el análisis falla — el tipo insiste en saber que la cadena está en UTC. Otras zonas deben pasar por ZonedDateTime.parse (o suministrarse mediante atZone).
  • La secuencia de eventos usó Duration.between(...). Cada resultado fue un recuento de milisegundos entero limpio — sin confusión de zonas, sin horario de verano, sin aritmética de calendario. Por eso el tiempo del lado del servidor pertenece en Instant: la aritmética es solo una resta de longs bajo el capó.
  • El bloque "mismo instante, dos etiquetas de zona" imprimió ZonedDateTimes que se veían diferentes pero eran el mismo Instant. atZone(...) es puramente una operación de visualización sobre Instant. Si quieres hacer aritmética de calendario (el próximo mes, fin de semana), hazlo sobre el ZonedDateTime, luego llama .toInstant() para volver.
  • Date.from(inst) y legacy.toInstant() no tuvieron pérdidas salvo en nanosegundos. Date solo lleva milisegundos, por lo que hacer el viaje de ida y vuelta a través de Date trunca la precisión sub-ms. Para la mayoría de los registros esto está bien; para la captura de eventos de alta precisión, quédate en Instant de extremo a extremo y nunca hagas el viaje de ida y vuelta a través de Date.
  • ChronoUnit.MONTHS.between(a, b) lanzó UnsupportedTemporalTypeException. Ese es el modo de fallo correcto: los meses no son segundos de longitud constante, y el JDK se niega a inventar una respuesta. Pasar por ZonedDateTime suministró la zona faltante, y la misma llamada funcionó. El patrón es general: las operaciones con forma de calendario necesitan una zona, y el sistema de tipos te obliga a suministrar una explícitamente.

Qué sigue

Instant es el momento. Los siguientes dos capítulos cubren las longitudes de tiempo entre momentos: Java Duration para mediciones de "X segundos, Y nanosegundos", y Java Period para longitudes de calendario de "X años, Y meses, Z días". Los dos juntos son la forma de expresar "una hora después" frente a "un mes después" sin que ninguno sea impreciso.

Práctica

Práctica
Una línea de registro incluye `created_at: 1700000000` (un segundo de época Unix). Necesitas un valor de `java.time` que puedas comparar con `Instant.now()` y pasar a un cálculo `Duration.between(...)`. ¿Cuál conversión es correcta?
Una línea de registro incluye `created_at: 1700000000` (un segundo de época Unix). Necesitas un valor de `java.time` que puedas comparar con `Instant.now()` y pasar a un cálculo `Duration.between(...)`. ¿Cuál conversión es correcta?
Was this page helpful?