wandres.dev
CRON TRIGGERS · workers programados

Expresiones cron: el vocabulario del tiempo

Cinco campos separados por espacios bastan para expresar casi cualquier periodicidad. Cómo leer y escribir una expresión cron, dónde declararla en triggers.crons de wrangler.jsonc, y la trampa que atrapa a todos: los cron triggers corren siempre en UTC.

⏱ 14 min

Una expresión cron es un mini-lenguaje de cinco campos para responder a una sola pregunta: ¿cuándo? Aprende a leer */5 * * * * de un vistazo y tendrás el vocabulario con el que se programa cualquier tarea en el edge. Pero hay una trampa que atrapa a todo el mundo la primera vez, y no está en la sintaxis: el reloj que interpreta esos campos no es el tuyo, es el de UTC.

🎯 Al terminar esta lección sabrás
  • Leer y escribir los cinco campos de una expresión cron: minuto, hora, día del mes, mes y día de la semana.
  • Declarar los horarios en el array triggers.crons de wrangler.jsonc.
  • Usar comodines, pasos, rangos y listas para expresar periodicidades reales.
  • Entender que los cron triggers corren siempre en UTC y planificar en consecuencia.

Los cinco campos

Una expresión cron son cinco campos separados por espacios. De izquierda a derecha: minuto, hora, día del mes, mes y día de la semana. Un asterisco significa “cualquier valor”.

┌───────────── minuto        (0 - 59)
│ ┌─────────── hora          (0 - 23)
│ │ ┌───────── dia del mes   (1 - 31)
│ │ │ ┌─────── mes           (1 - 12)
│ │ │ │ ┌───── dia de semana (0 - 7, 0 y 7 = domingo)
│ │ │ │ │
* * * * *

Cada campo admite algo más que un número o un asterisco:

  • * — todos los valores del campo.
  • */n — pasos: uno de cada n (por ejemplo */15 en el minuto es cada cuarto de hora).
  • a-b — un rango, como 1-5.
  • a,b,c — una lista de valores sueltos.
  • Nombres y extensiones: mon-fri, sun, y L (último) o LW (último día hábil del mes).

Con ese vocabulario, la mayoría de los horarios reales caben en una sola línea:

* * * * *        cada minuto
*/5 * * * *      cada 5 minutos
0 * * * *        al inicio de cada hora
30 8 * * *       a las 08:30 UTC todos los dias
0 9 * * mon-fri  a las 09:00 UTC de lunes a viernes
0 0 1 * *        medianoche UTC del dia 1 de cada mes
59 23 LW * *     a las 23:59 UTC del ultimo dia habil del mes
ℹ️
La granularidad mínima es un minuto

El campo más fino de una expresión cron es el minuto: no existe el “cada 10 segundos”. Si necesitas una cadencia por debajo del minuto, el cron no es la herramienta —lo son las alarms de un Durable Object, que veremos en el bloque de almacenamiento—. El cron cubre desde “cada minuto” hasta “una vez al año”, pero nada por debajo del minuto.

Definirlas en wrangler.jsonc

Los horarios viven en el manifiesto, bajo triggers.crons: un array de cadenas donde cada cadena es un trigger independiente, y todos invocan el mismo Worker.

{
  "name": "mi-worker",
  "main": "src/index.ts",
  "triggers": {
    "crons": [
      "*/5 * * * *", // sincroniza cada 5 minutos
      "0 0 * * *",   // limpieza a medianoche UTC
      "0 8 * * mon"  // informe los lunes a las 08:00 UTC
    ]
  }
}

Al hacer wrangler deploy, Wrangler registra estos horarios en la plataforma. Ten en cuenta que los cambios en triggers tardan en propagarse por la red global: añadir, modificar o borrar un cron puede llevar varios minutos —hasta unos quince— en surtir efecto. Para desactivar todos los horarios, deja el array vacío ("crons": []) y vuelve a desplegar.

💡
Comenta cada cron

wrangler.jsonc admite comentarios precisamente por casos como este. Una expresión como 59 23 LW * * es críptica para quien la lea dentro de seis meses —empezando por ti—. Un comentario al lado que diga “23:59 del último día hábil, para el cierre mensual” convierte una línea ilegible en una intención clara. El coste es una línea; el ahorro, una sesión entera de descifrado.

Todo corre en UTC

Aquí está la trampa. Los cron triggers se ejecutan siempre en UTC. No hay campo de zona horaria ni configuración regional: la hora que escribes es hora UTC, punto. Si quieres “las 3 de la madrugada en Madrid” tienes que traducirla tú a UTC y, peor aún, cargar con el horario de verano: Madrid es UTC+1 en invierno y UTC+2 en verano, y Cloudflare no desplaza nada por ti.

flowchart LR
Cron[cron diario a las 02:00 UTC] --> UTC[02:00 UTC fijo]
UTC -->|invierno UTC mas 1| Inv[03:00 hora de Madrid]
UTC -->|verano UTC mas 2| Ver[04:00 hora de Madrid]

Un 0 2 * * * fijo es la misma hora UTC todo el año, pero en Madrid cae a las 3 en invierno y a las 4 en verano. Si la hora local exacta importa —un informe que debe salir a las 9:00 de tu ciudad todo el año—, tienes dos salidas: elegir una hora UTC cuyo desfase aceptes, o calcular dentro del handler y abortar cuando no sea la hora local que buscas.

El reloj es de la plataforma, no tuyo

La tentación es pensar en el cron como “mi horario”, en mi zona, como el cron de mi portátil. Pero un horario que se ejecuta en cientos de ubicaciones a la vez no puede tener tu zona horaria: necesita una referencia canónica y única, y esa es UTC. No es un capricho de Cloudflare, es la misma razón por la que todo sistema distribuido serio guarda sus marcas de tiempo en UTC y traduce a hora local solo en el borde de la presentación. La hora local es un asunto de renderizado, no de almacenamiento ni de planificación —y por una razón profunda: con el horario de verano, la hora local deja de ser monótona y no ambigua—. Hay una hora que se repite en octubre y una hora que no existe en marzo; construir un planificador sobre semejante base es construir sobre arena. Cuando asumes que la expresión cron es una afirmación en tiempo absoluto, y que “las 3 en mi ciudad” es una cantidad derivada y a la deriva, dejas de pelearte con la plataforma y empiezas a diseñar horarios correctos por construcción: eliges horas UTC, o pones una guarda dentro del handler, pero nunca supones que el reloj sabe dónde vives. Es el mismo principio que separa al que guarda fechas con zona horaria explícita en su base de datos del que las almacena a pelo y descubre el desastre el último domingo de octubre, cuando un trabajo nocturno se ejecuta dos veces o ninguna.

⚔️ Traduce tiempo a cron
  1. Lee en voz alta qué significan */15 * * * *, 0 6 * * * y 0 0 1 1 *.
  2. Escribe la expresión para “cada día laborable a las 07:30 UTC”.
  3. Vives en una zona UTC+2 en verano y UTC+1 en invierno y quieres un informe a las 09:00 locales todo el año: explica por qué un único cron fijo no puede lograrlo y esboza dos soluciones.
  4. Añade tres horarios a un wrangler.jsonc con un comentario que explique la intención de cada uno.