Formateo de Fechas en Python, JavaScript y Java
Cómo parsear, formatear y mostrar fechas entre zonas horarias en Python, JavaScript y Java, con una referencia rápida de directivas por lenguaje.
Visión general
El formateo de fechas convierte un Date, datetime o Instant en un string legible; el parseo hace lo contrario. Las dos parecen triviales y las dos fallan en los mismos puntos: los formatos locales ambiguos (02/03/2026 no es el mismo día en EE. UU. y en Europa) y el manejo de zonas horarias.
La regla que ahorra la mayoría de los bugs en producción es simple: guarda y transmite fechas como instantes UTC (ISO 8601 o epoch), y pasa al locale y a la zona del usuario solo en la capa de presentación.
Cuándo usarla
Usa esta receta cuando necesites:
- Serializar fechas a JSON o XML para una API, donde ISO 8601 es el estándar de facto.
- Renderizar fechas en una interfaz para que cada lector vea su formato:
10/22/2026para usuarios de EE. UU.,22/10/2026para los de España. - Analizar fechas introducidas por usuarios o cargadas desde archivos. Consulta Validación de Datos para sanitizar esa entrada.
- Convertir un instante entre zonas horarias, por ejemplo mostrar la hora de un despliegue en la zona de cada miembro del equipo.
- Escribir timestamps precisos en logs y registros de auditoría. Consulta Logging para patrones estructurados.
Evita el formateo manual cuando necesites tiempos relativos (“hace 3 horas”) o aritmética de calendario (el próximo día laborable, cada dos martes). Las librerías dedicadas lo resuelven mejor; consulta Variantes.
Solución
Python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
# Instante actual, almacenado en UTC
now_utc = datetime.now(timezone.utc)
print(now_utc.isoformat()) # 2026-06-10T14:30:00+00:00
# Formatear para mostrar
print(now_utc.strftime("%Y-%m-%d %H:%M:%S")) # 2026-06-10 14:30:00
print(now_utc.strftime("%A, %B %d, %Y")) # Wednesday, June 10, 2026
# Parsear un string ISO 8601 y convertir de zona
dt = datetime.fromisoformat("2026-06-10T14:30:00+00:00")
madrid = dt.astimezone(ZoneInfo("Europe/Madrid"))
print(madrid.strftime("%Y-%m-%d %H:%M %Z")) # 2026-06-10 16:30 CEST
# Parsear un formato local de forma explícita
dt2 = datetime.strptime("10/06/2026", "%d/%m/%Y") # 10 de junio, no 6 de octubre
JavaScript
const now = new Date();
// ISO 8601, siempre UTC
console.log(now.toISOString()); // 2026-06-10T14:30:00.000Z
// Formateo según locale
console.log(now.toLocaleString('en-US', {
dateStyle: 'full',
timeStyle: 'short',
timeZone: 'America/New_York',
})); // Wednesday, June 10, 2026 at 10:30 AM
// Intl.DateTimeFormat, creado una vez y reutilizado
const fmt = new Intl.DateTimeFormat('de-DE', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit',
timeZone: 'Europe/Berlin',
});
console.log(fmt.format(now)); // 10.06.2026, 16:30
// Parsear un string ISO
const parsed = new Date('2026-06-10T14:30:00Z');
console.log(parsed.toISOString());
Java
import java.time.*;
import java.time.format.DateTimeFormatter;
ZonedDateTime nowUtc = ZonedDateTime.now(ZoneOffset.UTC);
System.out.println(nowUtc.format(DateTimeFormatter.ISO_INSTANT));
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
System.out.println(nowUtc.format(formatter)); // 2026-06-10 14:30:00 UTC
ZonedDateTime parsed = ZonedDateTime.parse("2026-06-10T14:30:00Z");
ZonedDateTime tokyo = parsed.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyo.format(formatter)); // 2026-06-10 23:30:00 JST
Cómo funciona
Un instante es un punto fijo en la línea temporal global. La hora local es otra cosa: es lo que muestra un reloj en una zona concreta, y se mueve con el horario de verano. 2026-06-10T14:30:00Z nombra un instante sin ambigüedad; 14:30 a secas puede ser cualquier momento del día.
La otra distinción que confunde es offset frente a zona horaria. +02:00 solo es una distancia fija respecto a UTC, mientras que Europe/Madrid es una zona con nombre en la base de datos IANA, con reglas propias que incluyen el horario de verano: UTC+1 en invierno y UTC+2 en verano. Si congelas el offset en tus datos, vas desfasado una hora durante medio año.
El horario de verano es donde están las trampas. En el cambio de primavera una hora local se salta y 02:30 nunca llega a ocurrir. En el de otoño una hora local ocurre dos veces, así que 01:30 no puede nombrar un solo instante. Trabajar en UTC esquiva ambos problemas.
Chuleta de patrones de formato
Las letras de patrón no son portables entre lenguajes, y las mayúsculas significan cosas distintas en cada uno. En Java, MM es el mes y mm, los minutos; Python usa la misma letra con otra convención: %m para el mes y %M para los minutos. Confundirlas produce salidas incorrectas sin ningún error.
Python strftime / strptime
| Directiva | Significado | Ejemplo |
|---|---|---|
%Y | Año de 4 dígitos | 2026 |
%m | Mes (01-12) | 06 |
%d | Día del mes (01-31) | 10 |
%A | Nombre completo del día de la semana | Wednesday |
%B | Nombre completo del mes | June |
%H | Hora 24h (00-23) | 14 |
%I | Hora 12h (01-12) | 02 |
%M | Minuto | 30 |
%S | Segundo | 00 |
%p | AM o PM | PM |
%z | Offset UTC | +0000 |
%Z | Nombre de la zona horaria | UTC |
%j | Día del año | 161 |
Java DateTimeFormatter
| Patrón | Significado | Ejemplo |
|---|---|---|
yyyy | Año | 2026 |
MM | Número de mes | 06 |
MMM | Nombre corto del mes | Jun |
MMMM | Nombre completo del mes | June |
dd | Día del mes | 10 |
EEEE | Día de la semana completo | Wednesday |
HH | Hora 0-23 | 14 |
mm | Minuto | 30 |
ss | Segundo | 00 |
XXX | Offset ISO 8601 | Z o +02:00 |
VV | ID de zona horaria IANA | Europe/Madrid |
z | Nombre corto de la zona | CEST |
JavaScript Intl.DateTimeFormat
| Opción | Valores | Salida de ejemplo |
|---|---|---|
dateStyle | full, long, medium, short | Wednesday, June 10, 2026 |
timeStyle | full, long, medium, short | 2:30 PM |
year | numeric, 2-digit | 2026 o 26 |
month | numeric, 2-digit, long, short, narrow | 6, 06, June, Jun, J |
day | numeric, 2-digit | 10 |
weekday | long, short, narrow | Wednesday, Wed, W |
hour, minute, second | numeric, 2-digit | 14, 30, 00 |
hour12 | true, false | 2:30 PM vs 14:30 |
timeZone | ID IANA | Europe/Madrid |
timeZoneName | short, long | CEST |
La misma salida en cada lenguaje
| Salida | Python | Java | JavaScript |
|---|---|---|---|
2026-06-10 | %Y-%m-%d | yyyy-MM-dd | dateStyle: 'short' (depende del locale) |
2026-06-10T14:30:00Z | isoformat() | ISO_INSTANT | toISOString() |
Wednesday, June 10, 2026 | %A, %B %d, %Y | EEEE, MMMM d, yyyy | dateStyle: 'full' en en-US |
14:30 | %H:%M | HH:mm | timeStyle: 'short' en en-GB |
2:30 PM | %I:%M %p | h:mm a | hour12: true en en-US |
Variantes y alternativas
-
Python: la stdlib
datetimejunto azoneinfo(Python 3.9+) cubre la mayoría de los casos.python-dateutilmaneja parseo difuso como “next Friday”. Arrow y Pendulum envuelven el mismo motor con una API más amigable. -
JavaScript:
Intl.DateTimeFormatviene con cada runtime moderno, cuesta 0 KB y conoce todas las zonas IANA. Usadate-fnsoDay.jscuando necesites parseo y aritmética, yLuxoncuando la app dependa mucho de las zonas horarias. Evitamomenten código nuevo: es mutable, pesa ~70 KB minificado y está en modo mantenimiento. -
Java: siempre
java.time.java.util.DateySimpleDateFormatson mutables y no son thread-safe. -
Nota de rendimiento: construir un formateador es la parte costosa. Las instancias de
DateTimeFormatterson inmutables y thread-safe, así que almacénalas en caché y reutilízalas. Lo mismo aplica aIntl.DateTimeFormatdentro de un bucle. -
Companion ejecutable: tres scripts funcionales (Python, JavaScript, Java) en el repo companion con los ejemplos de esta página.
Buenas prácticas
- Almacena cada fecha como instante UTC y convierte a hora local solo al renderizar para el usuario.
- Al transmitir por la red, envía siempre ISO 8601 en lugar de un formato propio del locale. Un valor como
2026-06-10T14:30:00Zes el mismo instante para un backend en Python, una API en Node y un servicio en Java. - Envía el offset o la zona horaria en las respuestas de API para que el cliente muestre la hora local correcta.
- Prefiere IDs IANA (
America/New_York) frente a abreviaturas (EST), que ignoran el horario de verano y son ambiguas (CSTnombra tres zonas distintas). - Nunca construyas ni parsees fechas concatenando strings o con expresiones regulares; usa el parser de la plataforma.
Errores comunes
- Almacenar hora local sin zona: una fila
2026-06-10 14:30en la base de datos queda irreconstruible en cuanto un servidor o un usuario cambia de zona. - Parsear strings no ISO con
Date.parse(): cada navegador interpreta10/06/2026a su manera; pasa siempre un formato explícito o una librería de parseo. - Confundir
MMconmm: mes frente a minutos es el bug silencioso más común en los patrones deDateTimeFormatter. - Formatear en la zona del servidor: la API contesta en la zona donde corre el servidor y el usuario suele estar en otra. Formatea en la capa de presentación.
- Asumir que toda hora local existe: una tarea programada a las
02:30no se ejecuta los días del cambio de primavera en las zonas con horario de verano.
Lectura adicional
-
RFC 3339 — Date and Time on the Internet — el perfil ISO 8601 que usa el formato en la red
-
Docs de Python: códigos de formato de strftime() y strptime()
Preguntas frecuentes
¿Debería almacenar timestamps o strings formateados en la base de datos?
Guarda instantes en una columna nativa TIMESTAMP WITH TIME ZONE (o segundos epoch), nunca strings formateados. Los strings pierden el instante y ordenan mal entre zonas. Consulta Transacciones de Base de Datos para patrones de integridad relacionados.
¿Cómo manejo el horario de verano?
Mantén todos los valores en UTC internamente y convierte con IDs IANA (Europe/Madrid) al mostrar. Guardar +02:00 en datos o en código se rompe en cuanto cambia el horario de verano.
¿Cuál es la diferencia entre una zona horaria y un offset UTC?
+02:00 es un offset: una distancia fija respecto a UTC que nunca cambia. Europe/Madrid es una zona horaria: una región con nombre cuyo offset salta entre +1 y +2 cuando entra el horario de verano. Almacena instantes y formatea con zonas horarias, no con offsets sueltos.
¿Qué diferencia hay entre toISOString() y toUTCString()?
Las APIs y las bases de datos esperan ISO 8601, y toISOString() produce exactamente eso (2026-06-10T14:30:00.000Z). toUTCString() devuelve el string antiguo RFC 7231 (Wed, 10 Jun 2026 14:30:00 GMT) que aparece en las cabeceras HTTP.
¿Cómo formateo una fecha en el locale del usuario sin una librería?
new Intl.DateTimeFormat(undefined, { dateStyle: 'long' }).format(date) toma el locale del runtime automáticamente. Si el usuario eligió una zona en la configuración, pásala como timeZone explícito.
Recursos Relacionados
Parsear JSON en Python, JavaScript y Java
Parsea cadenas JSON a objetos nativos en Python, JavaScript y Java — con manejo de errores, streaming para archivos grandes y validación de esquema.
RecipeLlamar a una API REST: Python, JS, Java y Go
Cómo hacer peticiones HTTP a una API REST y manejar la respuesta JSON en Python, JavaScript, Java y Go.
RecipeValidar y Sanitizar Datos de Input de Usuario
Cómo validar, sanitizar y restringir datos de input de usuario en el boundary de aplicación usando schemas, type checking y librerías de validación.
RecipeCron: programar tareas en Linux, Python y Node.js
Cómo programar y gestionar tareas recurrentes usando sintaxis cron en Linux, Python, Node.js y Kubernetes.
RecipePatrones de Procesamiento por Lotes
Diseña pipelines robustos de procesamiento por lotes para grandes datasets con retry, idempotencia y observabilidad.
GuideGuía TSDB: InfluxDB, TimescaleDB y ClickHouse
Guía práctica de TSDB: cuándo usar una base de datos de series temporales, modelo de datos, retención y cómo elegir entre InfluxDB, TimescaleDB y ClickHouse.