Fechas y horas en Bash: formatear, sumar días, diferencias y zonas horarias

Cómo trabajar con fechas en Bash usando date: darles formato, ponerlas en nombres de archivo, sumar y restar días, contar los días entre dos fechas, comprobar que una fecha es válida, cambiar de zona horaria y convertir marcas de tiempo Unix, con las diferencias que encontrarás en macOS.

Formatear una fecha con date

La orden date sin nada más escribe la fecha y la hora actuales. Con un + seguido de un formato decides qué partes salen y en qué orden, y con -d le das una fecha concreta en lugar de «ahora». En esta página usamos siempre -d con una fecha fija, para que la salida que ves sea la misma que obtendrás tú:

terminal$ date -d '2026-10-30 14:05:09' '+%Y-%m-%d %H:%M:%S'
2026-10-30 14:05:09
$ date -d '2026-10-30 14:05:09' +%F
2026-10-30
$ date -d '2026-10-30 14:05:09' '+%d/%m/%Y a las %H:%M'
30/10/2026 a las 14:05
$ date -d '2026-10-30 14:05:09' '+%A, %d de %B'
Friday, 30 de October
$ date -d '2026-10-30 14:05:09' '+semana %V, día %j del año'
semana 44, día 303 del año
$ date -d '2026-10-30 14:05:09' --iso-8601=seconds
2026-10-30T14:05:09+00:00
  • FormatoQué escribeEjemplo
  • %Y %m %dAño, mes y día con ceros2026, 10, 30
  • %H %M %SHora (00-23), minutos y segundos14, 05, 09
  • %FLo mismo que %Y-%m-%d (ISO 8601)2026-10-30
  • %TLo mismo que %H:%M:%S14:05:09
  • %A %BDía de la semana y mes, en el idioma del sistemaFriday, October
  • %sSegundos desde el 1 de enero de 1970 (marca de tiempo Unix)1793369109
  • %Z %zZona horaria, con nombre y como desfaseUTC, +0000

El día y el mes salen en inglés porque el sistema de las pruebas usa la configuración regional C.UTF-8. Para que salgan en español hace falta tener generada la configuración es_ES.UTF-8 (o la de tu país, como es_MX.UTF-8) y poner LC_TIME=es_ES.UTF-8 delante de la orden. Con locale -a ves las que tienes instaladas.

Fechas en los nombres de archivo

El uso más habitual de date en un script es poner la fecha en el nombre de un archivo: un registro por día, una copia de seguridad por fecha. El formato %F (año-mes-día) tiene una ventaja que no hay que despreciar: los nombres se ordenan solos en orden cronológico.

#!/usr/bin/env bash
# En un script real sería: hoy=$(date +%F) y ahora=$(date +%Y%m%d-%H%M%S)
hoy=$(date -d '2026-10-30 14:05:09' +%F)
ahora=$(date -d '2026-10-30 14:05:09' +%Y%m%d-%H%M%S)

echo "registro-$hoy.log"
echo "copia-$ahora.tar.gz"

# Con %F, el orden alfabético es el orden de las fechas
printf '%s\n' informe-2026-10-30.txt informe-2025-12-01.txt informe-2026-02-14.txt | sort
salidaregistro-2026-10-30.log
copia-20261030-140509.tar.gz
informe-2025-12-01.txt
informe-2026-02-14.txt
informe-2026-10-30.txt
Evita %d-%m-%Y en los nombres de archivo. Con el día delante, 01-12-2025 se ordena antes que 30-10-2026 y el listado deja de estar en orden. Y nunca uses / en un nombre: para Linux es el separador de carpetas.

Sumar y restar días, semanas y meses

El date de GNU entiende expresiones en inglés como +7 days, -2 weeks, next friday o yesterday, relativas a la fecha que le das con -d:

terminal$ date -d '2026-10-30 +7 days' +%F
2026-11-06
$ date -d '2026-10-30 -2 weeks' +%F
2026-10-16
$ date -d '2026-10-30 +1 year' +%F
2027-10-30
$ date -d '2026-10-30 next friday' +%F
2026-11-06
$ date -d '2026-10-30 yesterday' +%F
2026-10-29
$ date -d '2026-01-31 +1 month' +%F
2026-03-03

Fíjate en la última línea: el 31 de enero más un mes no es el 28 de febrero, sino el 3 de marzo. GNU date suma uno al mes (31 de febrero, que no existe) y lo desborda hacia marzo. Si necesitas «el último día del mes siguiente», calcula desde el día 1:

#!/usr/bin/env bash
# Último día del mes siguiente a una fecha: día 1 de dentro de dos meses, menos un día
fecha=2026-01-31
primero=$(date -d "$fecha" +%Y-%m-01)
date -d "$primero +2 months -1 day" +%F
salida2026-02-28

Cuántos días hay entre dos fechas

Para restar fechas, conviértelas a segundos con %s, réstalas con la aritmética de Bash y divide entre 86,400, los segundos de un día. Hazlo siempre en UTC (-u): si una de las fechas cae en el día del cambio de hora, en hora local ese día dura 23 o 25 horas y la división da un número con decimales que Bash trunca.

#!/usr/bin/env bash
dias_entre() {
    local desde hasta
    desde=$(date -u -d "$1" +%s) || return 1
    hasta=$(date -u -d "$2" +%s) || return 1
    echo $(( (hasta - desde) / 86400 ))
}

echo "Días hasta fin de año: $(dias_entre 2026-10-30 2026-12-31)"
echo "Días del año 2028: $(dias_entre 2028-01-01 2029-01-01)"
echo "Días desde una fecha pasada: $(dias_entre 2026-10-30 2026-01-01)"
salidaDías hasta fin de año: 62
Días del año 2028: 366
Días desde una fecha pasada: -302

Comprobar que una fecha es válida

date -d acaba con código de salida 1 si no entiende la fecha, así que sirve para validar lo que escribe el usuario. Pero es muy permisivo: también acepta cosas como tomorrow o 1 week ago. Si esperas un formato concreto, compruébalo antes con una expresión regular:

#!/usr/bin/env bash
fecha_valida() {
    [[ $1 =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || return 1
    # Además de la forma, el día tiene que existir (y date no debe «corregirlo»)
    [[ $(date -d "$1" +%F 2>/dev/null) == "$1" ]]
}

for f in 2026-10-30 2026-02-30 2028-02-29 30/10/2026 tomorrow; do
    if fecha_valida "$f"; then
        echo "$f: válida"
    else
        echo "$f: no válida"
    fi
done

date -d 2026-02-30 +%F
echo "código de salida de date: $?"
salida2026-10-30: válida
2026-02-30: no válida
2028-02-29: válida
30/10/2026: no válida
tomorrow: no válida
date: invalid date '2026-02-30'
código de salida de date: 1

Zonas horarias

La variable TZ elige la zona horaria de una orden. Los nombres son los de la base de datos de zonas de IANA, con el formato Continente/Ciudad. Si escribes la hora de partida con UTC al final, date la convierte a la zona que pidas:

#!/usr/bin/env bash
reunion='2026-10-30 18:00 UTC'
for zona in UTC America/Mexico_City America/Bogota America/Argentina/Buenos_Aires Europe/Madrid; do
    printf '%-20s %s\n' "$zona" "$(TZ=$zona date -d "$reunion" '+%F %H:%M %Z')"
done

echo
echo "El cambio de hora en España (25 de octubre de 2026):"
TZ=Europe/Madrid date -d '2026-10-24 12:00 UTC' '+%F %H:%M %Z (UTC%z)'
TZ=Europe/Madrid date -d '2026-10-26 12:00 UTC' '+%F %H:%M %Z (UTC%z)'

echo
echo "Una zona mal escrita no da error: se queda en UTC"
TZ=Europe/Madird date -d '2026-10-30 18:00 UTC' '+%F %H:%M %Z'
TZ=America/Buenos_Aires date -d '2026-10-30 18:00 UTC' '+%F %H:%M %Z'
salidaUTC                  2026-10-30 18:00 UTC
America/Mexico_City  2026-10-30 12:00 CST
America/Bogota       2026-10-30 13:00 -05
America/Argentina/Buenos_Aires 2026-10-30 15:00 -03
Europe/Madrid        2026-10-30 19:00 CET

El cambio de hora en España (25 de octubre de 2026):
2026-10-24 14:00 CEST (UTC+0200)
2026-10-26 13:00 CET (UTC+0100)

Una zona mal escrita no da error: se queda en UTC
2026-10-30 18:00 UTC
2026-10-30 18:00 UTC

México dejó de cambiar la hora en 2022, así que Ciudad de México está todo el año en UTC-6. Bogotá aparece como -05 porque la base de datos de zonas ya no inventa abreviaturas para los países que no las usan. España, en cambio, pasó el 25 de octubre de 2026 del horario de verano (CEST, UTC+2) al de invierno (CET, UTC+1). Usa siempre los nombres de ciudad y no abreviaturas como CST: la misma abreviatura significa cosas distintas en países distintos.

Un nombre de zona que no existe no da ningún error: date calla y usa UTC, como se ve en las dos últimas líneas. Europe/Madird tiene una errata, y America/Buenos_Aires es un nombre antiguo que Ubuntu 26.04 ya no instala de serie (está en el paquete tzdata-legacy); el actual es America/Argentina/Buenos_Aires. Antes de fiarte del resultado, comprueba que la zona existe en /usr/share/zoneinfo/.

Marcas de tiempo Unix

Una marca de tiempo Unix es el número de segundos desde el 1 de enero de 1970 a medianoche en UTC. Es lo que guardan muchos registros y bases de datos. Para pasar de una a otra:

#!/usr/bin/env bash
# De fecha a marca de tiempo (en un script real: date +%s, o la variable $EPOCHSECONDS)
date -u -d '2026-10-30 14:05:09' +%s

# De marca de tiempo a fecha: con @ delante
date -u -d @1793369109 '+%F %T %Z'

# Sin llamar a date: printf de Bash tiene el formato %(...)T
TZ=UTC printf '%(%F %T)T\n' 1793369109
salida1793369109
2026-10-30 14:05:09 UTC
2026-10-30 14:05:09

Desde Bash 5.0 tienes además la variable $EPOCHSECONDS (la marca de tiempo actual) y $EPOCHREALTIME (con microsegundos), que no lanzan ningún proceso: útiles en bucles que miden tiempos. Para convertir marcas sin abrir una terminal, en decodigo.com está el conversor de fecha Unix.

En macOS, date es distinto

macOS trae el date de BSD, que no tiene -d. Allí se usa -v para sumar y -j -f para leer una fecha con un formato:

  • Qué quieresLinux (GNU date)macOS (BSD date)
  • Una fecha concretadate -d '2026-10-30' +%Fdate -j -f '%Y-%m-%d' '2026-10-30' +%F
  • Sumar 7 díasdate -d '2026-10-30 +7 days' +%Fdate -j -v+7d -f '%Y-%m-%d' '2026-10-30' +%F
  • De marca de tiempo a fechadate -d @1793369109date -r 1793369109

Si tu script tiene que funcionar en los dos, instala en el Mac las herramientas de GNU con Homebrew (brew install coreutils, que da la orden gdate) o usa printf '%(…)T' de Bash para formatear, que funciona igual en todas partes.

Todos los scripts de esta página se ejecutaron con Bash 5.3 en Ubuntu 26.04, en un contenedor limpio sin acceso a la red, y pasaron ShellCheck 0.11 sin avisos antes de publicarla; las salidas están copiadas de esa ejecución.