Developer
CRON PARSER
Parse cron expressions in plain English. See next 10 execution times and build schedules from common presets.
Presets
Minute
Hour
Day (M)
Month
Day (W)
—
Next 10 Executions
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const DAYS = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
const MONTH_NAMES = ['January','February','March','April','May','June','July','August','September','October','November','December'];
const DAY_NAMES = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
function parseField(field, min, max) {
if (field === '*') return { all: true, values: null };
const values = new Set();
for (const part of field.split(',')) {
if (part.includes('/')) {
const [range, step] = part.split('/');
const s = parseInt(step);
const [rMin, rMax] = range === '*' ? [min, max] : range.split('-').map(Number);
for (let i = rMin; i <= (rMax || max); i += s) values.add(i);
} else if (part.includes('-')) {
const [lo, hi] = part.split('-').map(Number);
for (let i = lo; i <= hi; i++) values.add(i);
} else {
values.add(parseInt(part));
}
}
return { all: false, values };
}
function matchesField(val, parsed) {
if (parsed.all) return true;
return parsed.values.has(val);
}
function nextRuns(expr, count = 10) {
const parts = expr.trim().split(/\s+/);
if (parts.length < 5) return null;
const [mF, hF, domF, monF, dowF] = parts;
try {
const fields = [parseField(mF,0,59), parseField(hF,0,23), parseField(domF,1,31), parseField(monF,1,12), parseField(dowF,0,6)];
const runs = [];
let d = new Date();
d.setSeconds(0); d.setMilliseconds(0);
d = new Date(d.getTime() + 60000); // start from next minute
for (let iter = 0; iter < 500000 && runs.length < count; iter++) {
if (matchesField(d.getMonth()+1, fields[3]) &&
matchesField(d.getDate(), fields[2]) &&
matchesField(d.getDay(), fields[4]) &&
matchesField(d.getHours(), fields[1]) &&
matchesField(d.getMinutes(), fields[0])) {
runs.push(new Date(d));
}
d = new Date(d.getTime() + 60000);
}
return runs;
} catch { return null; }
}
function describeExpr(expr) {
const parts = expr.trim().split(/\s+/);
if (parts.length < 5) return 'Invalid cron expression (need 5 fields: min hour dom mon dow)';
const [min, hour, dom, mon, dow] = parts;
const descMin = min === '*' ? 'every minute' : min.startsWith('*/') ? `every ${min.slice(2)} minutes` : `at minute ${min}`;
const descHour = hour === '*' ? '' : hour.startsWith('*/') ? ` every ${hour.slice(2)} hours` : ` at ${hour.padStart(2,'0')}:${min === '*' ? '00' : min.padStart(2,'0')}`;
const descDom = dom === '*' ? '' : ` on day ${dom} of the month`;
const descMon = mon === '*' ? '' : ` in ${mon.split(',').map(m => MONTH_NAMES[parseInt(m)-1] || m).join(', ')}`;
const descDow = dow === '*' ? '' : dow === '1-5' ? ' on weekdays (Mon–Fri)' : dow === '0,6' || dow === '6,0' ? ' on weekends' : ` on ${dow.split(',').map(d => DAY_NAMES[parseInt(d)] || d).join(', ')}`;
if (expr === '* * * * *') return 'Runs every minute';
if (expr === '0 * * * *') return 'Runs at the start of every hour';
if (expr === '0 0 * * *') return 'Runs once a day at midnight';
if (expr === '0 0 * * 0') return 'Runs at midnight every Sunday';
if (expr === '0 0 1 * *') return 'Runs at midnight on the 1st of every month';
if (expr === '0 0 1 1 *') return 'Runs at midnight on January 1st (yearly)';
return `Runs ${descMin}${descHour}${descDom}${descMon}${descDow}`;
}
function fieldDesc(field, type) {
if (field === '*') return 'every ' + type;
if (field.startsWith('*/')) return 'every ' + field.slice(2);
if (field.includes('-')) return field.replace('-', '–');
if (field.includes(',')) return field;
return field;
}
function relTime(d) {
const diff = d - Date.now();
const s = Math.floor(diff / 1000);
if (s < 60) return 'in ' + s + 's';
if (s < 3600) return 'in ' + Math.floor(s/60) + 'm';
if (s < 86400) return 'in ' + Math.floor(s/3600) + 'h';
return 'in ' + Math.floor(s/86400) + 'd';
}
function parse() {
const expr = document.getElementById('cronInput').value;
const errEl = document.getElementById('errMsg');
errEl.style.display = 'none';
document.getElementById('cronInput').classList.remove('error');
const parts = expr.trim().split(/\s+/);
if (parts.length < 5) {
document.getElementById('cronDesc').textContent = 'Enter a 5-field cron expression';
document.getElementById('cronFields').innerHTML = '';
document.getElementById('nextRuns').innerHTML = '';
return;
}
const [min, hour, dom, mon, dow] = parts;
document.getElementById('cronDesc').textContent = describeExpr(expr);
const FIELD_DEFS = [
{ name: 'Minute', val: min, type: 'minute', range: '0–59' },
{ name: 'Hour', val: hour, type: 'hour', range: '0–23' },
{ name: 'Day (Month)', val: dom, type: 'day', range: '1–31' },
{ name: 'Month', val: mon, type: 'month', range: '1–12' },
{ name: 'Day (Week)', val: dow, type: 'weekday', range: '0–6' },
];
document.getElementById('cronFields').innerHTML = FIELD_DEFS.map(f =>
`<div class="cron-field">
<div class="cron-field-name">${f.name}</div>
<div class="cron-field-val">${f.val}</div>
<div class="cron-field-desc">${fieldDesc(f.val, f.type)}</div>
</div>`
).join('');
const runs = nextRuns(expr);
if (!runs) {
errEl.textContent = 'Could not compute next runs — check your expression.';
errEl.style.display = '';
document.getElementById('nextRuns').innerHTML = '';
return;
}
document.getElementById('nextRuns').innerHTML = runs.map((d, i) =>
`<div class="next-run">
<div class="next-run-num">${i+1}</div>
<div class="next-run-date">${d.toLocaleString()}</div>
<div class="next-run-rel">${relTime(d)}</div>
</div>`
).join('');
}
function setExpr(expr) {
document.getElementById('cronInput').value = expr;
parse();
}
function copyCron() {
const expr = document.getElementById('cronInput').value;
navigator.clipboard.writeText(expr).catch(() => {});
const btn = document.getElementById('copyBtn');
btn.textContent = 'Copied!'; btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'Copy Expression'; btn.classList.remove('copied'); }, 2000);
}
parse();