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 mandatoryLa 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 resolutionLa 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 rangetoEpochMilli 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 * 3600sNo 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 secondsPara 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 daysPara 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 -> InstantDate 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
ZonedDateTimeoLocalDateTimeusando 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.
Qué extraer de la ejecución:
Instant.parse("2025-11-04T19:30:00Z")se analizó solo por laZal final. Quita laZy el análisis falla — el tipo insiste en saber que la cadena está en UTC. Otras zonas deben pasar porZonedDateTime.parse(o suministrarse medianteatZone).- 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 enInstant: la aritmética es solo una resta delongs bajo el capó. - El bloque "mismo instante, dos etiquetas de zona" imprimió
ZonedDateTimes que se veían diferentes pero eran el mismoInstant.atZone(...)es puramente una operación de visualización sobreInstant. Si quieres hacer aritmética de calendario (el próximo mes, fin de semana), hazlo sobre elZonedDateTime, luego llama.toInstant()para volver. Date.from(inst)ylegacy.toInstant()no tuvieron pérdidas salvo en nanosegundos.Datesolo lleva milisegundos, por lo que hacer el viaje de ida y vuelta a través deDatetrunca 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 enInstantde extremo a extremo y nunca hagas el viaje de ida y vuelta a través deDate.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 porZonedDateTimesuministró 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.