La función DATE_FORMAT() convierte un valor de fecha o datetime en una cadena de texto con el formato que tú especifiques. Es una de las funciones más utilizadas de MySQL porque permite transformar el formato estándar YYYY-MM-DD HH:MM:SS en cualquier representación legible que tu aplicación o reporte necesite, desde 15/07/2025 hasta martes, 15 de julio de 2025.
A diferencia de las funciones de extracción como YEAR(), MONTH() o DAY() que devuelven componentes individuales, DATE_FORMAT() produce una cadena completa formateada exactamente como la defines, lo que evita tener que concatenar múltiples funciones para lograr el formato deseado.
Sintaxis
DATE_FORMAT(fecha, formato)El primer argumento es el valor de fecha, datetime o timestamp que deseas formatear. El segundo argumento es una cadena de formato que contiene especificadores precedidos por el carácter %. Todo texto en la cadena de formato que no sea un especificador se incluye literalmente en el resultado.
Especificadores de formato
MySQL ofrece una amplia variedad de especificadores. Estos son los más utilizados, organizados por categoría.
Componentes de fecha:
| Especificador | Descripción | Ejemplo |
|---|---|---|
%Y | Año con 4 dígitos | 2025 |
%y | Año con 2 dígitos | 25 |
%m | Mes con cero inicial (01-12) | 07 |
%c | Mes sin cero inicial (1-12) | 7 |
%M | Nombre completo del mes | July |
%b | Nombre abreviado del mes | Jul |
%d | Día con cero inicial (01-31) | 15 |
%e | Día sin cero inicial (1-31) | 15 |
%D | Día con sufijo ordinal inglés | 15th |
Componentes de hora:
| Especificador | Descripción | Ejemplo |
|---|---|---|
%H | Hora 24h con cero inicial (00-23) | 14 |
%k | Hora 24h sin cero inicial (0-23) | 14 |
%h o %I | Hora 12h con cero inicial (01-12) | 02 |
%l | Hora 12h sin cero inicial (1-12) | 2 |
%i | Minutos con cero inicial (00-59) | 32 |
%s o %S | Segundos con cero inicial (00-59) | 08 |
%f | Microsegundos (000000-999999) | 451293 |
%p | AM o PM | PM |
Formatos compuestos y otros:
| Especificador | Descripción | Ejemplo |
|---|---|---|
%r | Hora en formato 12h completo | 02:32:08 PM |
%T | Hora en formato 24h completo | 14:32:08 |
%W | Nombre completo del día de la semana | Tuesday |
%a | Nombre abreviado del día | Tue |
%w | Número del día de la semana (0=domingo) | 2 |
%j | Día del año (001-366) | 196 |
%U | Semana del año (domingo como primer día) | 28 |
%u | Semana del año (lunes como primer día) | 28 |
%% | Literal % | % |
Comportamiento básico
La forma más directa de usar DATE_FORMAT() es aplicar los especificadores para reorganizar la fecha en el orden y formato deseado.
SELECT DATE_FORMAT('2025-07-15 14:32:08', '%d/%m/%Y') AS formato_europeo;| formato_europeo |
|---|
| 15/07/2025 |
Puedes incluir texto literal entre los especificadores. MySQL lo reproduce tal cual en el resultado.
SELECT DATE_FORMAT('2025-07-15 14:32:08', '%d de %M de %Y') AS formato_largo;| formato_largo |
|---|
| 15 de July de 2025 |
Para incluir tanto fecha como hora, combina los especificadores de ambos grupos.
SELECT DATE_FORMAT('2025-07-15 14:32:08', '%d/%m/%Y %H:%i:%s') AS fecha_hora_es;| fecha_hora_es |
|---|
| 15/07/2025 14:32:08 |
El formato de 12 horas con AM/PM se logra usando %h (o %I) junto con %p.
SELECT DATE_FORMAT('2025-07-15 14:32:08', '%d/%m/%Y %h:%i %p') AS formato_12h;| formato_12h |
|---|
| 15/07/2025 02:32 PM |
También puedes usar los formatos compuestos %r y %T para obtener la hora completa de un solo especificador.
SELECT DATE_FORMAT('2025-07-15 14:32:08', '%r') AS formato_r,
DATE_FORMAT('2025-07-15 14:32:08', '%T') AS formato_T;| formato_r | formato_T |
|---|---|
| 02:32:08 PM | 14:32:08 |
Consideraciones sobre el idioma español
Por defecto, MySQL devuelve los nombres de meses y días de la semana en inglés. Los especificadores %M, %W, %b y %a producen "July", "Tuesday", "Jul" y "Tue" respectivamente. Para obtener los nombres en español, necesitas cambiar la variable lc_time_names de la sesión.
SET lc_time_names = 'es_ES';
SELECT DATE_FORMAT('2025-07-15 14:32:08', '%W, %d de %M de %Y') AS fecha_espanol;| fecha_espanol |
|---|
| martes, 15 de julio de 2025 |
Con lc_time_names = 'es_ES', todos los nombres de meses y días se devuelven en español y en minúsculas, como es correcto gramaticalmente en español.
SET lc_time_names = 'es_ES';
SELECT DATE_FORMAT('2025-07-15', '%W') AS dia,
DATE_FORMAT('2025-07-15', '%M') AS mes,
DATE_FORMAT('2025-07-15', '%a') AS dia_corto,
DATE_FORMAT('2025-07-15', '%b') AS mes_corto;| dia | mes | dia_corto | mes_corto |
|---|---|---|---|
| martes | julio | mar | jul |
Esta configuración afecta solo a la sesión actual. Si necesitas que se aplique a todas las conexiones, inclúyela en el archivo de configuración del servidor o establécela en tu aplicación al abrir cada conexión.
Caso práctico: formatear fechas para reportes
En reportes de negocio, las fechas deben ser legibles para personas no técnicas. DATE_FORMAT() permite generar columnas con formatos amigables directamente desde la consulta.
SET lc_time_names = 'es_ES';
SELECT p.id,
c.nombre AS cliente,
p.total,
DATE_FORMAT(p.fecha_pedido, '%d de %M de %Y') AS fecha,
DATE_FORMAT(p.fecha_pedido, '%H:%i') AS hora
FROM pedidos p
JOIN clientes c ON c.id = p.cliente_id
WHERE p.fecha_pedido >= '2025-07-14'
ORDER BY p.fecha_pedido;| id | cliente | total | fecha | hora |
|---|---|---|---|---|
| 85 | Laura Vega | 132.00 | 14 de julio de 2025 | 09:15 |
| 86 | Roberto Díaz | 259.90 | 15 de julio de 2025 | 14:32 |
| 87 | Sofía Morales | 84.50 | 15 de julio de 2025 | 14:32 |
Para generar encabezados de reporte con el periodo formateado, puedes usar DATE_FORMAT() sobre las fechas de inicio y fin.
SET lc_time_names = 'es_ES';
SELECT CONCAT(
'Reporte del ',
DATE_FORMAT('2025-07-01', '%d de %M'),
' al ',
DATE_FORMAT('2025-07-31', '%d de %M de %Y')
) AS titulo_reporte;| titulo_reporte |
|---|
| Reporte del 01 de julio al 31 de julio de 2025 |
Caso práctico: agrupar por periodos formateados
DATE_FORMAT() es extremadamente útil para crear agrupaciones legibles por mes, semana o trimestre.
SELECT DATE_FORMAT(fecha_pedido, '%Y-%m') AS mes,
COUNT(*) AS pedidos,
SUM(total) AS ingresos
FROM pedidos
WHERE fecha_pedido >= '2025-01-01'
GROUP BY DATE_FORMAT(fecha_pedido, '%Y-%m')
ORDER BY mes;| mes | pedidos | ingresos |
|---|---|---|
| 2025-01 | 142 | 18540.50 |
| 2025-02 | 128 | 16720.30 |
| 2025-03 | 156 | 20380.00 |
| 2025-04 | 135 | 17640.75 |
| 2025-05 | 161 | 21020.20 |
| 2025-06 | 148 | 19320.60 |
| 2025-07 | 87 | 11350.40 |
Para nombres de mes legibles en español, combina con la configuración de locale.
SET lc_time_names = 'es_ES';
SELECT DATE_FORMAT(fecha_pedido, '%M %Y') AS mes,
COUNT(*) AS pedidos,
FORMAT(SUM(total), 2) AS ingresos
FROM pedidos
WHERE fecha_pedido >= '2025-04-01'
GROUP BY DATE_FORMAT(fecha_pedido, '%Y-%m'),
DATE_FORMAT(fecha_pedido, '%M %Y')
ORDER BY DATE_FORMAT(fecha_pedido, '%Y-%m');| mes | pedidos | ingresos |
|---|---|---|
| abril 2025 | 135 | 17,640.75 |
| mayo 2025 | 161 | 21,020.20 |
| junio 2025 | 148 | 19,320.60 |
| julio 2025 | 87 | 11,350.40 |
Observa que en el GROUP BY usamos el formato %Y-%m para garantizar el orden correcto, ya que ordenar alfabéticamente por nombre de mes produciría un orden incorrecto.
Caso práctico: formatos para exportación y APIs
Cuando exportas datos para consumo por otros sistemas, DATE_FORMAT() te permite generar el formato exacto que el sistema receptor espera.
-- Formato ISO 8601 (estándar para APIs REST)
SELECT DATE_FORMAT(NOW(), '%Y-%m-%dT%H:%i:%s') AS iso_8601;
-- Formato para archivos con fecha en el nombre
SELECT DATE_FORMAT(NOW(), '%Y%m%d_%H%i%s') AS nombre_archivo;
-- Formato RFC 2822 (correos electrónicos)
SELECT DATE_FORMAT(NOW(), '%a, %d %b %Y %H:%i:%s') AS rfc_2822;| iso_8601 | nombre_archivo | rfc_2822 |
|---|---|---|
| 2025-07-15T14:32:08 | 20250715_143208 | Tue, 15 Jul 2025 14:32:08 |
Manejo de NULL
Si cualquiera de los dos argumentos de DATE_FORMAT() es NULL, el resultado es NULL.
SELECT DATE_FORMAT(NULL, '%d/%m/%Y') AS fecha_nula,
DATE_FORMAT('2025-07-15', NULL) AS formato_nulo;| fecha_nula | formato_nulo |
|---|---|
| NULL | NULL |
Cuando la columna de fecha puede contener valores nulos, usa COALESCE o IFNULL para proporcionar un texto alternativo.
SELECT id,
COALESCE(
DATE_FORMAT(fecha_envio, '%d/%m/%Y %H:%i'),
'Pendiente de envío'
) AS estado_envio
FROM pedidos;| id | estado_envio |
|---|---|
| 86 | 15/07/2025 16:45 |
| 87 | Pendiente de envío |
Si la fecha contiene un valor inválido, DATE_FORMAT() devuelve NULL y genera una advertencia.
Combinación con otras funciones
DATE_FORMAT() se combina naturalmente con funciones que generan o manipulan fechas.
SET lc_time_names = 'es_ES';
SELECT DATE_FORMAT(NOW(), '%W %d de %M, %Y - %H:%i') AS ahora_formateado,
DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%M %Y') AS mes_anterior,
DATE_FORMAT(LAST_DAY(NOW()), '%d de %M') AS fin_de_mes;| ahora_formateado | mes_anterior | fin_de_mes |
|---|---|---|
| martes 15 de julio, 2025 - 14:32 | junio 2025 | 31 de julio |
Una combinación poderosa es usar DATE_FORMAT() junto con STR_TO_DATE() para convertir entre formatos. Mientras DATE_FORMAT() transforma una fecha en cadena, STR_TO_DATE() hace la operación inversa.
SET lc_time_names = 'es_ES';
-- Convertir fecha en formato español a formato MySQL
SELECT STR_TO_DATE('15/07/2025', '%d/%m/%Y') AS fecha_mysql;
-- Y volver al formato español
SELECT DATE_FORMAT(
STR_TO_DATE('15/07/2025', '%d/%m/%Y'),
'%d de %M de %Y'
) AS fecha_formateada;| fecha_mysql | fecha_formateada |
|---|---|
| 2025-07-15 | 15 de julio de 2025 |
Para aplicaciones web donde necesitas mostrar "hace cuánto tiempo" ocurrió un evento, puedes combinar DATE_FORMAT() con lógica condicional.
SELECT id, fecha_pedido,
CASE
WHEN DATE(fecha_pedido) = CURDATE() THEN
CONCAT('Hoy a las ', DATE_FORMAT(fecha_pedido, '%H:%i'))
WHEN DATE(fecha_pedido) = CURDATE() - INTERVAL 1 DAY THEN
CONCAT('Ayer a las ', DATE_FORMAT(fecha_pedido, '%H:%i'))
ELSE
DATE_FORMAT(fecha_pedido, '%d/%m/%Y')
END AS fecha_amigable
FROM pedidos
ORDER BY fecha_pedido DESC
LIMIT 4;| id | fecha_pedido | fecha_amigable |
|---|---|---|
| 87 | 2025-07-15 14:32:08 | Hoy a las 14:32 |
| 86 | 2025-07-15 14:32:08 | Hoy a las 14:32 |
| 85 | 2025-07-14 09:15:22 | Ayer a las 09:15 |
| 84 | 2025-07-12 18:40:55 | 12/07/2025 |
Recuerda que DATE_FORMAT() devuelve siempre una cadena de texto (VARCHAR), no un valor de fecha. Esto significa que si usas el resultado en comparaciones de orden, se ordenará alfabéticamente, no cronológicamente. Para mantener el orden temporal, ordena siempre por la columna de fecha original, no por la versión formateada.
En el siguiente artículo veremos TIME_FORMAT para dar formato personalizado a valores de hora.
Escrito por Eduardo Lázaro
