W3docs

Formateo de fechas en Java

Formatea fechas y horas de Java en cadenas con DateTimeFormatter usando patrones estándar o personalizados.

El formateo de fechas consiste en convertir un valor de fecha/hora en una cadena legible por humanos. Este capítulo cubre DateTimeFormatter — cómo construir uno (integrado, localizado o basado en patrones), el alfabeto completo de patrones, el manejo de locale y zona horaria, y los errores comunes que producen resultados incorrectos. Funciona con todos los tipos de java.time: LocalDate, LocalTime, LocalDateTime, ZonedDateTime e Instant.

Cada tipo de java.time tiene un toString() que produce la representación ISO-8601: 2025-11-04, 14:30:00, 2025-11-04T14:30:00Z. Eso está bien para logs y tráfico máquina a máquina. Para mostrar información a humanos ("4 de noviembre de 2025" o "4 Nov, 14:30") necesitas un formateador.

La clase es java.time.format.DateTimeFormatter. Es el reemplazo moderno, thread-safe e inmutable del legado java.text.SimpleDateFormat (que no era thread-safe y causaba errores sutiles en producción cuando se compartía). Guárdalo en caché como static final y reutilízalo en todos los hilos para siempre — sin sincronización, sin copias defensivas.

Tres formas de construir un formateador

// 1. Built-in ISO formatters
DateTimeFormatter.ISO_LOCAL_DATE;                  // 2025-11-04
DateTimeFormatter.ISO_LOCAL_DATE_TIME;             // 2025-11-04T14:30:00
DateTimeFormatter.ISO_OFFSET_DATE_TIME;            // 2025-11-04T14:30:00-05:00
DateTimeFormatter.ISO_ZONED_DATE_TIME;             // 2025-11-04T14:30:00-05:00[America/New_York]
DateTimeFormatter.ISO_INSTANT;                     // 2025-11-04T19:30:00Z
DateTimeFormatter.BASIC_ISO_DATE;                  // 20251104

// 2. Localised formatters
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);          // November 4, 2025 (en-US)
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);    // Nov 4, 2025, 2:30:00 PM

// 3. Pattern-based formatters
DateTimeFormatter.ofPattern("dd MMM yyyy");                   // 04 Nov 2025
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm zzz");          // 2025-11-04 14:30 EST

La API de patrones es la que más usarás. La localizada es la correcta cuando necesitas un formato apropiado para una cultura y quieres que el JDK elija la disposición por ti.

Formateo

La forma de llamada es simétrica en ambos lados:

String s = formatter.format(temporal);
String s2 = temporal.format(formatter);                       // same thing, fluent style

Ambas funcionan. La mayoría del código usa la forma fluida.

LocalDate today = LocalDate.now();
String us = today.format(DateTimeFormatter.ofPattern("MM/dd/yyyy"));         // 11/04/2025
String iso = today.format(DateTimeFormatter.ISO_LOCAL_DATE);                 // 2025-11-04
String eu = today.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));          // 04.11.2025

El alfabeto de patrones

La tabla principal — a la que volverás. Las letras distinguen mayúsculas y minúsculas y la cantidad importa.

LetraSignificadoEjemplo
yañoy2025, yy25, yyyy2025
MmesM11, MM11, MMMNov, MMMMNovember
ddía del mesd4, dd04
Edía de la semanaETue, EEEETuesday
Hhora 0-23H14, HH14
hhora 1-12h2, hh02 (usar con a)
aAM/PMaPM
mminutom5, mm05
ssegundos9, ss09
Sfracción de segundoSSS123 (millis)
nnanosegundonnnnnnnnn123456789
znombre de zonazEST, zzzzEastern Standard Time
Zdesplazamiento de zonaZ-0500, ZZ-0500, ZZZZGMT-05:00
Xdesplazamiento ISOX-05, XX-0500, XXX-05:00
VID de zonaVVAmerica/New_York

El texto literal se escribe entre comillas simples:

DateTimeFormatter.ofPattern("EEEE, MMMM d 'at' h:mm a");      // Tuesday, November 4 at 2:30 PM

Para una comilla simple literal, usa dos: ''.

El par que más confunde es m vs M (minúscula = minuto, mayúscula = mes) y H vs h (mayúscula = 0-23, minúscula = 1-12). La mayoría de los errores del tipo "la hora está mal por algo extraño" provienen de uno de estos errores tipográficos.

Localización: Locale y withLocale

Un formateador toma el locale predeterminado de la JVM a menos que le indiques lo contrario. Para una salida "siempre en inglés" o "siempre en alemán", fija el locale:

DateTimeFormatter english = DateTimeFormatter.ofPattern("EEEE, MMMM d", Locale.US);
DateTimeFormatter german  = DateTimeFormatter.ofPattern("EEEE, d. MMMM", Locale.GERMAN);
DateTimeFormatter french  = DateTimeFormatter.ofPattern("EEEE d MMMM", Locale.FRENCH);

today.format(english);   // Tuesday, November 4
today.format(german);    // Dienstag, 4. November
today.format(french);    // mardi 4 novembre

Para contenido renderizado en el servidor, siempre pasa un locale. El "locale predeterminado de la JVM" es impredecible en servidores de producción y es la fuente de errores del tipo "funciona bien en mi laptop, pero mal en el servidor".

Visualización de zona horaria

ZonedDateTime e Instant son los únicos tipos que tienen información de zona. Formatear un LocalDateTime con un patrón que incluye z o Z lanza una excepción — no hay zona que imprimir. Convierte primero:

ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/New_York"));
zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z"));   // 2025-11-04 14:30 EST

Para Instant, el formateador también necesita una zona — Instant no tiene ninguna, por lo que los formateadores de visualización que incluyen cualquier campo dependiente de zona necesitan un withZone:

DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
    .withZone(ZoneId.of("America/New_York"));
f.format(Instant.now());                                      // formatter supplies the zone for display

Sin withZone, formatear un Instant con un patrón con forma de calendario lanza una excepción.

Formateadores con estilo mediante FormatStyle

Las fábricas localizadas ofrecen cuatro tamaños canónicos:

DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);   // 11/4/25 (en-US), 04.11.25 (de-DE)
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);  // Nov 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);    // November 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);    // Tuesday, November 4, 2025

Los mismos cuatro tamaños existen para ofLocalizedTime y ofLocalizedDateTime. Úsalos cuando quieras que la disposición siga el locale del usuario en lugar de imponer una forma fija. Combínalos con .withLocale(...) para fijar el locale.

Un ejemplo práctico: una fecha, seis variantes de visualización

El programa a continuación formatea un ZonedDateTime de seis maneras comunes: ISO para logs de máquina, formato US de 12 horas para usuarios en inglés, formato europeo de 24 horas para usuarios alemanes, una forma larga localizada, un patrón personalizado con texto literal incrustado, y un formateador Instant-mediante-withZone para marcas de tiempo sin procesar.

java— editable, runs on the server

Lo que debes extraer de la ejecución:

  • Los campos static final DateTimeFormatter en caché tienen la forma correcta. DateTimeFormatter es inmutable y thread-safe; crear uno es barato pero no gratuito, y reutilizar la misma instancia en todas partes es el patrón recomendado por el JDK. No construyas uno nuevo dentro de un bucle muy ejecutado.
  • El mismo ZonedDateTime produjo seis cadenas diferentes dependiendo del formateador. El objeto de valor nunca cambió; el formateador es lo único que controla la disposición. Esa es la separación para la que existe DateTimeFormatter — mantén el tipo de valor limpio y delega la presentación al formateador.
  • El bloque de "errores tipográficos comunes" imprimió 14:11 para HH:MM porque M es mes, no minuto. Los dos son el par más confuso del alfabeto de patrones. Si la hora que se muestra parece sospechosamente un componente de fecha, revisa las mayúsculas del patrón.
  • La escala de FormatStyle produjo cuatro cadenas progresivamente más largas. Usa FormatStyle.MEDIUM como valor predeterminado razonable para "mostrar una fecha a un usuario sin pensar demasiado"; LONG y FULL para contextos donde el año y el día de la semana deben ser inequívocos; SHORT para espacios de UI ajustados.
  • LocalDateTime con un patrón con zona lanzó una excepción — el formateador necesita datos de zona, y LocalDateTime no tiene ninguna. La solución es convertir (ldt.atZone(zone)) o eliminar el campo con zona del patrón. De cualquier manera, el modo de fallo es claro en tiempo de ejecución.

¿Qué sigue?

El formateo es la dirección valor → string. El siguiente capítulo, Análisis de fechas en Java, es la operación inversa — string → valor — usando los mismos patrones de DateTimeFormatter y el mismo conjunto de advertencias. Los dos juntos forman el límite de E/S para cualquier código que intercambie fechas con usuarios, configuraciones, logs o APIs remotas.

Práctica

Práctica
Un servidor web registra marcas de tiempo con `ZonedDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm'))`. En una JVM con locale alemán, el mes se imprime como 'Nov' en lugar de '11'. ¿Cuál es la causa más probable?
Un servidor web registra marcas de tiempo con `ZonedDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm'))`. En una JVM con locale alemán, el mes se imprime como 'Nov' en lugar de '11'. ¿Cuál es la causa más probable?
Was this page helpful?