El pilar del formato de fechas en Java
java.time.format.DateTimeFormatter (introducido en Java 8) es la API moderna para convertir entre objetos Temporal (como LocalDate, LocalDateTime, ZonedDateTime) y cadenas. Reemplaza al antiguo y propenso a errores SimpleDateFormat, que era mutable y no thread-safe.
DateTimeFormatter es inmutable y thread-safe, ideal para constantes de clase.
Formatear objetos a String
Cada clase de java.time tiene un método format(DateTimeFormatter):
LocalDate hoy = LocalDate.now();
String s = hoy.format(DateTimeFormatter.ISO_LOCAL_DATE); // "2026-04-17"
LocalDateTime ahora = LocalDateTime.now();
String t = ahora.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
// "2026-04-17T15:23:45.123"
Parsear strings a Temporal
Cada clase tiene un método estático parse(CharSequence, DateTimeFormatter):
LocalDate fecha = LocalDate.parse("2026-04-17");
// O explícito:
LocalDate fecha2 = LocalDate.parse("17/04/2026",
DateTimeFormatter.ofPattern("dd/MM/yyyy"));
Si la entrada no cuadra con el formato, lanza DateTimeParseException.
Formatos predefinidos
DateTimeFormatter incluye constantes estáticas para formatos ISO comunes:
| Constante | Ejemplo |
|-----------|---------|
| ISO_LOCAL_DATE | 2026-04-17 |
| ISO_LOCAL_TIME | 15:23:45.123 |
| ISO_LOCAL_DATE_TIME | 2026-04-17T15:23:45.123 |
| ISO_OFFSET_DATE_TIME | 2026-04-17T15:23:45+02:00 |
| ISO_ZONED_DATE_TIME | 2026-04-17T15:23:45+02:00[Europe/Madrid] |
| ISO_DATE | Varios (acepta tanto 2026-04-17 como +01000000-04-17) |
| ISO_INSTANT | 2026-04-17T13:23:45.123Z |
| BASIC_ISO_DATE | 20260417 |
| RFC_1123_DATE_TIME | Fri, 17 Apr 2026 15:23:45 GMT |
Úsalos siempre que sea posible: son la forma canónica de intercambio de fechas.
Patrones personalizados con ofPattern
Para formatos no estándar, crea un formatter con DateTimeFormatter.ofPattern(String):
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String s = hoy.format(fmt); // "17/04/2026"
LocalDate f = LocalDate.parse("01/01/2026", fmt);
Símbolos de patrón principales
| Símbolo | Significado | Ejemplos |
|---------|-------------|----------|
| y | Año | y a 2026, yy a 26, yyyy a 2026 |
| M | Mes | M a 4, MM a 04, MMM a abr., MMMM a abril |
| d | Día del mes | d a 7, dd a 07 |
| E | Día de la semana | EEE a vie., EEEE a viernes |
| H | Hora 24h | H a 9, HH a 09 |
| h | Hora 12h | h a 9, hh a 09 |
| m | Minuto | m a 5, mm a 05 |
| s | Segundo | s, ss |
| S | Milisegundo | SSS a 123 |
| a | AM/PM | a a AM / PM |
| z | Nombre de zona | z a CEST |
| V | Zona ID | V a Europe/Madrid |
| Z | Offset | Z a +0200, ZZZZZ a +02:00 |
| 'texto' | Literal | 'T' |
Ejemplo combinado:
DateTimeFormatter f = DateTimeFormatter.ofPattern("EEEE, d 'de' MMMM 'de' yyyy");
LocalDate fecha = LocalDate.of(2026, 4, 17);
System.out.println(fecha.format(f.withLocale(new Locale("es"))));
// "viernes, 17 de abril de 2026"
Locale
El idioma de nombres de meses/días depende del Locale. Por defecto usa el locale de la JVM. Cámbialo con withLocale:
DateTimeFormatter espanol = DateTimeFormatter.ofPattern("EEEE d MMMM yyyy")
.withLocale(new Locale("es"));
DateTimeFormatter ingles = DateTimeFormatter.ofPattern("EEEE, MMMM d, yyyy")
.withLocale(Locale.US);
LocalDate f = LocalDate.of(2026, 4, 17);
f.format(espanol); // "viernes 17 abril 2026"
f.format(ingles); // "Friday, April 17, 2026"
Formatters localizados por estilo
DateTimeFormatter.ofLocalizedDate, ofLocalizedTime y ofLocalizedDateTime producen formatters adaptados al locale y a un estilo (FULL, LONG, MEDIUM, SHORT):
DateTimeFormatter f = DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(new Locale("es"));
LocalDate.now().format(f); // "viernes, 17 de abril de 2026"
Ideal cuando quieres el formato "natural" del idioma sin diseñar un patrón a mano.
Parseo robusto: errores y alternativas
El parseo lanza DateTimeParseException si no cuadra. Captúralo o valida antes:
try {
LocalDate f = LocalDate.parse(entrada, DateTimeFormatter.ofPattern("dd/MM/yyyy"));
} catch (DateTimeParseException e) {
log.warn("Fecha inválida: {}", entrada, e);
}
Para probar múltiples formatos, usa DateTimeFormatterBuilder.appendOptional o intenta varios en cadena.
Modo estricto vs lenient
Por defecto, DateTimeFormatter es estricto (SMART): "2026-02-30" falla porque febrero no tiene 30. Puedes cambiar con ResolverStyle:
DateTimeFormatter lenient = DateTimeFormatter
.ofPattern("yyyy-MM-dd")
.withResolverStyle(ResolverStyle.LENIENT);
// LENIENT permite overflow: "2026-02-30" se reinterpreta como 2 de marzo
En producción, STRICT es preferible para evitar interpretaciones ambiguas.
Formatter con DateTimeFormatterBuilder
Para casos complejos que requieren composición de partes:
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
.appendLiteral('-')
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(ChronoField.DAY_OF_MONTH, 2)
.optionalStart()
.appendLiteral(' ')
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.optionalEnd()
.toFormatter();
Permite formatos con partes opcionales, locale-específico, etc.
Patrones recomendados de uso
Formatter como constante
public class Fechas {
public static final DateTimeFormatter DD_MM_YYYY =
DateTimeFormatter.ofPattern("dd/MM/yyyy");
public static final DateTimeFormatter ISO_MINUTE =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm");
}
Inmutable, thread-safe, reutilizable sin coste.
Formatter por hilo (antigua necesidad con SimpleDateFormat)
Con DateTimeFormatter no lo necesitas: es thread-safe, compártelo libremente.
SimpleDateFormat (legacy)
El antiguo java.text.SimpleDateFormat tenía graves problemas:
- Mutable: cambiar el locale o patrón modifica la instancia.
- No thread-safe: en concurrente dabá resultados corruptos silenciosamente.
- Patrones ligeramente distintos.
Nunca uses SimpleDateFormat en código nuevo: DateTimeFormatter es superior en todos los aspectos.
Resumen
DateTimeFormatter:
- Inmutable y thread-safe.
- Formatos ISO listos para usar (
ISO_LOCAL_DATE, etc.). - Patrones personalizados con
ofPattern. - Localización con
withLocale. - Estilos localizados con
ofLocalizedDate/Time/DateTime.
Patrones recomendados:
- Declara formatters como
static finalen tus clases. - Para intercambio técnico, usa ISO.
- Para usuarios, combina
ofLocalizedXxxconwithLocale. - Captura
DateTimeParseExceptional parsear entradas externas.
Es la base del manejo de fechas en Java moderno. Dominarlo evita un 90% de los bugs clásicos con fechas.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Java
Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Formatear LocalDate, LocalDateTime, ZonedDateTime a String. Parsear cadenas a Temporal con formato predefinido o personalizado. Usar los formatos ISO estándar (ISO_LOCAL_DATE, ISO_DATE_TIME, etc.). Construir DateTimeFormatter.ofPattern('yyyy-MM-dd') con patrones. Localizar formato con withLocale. Manejar errores de parseo con DateTimeParseException.