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:

EspecificadorDescripciónEjemplo
%YAño con 4 dígitos2025
%yAño con 2 dígitos25
%mMes con cero inicial (01-12)07
%cMes sin cero inicial (1-12)7
%MNombre completo del mesJuly
%bNombre abreviado del mesJul
%dDía con cero inicial (01-31)15
%eDía sin cero inicial (1-31)15
%DDía con sufijo ordinal inglés15th

Componentes de hora:

EspecificadorDescripciónEjemplo
%HHora 24h con cero inicial (00-23)14
%kHora 24h sin cero inicial (0-23)14
%h o %IHora 12h con cero inicial (01-12)02
%lHora 12h sin cero inicial (1-12)2
%iMinutos con cero inicial (00-59)32
%s o %SSegundos con cero inicial (00-59)08
%fMicrosegundos (000000-999999)451293
%pAM o PMPM

Formatos compuestos y otros:

EspecificadorDescripciónEjemplo
%rHora en formato 12h completo02:32:08 PM
%THora en formato 24h completo14:32:08
%WNombre completo del día de la semanaTuesday
%aNombre abreviado del díaTue
%wNúmero del día de la semana (0=domingo)2
%jDía del año (001-366)196
%USemana del año (domingo como primer día)28
%uSemana 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_rformato_T
02:32:08 PM14: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;
diamesdia_cortomes_corto
martesjuliomarjul

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;
idclientetotalfechahora
85Laura Vega132.0014 de julio de 202509:15
86Roberto Díaz259.9015 de julio de 202514:32
87Sofía Morales84.5015 de julio de 202514: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;
mespedidosingresos
2025-0114218540.50
2025-0212816720.30
2025-0315620380.00
2025-0413517640.75
2025-0516121020.20
2025-0614819320.60
2025-078711350.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');
mespedidosingresos
abril 202513517,640.75
mayo 202516121,020.20
junio 202514819,320.60
julio 20258711,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_8601nombre_archivorfc_2822
2025-07-15T14:32:0820250715_143208Tue, 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_nulaformato_nulo
NULLNULL

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;
idestado_envio
8615/07/2025 16:45
87Pendiente 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_formateadomes_anteriorfin_de_mes
martes 15 de julio, 2025 - 14:32junio 202531 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_mysqlfecha_formateada
2025-07-1515 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;
idfecha_pedidofecha_amigable
872025-07-15 14:32:08Hoy a las 14:32
862025-07-15 14:32:08Hoy a las 14:32
852025-07-14 09:15:22Ayer a las 09:15
842025-07-12 18:40:5512/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