Validador de expressão cron
Digite uma expressão cron e veja se ela é válida, o que ela significa e quando será executada nas próximas 5 vezes.
Processamento local: nada do que você digita sai do seu navegador.
Como usar
Digite os 5 campos separados por espaço: minuto (0-59), hora (0-23), dia do mês (1-31), mês (1-12 ou JAN-DEC) e dia da semana (0-7 ou SUN-SAT, sendo 0 e 7 domingo). Em cada campo vale * (todos), listas (1,15), faixas (9-18) e passos (*/15 ou 10-30/5). Também aceitamos atalhos como @daily e @hourly.
Detalhes importantes
- Se o dia do mês e o dia da semana estiverem ambos preenchidos (sem
*), o cron padrão executa quando qualquer um dos dois bater, não os dois ao mesmo tempo. - As próximas execuções são calculadas no fuso horário do seu navegador. O servidor onde o cron roda pode usar outro fuso, geralmente UTC.
- Esta ferramenta cobre o formato clássico de 5 campos. Extensões como segundos, ano,
L,Wou#(Quartz, Spring, AWS) não são suportadas.
Como implementar em código
Exemplos prontos para usar no seu projeto.
const FAIXAS = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]]; // min, hora, dia, mês, dia da semana
function cronValido(expr) {
const campos = expr.trim().split(/\s+/);
if (campos.length !== 5) return false;
return campos.every((campo, i) => campo.split(',').every(parte => {
const m = /^(?:\*|(\d+)(?:-(\d+))?)(?:\/(\d+))?$/.exec(parte);
if (!m) return false;
if (m[3] !== undefined && +m[3] < 1) return false; // passo >= 1
if (m[1] === undefined) return true; // "*" ou "*/n"
const a = +m[1], b = m[2] === undefined ? a : +m[2];
return a >= FAIXAS[i][0] && b <= FAIXAS[i][1] && a <= b;
}));
}
console.log(cronValido('*/15 9-18 * * 1-5')); // true
console.log(cronValido('60 * * * *')); // false (minuto > 59)Não cobre nomes (JAN, MON) nem atalhos (@daily). Para calcular execuções use a lib cron-parser.
import re
FAIXAS = [(0, 59), (0, 23), (1, 31), (1, 12), (0, 7)] # min, hora, dia, mês, dia da semana
PARTE = re.compile(r"^(?:\*|(\d+)(?:-(\d+))?)(?:/(\d+))?$")
def cron_valido(expr: str) -> bool:
campos = expr.split()
if len(campos) != 5:
return False
for campo, (mn, mx) in zip(campos, FAIXAS):
for parte in campo.split(","):
m = PARTE.match(parte)
if not m:
return False
ini, fim, passo = m.groups()
if passo is not None and int(passo) < 1:
return False
if ini is not None:
a = int(ini)
b = int(fim) if fim is not None else a
if a < mn or b > mx or a > b:
return False
return True
print(cron_valido("*/15 9-18 * * 1-5")) # True
print(cron_valido("60 * * * *")) # FalsePara calcular as próximas execuções use o pacote croniter.
package main
import (
"fmt"
"regexp"
"strconv"
"strings"
)
var faixas = [5][2]int{{0, 59}, {0, 23}, {1, 31}, {1, 12}, {0, 7}} // min, hora, dia, mês, dia da semana
var parteRe = regexp.MustCompile(`^(?:\*|(\d+)(?:-(\d+))?)(?:/(\d+))?$`)
func cronValido(expr string) bool {
campos := strings.Fields(expr)
if len(campos) != 5 {
return false
}
for i, campo := range campos {
for _, parte := range strings.Split(campo, ",") {
m := parteRe.FindStringSubmatch(parte)
if m == nil {
return false
}
if m[3] != "" {
if p, _ := strconv.Atoi(m[3]); p < 1 {
return false
}
}
if m[1] != "" {
a, _ := strconv.Atoi(m[1])
b := a
if m[2] != "" {
b, _ = strconv.Atoi(m[2])
}
if a < faixas[i][0] || b > faixas[i][1] || a > b {
return false
}
}
}
}
return true
}
func main() {
fmt.Println(cronValido("*/15 9-18 * * 1-5")) // true
fmt.Println(cronValido("60 * * * *")) // false
}Para analisar e calcular execuções use github.com/robfig/cron/v3 (cron.ParseStandard).
const faixas = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]]; // min, hora, dia, mês, dia da semana
final parteRe = RegExp(r'^(?:\*|(\d+)(?:-(\d+))?)(?:/(\d+))?$');
bool cronValido(String expr) {
final campos = expr.trim().split(RegExp(r'\s+'));
if (campos.length != 5) return false;
for (var i = 0; i < 5; i++) {
for (final parte in campos[i].split(',')) {
final m = parteRe.firstMatch(parte);
if (m == null) return false;
if (m.group(3) != null && int.parse(m.group(3)!) < 1) return false;
if (m.group(1) != null) {
final a = int.parse(m.group(1)!);
final b = m.group(2) != null ? int.parse(m.group(2)!) : a;
if (a < faixas[i][0] || b > faixas[i][1] || a > b) return false;
}
}
}
return true;
}
void main() {
print(cronValido('*/15 9-18 * * 1-5')); // true
print(cronValido('60 * * * *')); // false
}import java.util.regex.*;
public class CronValidator {
static final int[][] FAIXAS = {{0, 59}, {0, 23}, {1, 31}, {1, 12}, {0, 7}}; // min, hora, dia, mês, dia da semana
static final Pattern PARTE = Pattern.compile("^(?:\\*|(\\d+)(?:-(\\d+))?)(?:/(\\d+))?$");
static boolean cronValido(String expr) {
String[] campos = expr.trim().split("\\s+");
if (campos.length != 5) return false;
for (int i = 0; i < 5; i++) {
for (String parte : campos[i].split(",", -1)) {
Matcher m = PARTE.matcher(parte);
if (!m.matches()) return false;
if (m.group(3) != null && Integer.parseInt(m.group(3)) < 1) return false;
if (m.group(1) != null) {
int a = Integer.parseInt(m.group(1));
int b = m.group(2) != null ? Integer.parseInt(m.group(2)) : a;
if (a < FAIXAS[i][0] || b > FAIXAS[i][1] || a > b) return false;
}
}
}
return true;
}
public static void main(String[] args) {
System.out.println(cronValido("*/15 9-18 * * 1-5")); // true
System.out.println(cronValido("60 * * * *")); // false
}
}Para o cron do Spring (@Scheduled), CronExpression.isValidExpression(expr) já faz isso, mas exige 6 campos (com segundos).
<?php
function cronValido(string $expr): bool {
$faixas = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]]; // min, hora, dia, mês, dia da semana
$campos = preg_split('/\s+/', trim($expr));
if (count($campos) !== 5) return false;
foreach ($campos as $i => $campo) {
foreach (explode(',', $campo) as $parte) {
if (!preg_match('~^(?:\*|(\d+)(?:-(\d+))?)(?:/(\d+))?$~', $parte, $m)) return false;
if (isset($m[3]) && $m[3] !== '' && (int)$m[3] < 1) return false;
if (isset($m[1]) && $m[1] !== '') {
$a = (int)$m[1];
$b = (isset($m[2]) && $m[2] !== '') ? (int)$m[2] : $a;
if ($a < $faixas[$i][0] || $b > $faixas[$i][1] || $a > $b) return false;
}
}
}
return true;
}
var_dump(cronValido('*/15 9-18 * * 1-5')); // bool(true)
var_dump(cronValido('60 * * * *')); // bool(false)Para calcular as próximas execuções use o pacote dragonmantank/cron-expression.
using System;
using System.Text.RegularExpressions;
class Program
{
static readonly int[][] Faixas = { new[]{0,59}, new[]{0,23}, new[]{1,31}, new[]{1,12}, new[]{0,7} }; // min, hora, dia, mês, dia da semana
static readonly Regex Parte = new(@"^(?:\*|(\d+)(?:-(\d+))?)(?:/(\d+))?$");
static bool CronValido(string expr)
{
var campos = expr.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
if (campos.Length != 5) return false;
for (int i = 0; i < 5; i++)
foreach (var parte in campos[i].Split(','))
{
var m = Parte.Match(parte);
if (!m.Success) return false;
if (m.Groups[3].Success && int.Parse(m.Groups[3].Value) < 1) return false;
if (m.Groups[1].Success)
{
int a = int.Parse(m.Groups[1].Value);
int b = m.Groups[2].Success ? int.Parse(m.Groups[2].Value) : a;
if (a < Faixas[i][0] || b > Faixas[i][1] || a > b) return false;
}
}
return true;
}
static void Main()
{
Console.WriteLine(CronValido("*/15 9-18 * * 1-5")); // True
Console.WriteLine(CronValido("60 * * * *")); // False
}
}Para calcular execuções use o pacote NuGet Cronos (CronExpression.Parse).
Perguntas frequentes
Qual a diferença entre cron de 5 e de 6 campos?
O cron clássico do Unix tem 5 campos (minuto a dia da semana). Alguns sistemas, como Quartz e Spring, adicionam segundos no início, e AWS EventBridge adiciona o ano. Aqui validamos só o formato de 5 campos.
O que significa */5 no campo de minutos?
Significa a cada 5 minutos, começando do 0: nos minutos 0, 5, 10 e assim por diante até 55.
Por que a expressão 0 0 31 2 * não tem próxima execução?
Porque fevereiro nunca tem dia 31. A expressão é sintaticamente válida, mas nunca dispara.
Veja também
Precisa de um sistema ou app sob medida?
A VcInova desenvolve software, aplicativos e integrações para empresas de todo o Brasil.
Fale com a VcInova