@mj-muin/cronex
v0.1.0
Published
Explain cron expressions in human language. Bidirectional: cron → human, human → cron. Zero dependencies.
Maintainers
Readme
@muin/cron-explain
Finally understand and generate cron expressions in human language. Bidirectional conversion with visual preview.
Features
- 🎨 Interactive Mode - Visual cron expression builder with real-time preview
- 🔄 Bidirectional - Explain cron expressions OR generate from natural language
- 👀 Next Run Times - See exactly when your cron will execute (next 10 occurrences)
- ⚙️ Timezone Support - Preview schedules in any timezone
- 📋 Multiple Formats - Human-readable, technical, and crontab-ready output
- 🛡️ Validation - Catch invalid cron expressions before they fail silently
- 🌍 Common Presets - Quick access to common schedules (hourly, daily, weekly, etc.)
- ⚡ Fast & Lightweight - No external dependencies, pure JavaScript
Installation
npm install -g @muin/cron-explainOr use directly with npx:
npx @muin/cron-explainQuick Start
Explain a cron expression:
cron-explain "0 5 * * 1"
# Output: Every Monday at 5:00 AMGenerate from natural language:
cron-explain "every day at 3pm"
# Output: 0 15 * * *Interactive mode (recommended):
cron-explain --interactiveUsage
Command-Line Interface
# Explain a cron expression
cron-explain "0 5 * * 1"
cron-explain "*/15 * * * *"
cron-explain "0 0 1 1 *"
# Generate from natural language
cron-explain "every monday at 5am"
cron-explain "every 15 minutes"
cron-explain "first day of every month"
# Show next run times
cron-explain "0 9 * * 1-5" --next 10
# Explain with timezone
cron-explain "0 9 * * *" --timezone "America/New_York"
# Validate expression
cron-explain "0 25 * * *" --validate
# Error: Invalid hour: 25
# Output in different formats
cron-explain "every monday at 5am" --format cron # 0 5 * * 1
cron-explain "0 5 * * 1" --format human # Every Monday at 5:00 AM
cron-explain "0 5 * * 1" --format verbose # Detailed breakdownOptions
<expression>- Cron expression or natural language description-i, --interactive- Launch interactive mode with visual builder-n, --next <count>- Show next N execution times (default: 5)-t, --timezone <tz>- Timezone for execution times (default: system timezone)-f, --format <type>- Output format:human,cron,verbose,json-v, --validate- Validate expression and show errors--examples- Show common cron expression examples--presets- List available schedule presets
Examples
Example 1: Basic Explanations
$ cron-explain "0 5 * * 1"
Every Monday at 5:00 AM
$ cron-explain "*/15 * * * *"
Every 15 minutes
$ cron-explain "0 0 * * *"
Every day at midnight
$ cron-explain "0 9-17 * * 1-5"
Every hour from 9:00 AM to 5:00 PM, Monday through Friday
$ cron-explain "0 0 1 * *"
At midnight on the first day of every month
$ cron-explain "30 2 * * 0"
Every Sunday at 2:30 AMExample 2: Natural Language to Cron
$ cron-explain "every day at 3pm"
0 15 * * *
Description: Every day at 3:00 PM
$ cron-explain "every monday at 9am"
0 9 * * 1
Description: Every Monday at 9:00 AM
$ cron-explain "every 30 minutes"
*/30 * * * *
Description: Every 30 minutes
$ cron-explain "every hour between 9am and 5pm on weekdays"
0 9-17 * * 1-5
Description: Every hour from 9:00 AM to 5:00 PM, Monday through Friday
$ cron-explain "first day of every month at midnight"
0 0 1 * *
Description: At midnight on the first day of every month
$ cron-explain "last friday of every month at 5pm"
0 17 * * 5#4
Description: Every last Friday of the month at 5:00 PMExample 3: Next Execution Times
$ cron-explain "0 9 * * 1-5" --next 10
Expression: 0 9 * * 1-5
Description: Every weekday (Monday-Friday) at 9:00 AM
Timezone: America/Los_Angeles (PST)
Next 10 executions:
1. Monday, Feb 10, 2025 at 9:00 AM PST (in 2 days)
2. Tuesday, Feb 11, 2025 at 9:00 AM PST (in 3 days)
3. Wednesday, Feb 12, 2025 at 9:00 AM PST (in 4 days)
4. Thursday, Feb 13, 2025 at 9:00 AM PST (in 5 days)
5. Friday, Feb 14, 2025 at 9:00 AM PST (in 6 days)
6. Monday, Feb 17, 2025 at 9:00 AM PST (in 9 days)
7. Tuesday, Feb 18, 2025 at 9:00 AM PST (in 10 days)
8. Wednesday, Feb 19, 2025 at 9:00 AM PST (in 11 days)
9. Thursday, Feb 20, 2025 at 9:00 AM PST (in 12 days)
10. Friday, Feb 21, 2025 at 9:00 AM PST (in 13 days)Example 4: Timezone Conversions
$ cron-explain "0 9 * * *" --timezone "Europe/London"
Expression: 0 9 * * *
Description: Every day at 9:00 AM
Timezone: Europe/London (GMT)
Next execution: Tomorrow at 9:00 AM GMT
Equivalent in other timezones:
- America/New_York: 4:00 AM EST
- America/Los_Angeles: 1:00 AM PST
- Asia/Tokyo: 6:00 PM JST
- Australia/Sydney: 8:00 PM AEDTExample 5: Verbose Format
$ cron-explain "30 2 * * 0" --format verbose
╭─ Cron Expression Breakdown ──────────────────────────╮
│ │
│ Expression: 30 2 * * 0 │
│ │
│ ┌───────────── minute (0 - 59) │
│ │ ┌─────────── hour (0 - 23) │
│ │ │ ┌───────── day of month (1 - 31) │
│ │ │ │ ┌─────── month (1 - 12) │
│ │ │ │ │ ┌───── day of week (0 - 6) (Sun - Sat) │
│ │ │ │ │ │ │
│ 30 2 * * 0 │
│ │ │ │ │ │ │
│ │ │ │ │ └─ Every Sunday │
│ │ │ │ └─── Every month │
│ │ │ └───── Every day of month │
│ │ └─────── At 2:00 AM │
│ └────────── At 30 minutes past the hour │
│ │
│ Human-readable: Every Sunday at 2:30 AM │
│ │
╰───────────────────────────────────────────────────────╯
Field Details:
Minute: 30 (at 30 minutes past the hour)
Hour: 2 (2:00 AM)
Day of Month: * (every day)
Month: * (every month)
Day of Week: 0 (Sunday)
Special Patterns Detected: NoneExample 6: Interactive Mode
$ cron-explain --interactive
╭─────────────────────────────────────────────────────────────╮
│ ⏰ Cron Explain - Interactive Mode │
│ Build and understand cron expressions visually │
╰─────────────────────────────────────────────────────────────╯
? What would you like to do?
❯ Build a cron expression
Explain an existing expression
Browse common presets
? Select frequency:
❯ Every minute
Every hour
Every day
Every week
Every month
Custom schedule
? What time? (Use arrow keys or type)
┌──────────────────────────┐
│ Hour: 09 ◀ ▶ │
│ Minute: 00 ◀ ▶ │
│ │
│ Format: AM/PM ◀ ▶ │
└──────────────────────────┘
? Which days?
◉ Monday
◉ Tuesday
◉ Wednesday
◉ Thursday
◉ Friday
◯ Saturday
◯ Sunday
╭─── Preview ──────────────────────────────────────────╮
│ │
│ Cron Expression: 0 9 * * 1-5 │
│ │
│ Human-readable: Every weekday at 9:00 AM │
│ │
│ Next 5 runs: │
│ Mon Feb 10, 2025 at 9:00 AM │
│ Tue Feb 11, 2025 at 9:00 AM │
│ Wed Feb 12, 2025 at 9:00 AM │
│ Thu Feb 13, 2025 at 9:00 AM │
│ Fri Feb 14, 2025 at 9:00 AM │
│ │
╰──────────────────────────────────────────────────────╯
? What would you like to do?
❯ 📋 Copy to clipboard
💾 Save to crontab
🔄 Modify schedule
✏️ Add a label/comment
❌ Exit
✓ Copied to clipboard: 0 9 * * 1-5
💡 Tip: Add this to your crontab:
crontab -e
# Then paste: 0 9 * * 1-5 /path/to/script.sh
Thanks for using cron-explain! ⏰Example 7: Common Presets
$ cron-explain --presets
╭─ Common Cron Presets ─────────────────────────────────╮
│ │
│ @hourly → 0 * * * * (every hour) │
│ @daily → 0 0 * * * (every day at midnight) │
│ @weekly → 0 0 * * 0 (every Sunday at midnight) │
│ @monthly → 0 0 1 * * (first of month) │
│ @yearly → 0 0 1 1 * (Jan 1st at midnight) │
│ @reboot → (at system startup) │
│ │
│ Business Hours (9-5 weekdays): │
│ Start: 0 9 * * 1-5 │
│ Every hr: 0 9-17 * * 1-5 │
│ End: 0 17 * * 1-5 │
│ │
│ Maintenance Windows: │
│ Late night: 0 2 * * * (2 AM daily) │
│ Weekend: 0 0 * * 6 (Sat midnight) │
│ Monthly: 0 2 1 * * (1st at 2 AM) │
│ │
│ Backups: │
│ Hourly: 0 * * * * │
│ Daily: 0 1 * * * (1 AM) │
│ Weekly: 0 2 * * 0 (Sun 2 AM) │
│ │
╰────────────────────────────────────────────────────────╯Example 8: Validation
$ cron-explain "0 25 * * *" --validate
❌ Invalid cron expression: 0 25 * * *
Errors found:
• Hour value 25 is out of range (must be 0-23)
Did you mean:
0 23 * * * → Every day at 11:00 PM
0 5 * * * → Every day at 5:00 AM
Tip: Use --interactive mode to build valid expressions
$ cron-explain "*/90 * * * *" --validate
⚠️ Warning: This cron expression may not work as expected
Issues:
• Interval '*/90' for minutes will only match at minute 0
• Consider using '0 */1 * * *' for hourly execution instead
Suggestion: For "every 90 minutes", consider scheduling:
0 0,1,3,4,6,7,9,10,12,13,15,16,18,19,21,22 * * *Example 9: JSON Output for Automation
$ cron-explain "0 9 * * 1-5" --format json --next 3
{
"expression": "0 9 * * 1-5",
"description": "Every weekday (Monday-Friday) at 9:00 AM",
"fields": {
"minute": "0",
"hour": "9",
"dayOfMonth": "*",
"month": "*",
"dayOfWeek": "1-5"
},
"interpretation": {
"minute": "At 0 minutes past the hour",
"hour": "At 9:00 AM",
"dayOfMonth": "Every day of the month",
"month": "Every month",
"dayOfWeek": "Monday through Friday"
},
"nextRuns": [
{
"timestamp": "2025-02-10T09:00:00-08:00",
"formatted": "Monday, Feb 10, 2025 at 9:00 AM PST",
"fromNow": "in 2 days"
},
{
"timestamp": "2025-02-11T09:00:00-08:00",
"formatted": "Tuesday, Feb 11, 2025 at 9:00 AM PST",
"fromNow": "in 3 days"
},
{
"timestamp": "2025-02-12T09:00:00-08:00",
"formatted": "Wednesday, Feb 12, 2025 at 9:00 AM PST",
"fromNow": "in 4 days"
}
],
"timezone": "America/Los_Angeles",
"valid": true,
"warnings": []
}Supported Cron Syntax
Standard Fields
┌───────────── minute (0 - 59)
│ ┌─────────── hour (0 - 23)
│ │ ┌───────── day of month (1 - 31)
│ │ │ ┌─────── month (1 - 12)
│ │ │ │ ┌───── day of week (0 - 6) (Sunday=0 or 7)
│ │ │ │ │
* * * * *Special Characters
| Character | Meaning | Example |
|-----------|---------|---------|
| * | Any value | * * * * * = every minute |
| , | Value list | 0 9,17 * * * = 9am and 5pm |
| - | Range | 0 9-17 * * * = 9am to 5pm |
| / | Step values | */15 * * * * = every 15 min |
| # | Nth occurrence | 0 0 * * 1#2 = 2nd Monday |
| L | Last | 0 0 L * * = last day of month |
Month Names
Both numbers and names are supported:
0 0 1 jan * # January 1st
0 0 1 1 * # Same thing
0 0 * jun-aug * # June through AugustDay of Week Names
0 9 * * mon # Every Monday
0 9 * * 1 # Same thing
0 9 * * mon-fri # WeekdaysSpecial Keywords
| Keyword | Equivalent | Description |
|---------|------------|-------------|
| @hourly | 0 * * * * | Every hour |
| @daily | 0 0 * * * | Every day at midnight |
| @weekly | 0 0 * * 0 | Every Sunday |
| @monthly | 0 0 1 * * | First of month |
| @yearly | 0 0 1 1 * | January 1st |
| @annually | 0 0 1 1 * | Same as @yearly |
| @reboot | N/A | At system boot |
Common Use Cases
1. Understanding Existing Crontabs
The most common use case - decode cryptic cron expressions:
# From your crontab
crontab -l | grep "backup" | cut -d' ' -f1-5 | cron-explain
# Output: Every Sunday at 2:00 AM
# Quick lookup
cron-explain "30 4 1,15 * *"
# Output: At 4:30 AM on the 1st and 15th of every month2. Creating New Cron Jobs
Build cron expressions without memorizing syntax:
# Natural language input
cron-explain "every day at 3am" > /tmp/schedule
cat /tmp/schedule # 0 3 * * *
# Add to crontab
echo "0 3 * * * /path/to/backup.sh" | crontab -
# Or use interactive mode
cron-explain --interactive
# Walk through visual builder, then copy output3. Debugging Schedules
See when jobs will actually run:
# Check if your job will run when you expect
cron-explain "0 2 * * 6" --next 10
# Shows next 10 Saturdays at 2 AM
# Verify timezone issues
cron-explain "0 9 * * *" --timezone "UTC" --next 5
# See UTC times vs your local time4. Documentation
Generate human-readable schedules for documentation:
# For your README
echo "Deployment schedule:" > README.md
cron-explain "0 2 * * 0" >> README.md
# Output: Every Sunday at 2:00 AM
# For team wiki
for job in $(crontab -l | cut -d' ' -f1-5); do
echo "$job → $(cron-explain "$job")"
done5. CI/CD Integration
Validate cron expressions in your pipeline:
# In your CI script
if ! cron-explain "$CRON_SCHEDULE" --validate; then
echo "Invalid cron expression!"
exit 1
fi
# Or get JSON output for processing
cron-explain "$CRON_SCHEDULE" --format json | jq '.valid'6. Learning Cron Syntax
Educational tool for understanding cron:
# See examples
cron-explain --examples
# Try different patterns
cron-explain "*/5 * * * *" # Every 5 minutes
cron-explain "0 */2 * * *" # Every 2 hours
cron-explain "0 0 */2 * *" # Every 2 days
# Use verbose mode to learn
cron-explain "30 2 * * 0" --format verboseWhy This Tool?
The Problem
Cron expressions are powerful but cryptic...
Without cron-explain:
0 5 * * 1- What does this mean? 🤔
- When does it run next?
- Is it Monday or Sunday (0 vs 1)?
- Is it 5am or 5pm?
- Will it run this week?
- How do I create a similar schedule?
With cron-explain:
$ cron-explain "0 5 * * 1"
Every Monday at 5:00 AM
Next 5 runs:
Mon Feb 10, 2025 at 5:00 AM (in 2 days)
Mon Feb 17, 2025 at 5:00 AM (in 9 days)
Mon Feb 24, 2025 at 5:00 AM (in 16 days)
...10 seconds. Crystal clear. ✅
The Benefits
- ⏱️ Save 5-10 minutes every time you need to understand/create a cron
- 🐛 Fewer bugs - See exactly when jobs run before deploying
- 🧠 Learning tool - Understand cron syntax without googling
- 🌍 Timezone aware - No more "why didn't my job run?" mysteries
- ✅ Validation - Catch errors before they cause outages
Common Gotchas & Troubleshooting
Issue: "0 vs 7 for Sunday - which is correct?"
Cause: Both are valid! Sunday can be 0 or 7
Solution:
# These are identical:
cron-explain "0 0 * * 0" # Sunday (0)
cron-explain "0 0 * * 7" # Sunday (7)
# Both output: Every Sunday at midnight
# Recommendation: Use 0 for consistencyIssue: "My cron runs every minute instead of daily"
Cause: Missing fields or incorrect wildcard usage
Solution:
# Wrong: Only specifying hour
0 * * * # ❌ This is invalid (missing day of week field)
# Wrong: Too many wildcards
* * * * * # ❌ Every minute!
# Correct: Daily at specific time
0 9 * * * # ✅ Every day at 9 AM
# Validate before using:
cron-explain "* * * * *" --validate
# Warning: This runs every minute!Issue: "*/15 for every 15 minutes doesn't work"
Cause: Step values only work at intervals that divide evenly
Solution:
# This works (15 divides into 60):
*/15 * * * * # ✅ At :00, :15, :30, :45
# This doesn't (7 doesn't divide into 60):
*/7 * * * * # ❌ Only runs at :00!
# See what it actually does:
cron-explain "*/7 * * * *" --next 5
# Shows: Only at minute 0
# For irregular intervals, list explicitly:
0,7,14,21,28,35,42,49,56 * * * * # Every 7 minutesIssue: "Daylight Saving Time causes missed jobs"
Cause: Clock changes during DST transitions
Solution:
# Problem: Job scheduled at 2 AM during DST transition
0 2 * * * # May run twice or not at all
# Solution 1: Use UTC timezone
cron-explain "0 2 * * *" --timezone UTC
# Runs at consistent absolute time
# Solution 2: Avoid 2-3 AM window
0 1 * * * # Run at 1 AM (before DST change)
0 4 * * * # Run at 4 AM (after DST change)
# Check DST impact:
cron-explain "0 2 * * *" --next 30 --timezone "America/New_York"
# Look for gaps or duplicatesIssue: "Month ranges don't work as expected"
Cause: Confusion between month numbers and names
Solution:
# Wrong: Months are 1-12, not 0-11
0 0 1 0 * # ❌ Invalid: month 0
# Correct: January is 1
0 0 1 1 * # ✅ January 1st
# Use names to avoid confusion:
0 0 1 jan * # ✅ Clearer
0 0 1 jan-mar * # ✅ Jan through March
# Validate month numbers:
cron-explain "0 0 1 0 *" --validate
# Error: Month 0 is invalidIssue: "Last day of month scheduling is tricky"
Cause: Different months have different last days
Solution:
# Don't use 31 (won't run in shorter months):
0 0 31 * * # ❌ Skips Feb, Apr, Jun, Sep, Nov
# Use 'L' for last day (if supported):
0 0 L * * # ✅ Last day of every month
# Or use day ranges:
0 0 28-31 * * # ⚠️ Runs on last 4 days
# Best approach: Multiple days
0 0 1 * * # First of month
0 0 15 * * # Middle of month
# Check behavior:
cron-explain "0 0 31 * *" --next 12
# See which months are skippedIssue: "Combination of day-of-month AND day-of-week"
Cause: Both are OR, not AND
Solution:
# This runs when EITHER condition is true:
0 0 13 * 5 # ❌ Runs on 13th OR Friday (not 13th + Friday)
# Output: "At midnight on the 13th OR on Friday"
# For "Friday the 13th", you need advanced syntax:
# Use separate jobs or scripting logic
# Verify behavior:
cron-explain "0 0 13 * 5" --next 30
# See both 13th AND Fridays listedIssue: "Job runs on wrong timezone"
Cause: Cron uses system timezone, may not match your location
Solution:
# Check system timezone
timedatectl status
# Preview in your timezone:
cron-explain "0 9 * * *" --timezone "America/Los_Angeles"
# Set timezone in cron (if supported):
TZ="America/Los_Angeles"
0 9 * * * /path/to/script.sh
# Or use UTC and convert in script:
0 17 * * * # 17:00 UTC = 9:00 AM PSTIssue: "@hourly doesn't work in my cron"
Cause: Special keywords require vixie-cron or similar
Solution:
# Not all cron implementations support keywords:
@hourly /path/to/script.sh # May not work
# Use standard syntax instead:
0 * * * * /path/to/script.sh # ✅ Works everywhere
# Check if your cron supports it:
cron-explain "@hourly"
# Tool will show equivalent: 0 * * * *Issue: "Command not found" when running cron job
Cause: Different PATH in cron environment
Solution:
# Cron has minimal PATH
# Test what your job will see:
* * * * * env > /tmp/cron-env.txt
# Compare with: env > /tmp/shell-env.txt
# Fix: Use absolute paths in cron:
0 9 * * * /usr/local/bin/python3 /home/user/script.py
# Or set PATH in crontab:
PATH=/usr/local/bin:/usr/bin:/bin
0 9 * * * python3 /home/user/script.py
# This is shell/scripting, not cron-explain issueReal-World Workflows
Workflow 1: Production Deployment Checklist
Deploy cron jobs safely to production:
#!/bin/bash
# scripts/deploy-cron.sh - Safe cron deployment workflow
CRON_FILE="config/production.crontab"
STAGING_CRONTAB="config/staging.crontab"
echo "🔍 Step 1: Validate all cron expressions"
while IFS= read -r line; do
# Skip comments and empty lines
[[ "$line" =~ ^#.*$ ]] || [[ -z "$line" ]] && continue
# Extract cron expression (first 5 fields)
cron_expr=$(echo "$line" | awk '{print $1,$2,$3,$4,$5}')
if ! cron-explain "$cron_expr" --validate 2>/dev/null; then
echo "❌ Invalid cron: $line"
exit 1
fi
echo "✅ Valid: $cron_expr → $(cron-explain "$cron_expr")"
done < "$CRON_FILE"
echo ""
echo "📅 Step 2: Check for scheduling conflicts"
# Extract all cron expressions and check overlap
cron-explain "$cron_expr" --next 100 --format json | \
jq -r '.nextRuns[].timestamp' > /tmp/schedule.txt
# Detect jobs running at same time
duplicates=$(sort /tmp/schedule.txt | uniq -d | wc -l)
if [ "$duplicates" -gt 10 ]; then
echo "⚠️ Warning: $duplicates time slots have multiple jobs"
echo " Consider spreading them out for better load distribution"
fi
echo ""
echo "🌍 Step 3: Preview in production timezone"
while IFS= read -r line; do
[[ "$line" =~ ^#.*$ ]] || [[ -z "$line" ]] && continue
cron_expr=$(echo "$line" | awk '{print $1,$2,$3,$4,$5}')
command=$(echo "$line" | cut -d' ' -f6-)
echo ""
echo "Job: $command"
cron-explain "$cron_expr" --timezone "UTC" --next 3
done < "$CRON_FILE"
echo ""
echo "🚀 Step 4: Deploy to staging first"
scp "$CRON_FILE" staging:/tmp/new.crontab
ssh staging "crontab /tmp/new.crontab && crontab -l"
echo ""
read -p "✋ Staging deployed. Check logs. Deploy to prod? (y/N) " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
scp "$CRON_FILE" production:/tmp/new.crontab
ssh production "crontab /tmp/new.crontab"
echo "✅ Production crontab updated!"
else
echo "❌ Deployment cancelled"
exit 1
fiSafety checklist:
- ✅ Validate syntax
- ✅ Check for conflicts
- ✅ Verify timezone
- ✅ Test in staging
- ✅ Monitor first run
Workflow 2: Monitoring & Alerting Integration
Ensure cron jobs run on schedule:
#!/bin/bash
# scripts/cron-monitor.sh - Monitor cron execution
CRON_SCHEDULE="0 2 * * *" # Your backup job
ALERT_EMAIL="[email protected]"
ALERT_SLACK="https://hooks.slack.com/services/YOUR/WEBHOOK"
# 1. Calculate when job should have run
EXPECTED_RUN=$(cron-explain "$CRON_SCHEDULE" --next 1 --format json | \
jq -r '.nextRuns[0].timestamp')
# 2. Check if it actually ran (look for log entry)
LAST_RUN=$(grep "backup completed" /var/log/backup.log | tail -1 | cut -d' ' -f1-3)
LAST_RUN_TS=$(date -d "$LAST_RUN" +%s)
EXPECTED_TS=$(date -d "$EXPECTED_RUN" +%s)
# 3. Alert if missed
TIME_DIFF=$(($(date +%s) - EXPECTED_TS))
if [ $TIME_DIFF -gt 3600 ] && [ $LAST_RUN_TS -lt $EXPECTED_TS ]; then
# Job didn't run!
MESSAGE="🚨 Cron job missed: Expected at $EXPECTED_RUN, last run was $LAST_RUN"
# Email alert
echo "$MESSAGE" | mail -s "Cron Failure Alert" "$ALERT_EMAIL"
# Slack alert
curl -X POST "$ALERT_SLACK" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"$MESSAGE\"}"
fi
# 4. Log next run time for visibility
NEXT_RUN=$(cron-explain "$CRON_SCHEDULE" --next 1)
echo "$(date): Next scheduled run: $NEXT_RUN" >> /var/log/cron-monitor.logRun this monitor every hour:
0 * * * * /scripts/cron-monitor.shWorkflow 3: Dynamic Cron Generation
Generate cron schedules programmatically:
#!/usr/bin/env python3
# scripts/generate-maintenance-windows.py
"""
Generate staggered maintenance windows for multi-region deployments
"""
import subprocess
import json
from datetime import datetime, timedelta
# Regions and their preferred maintenance windows (local time)
REGIONS = {
"us-east": {"hour": 2, "timezone": "America/New_York"},
"eu-west": {"hour": 3, "timezone": "Europe/London"},
"ap-south": {"hour": 4, "timezone": "Asia/Tokyo"},
}
def generate_cron(region, config):
"""Generate cron expression for region's maintenance window"""
# Convert local time to UTC
hour = config["hour"]
timezone = config["timezone"]
# Generate cron expression
cron_expr = f"0 {hour} * * 0" # Every Sunday at specified hour
# Use cron-explain to verify and show in UTC
result = subprocess.run(
["cron-explain", cron_expr, "--timezone", timezone, "--format", "json"],
capture_output=True,
text=True
)
schedule = json.loads(result.stdout)
return {
"region": region,
"local_time": f"{hour}:00 {timezone}",
"cron": cron_expr,
"description": schedule["description"],
"next_run": schedule["nextRuns"][0]["formatted"]
}
# Generate schedules
print("# Maintenance Window Cron Schedule")
print("# Generated:", datetime.now().isoformat())
print()
for region, config in REGIONS.items():
schedule = generate_cron(region, config)
print(f"# {region.upper()}: {schedule['description']}")
print(f"# Local time: {schedule['local_time']}")
print(f"# Next run: {schedule['next_run']}")
print(f"{schedule['cron']} /scripts/maintenance.sh {region}")
print()Output:
# Maintenance Window Cron Schedule
# Generated: 2025-02-08T10:30:00
# US-EAST: Every Sunday at 2:00 AM
# Local time: 2:00 America/New_York
# Next run: Sunday, Feb 09, 2025 at 7:00 AM UTC
0 2 * * 0 /scripts/maintenance.sh us-east
# EU-WEST: Every Sunday at 3:00 AM
# Local time: 3:00 Europe/London
# Next run: Sunday, Feb 09, 2025 at 3:00 AM UTC
0 3 * * 0 /scripts/maintenance.sh eu-west
# AP-SOUTH: Every Sunday at 4:00 AM
# Local time: 4:00 Asia/Tokyo
# Next run: Sunday, Feb 09, 2025 at 7:00 PM UTC (Sat)
0 4 * * 0 /scripts/maintenance.sh ap-southWorkflow 4: Cron Documentation Auto-Generation
Keep your cron jobs documented:
#!/bin/bash
# scripts/document-crons.sh - Auto-generate cron documentation
OUTPUT="docs/CRON_JOBS.md"
cat > "$OUTPUT" << 'EOF'
# Cron Jobs Documentation
Auto-generated from production crontab.
Last updated: $(date)
---
EOF
crontab -l | while IFS= read -r line; do
# Handle comments
if [[ "$line" =~ ^#.*$ ]]; then
echo "$line" >> "$OUTPUT"
continue
fi
# Skip empty lines
[[ -z "$line" ]] && continue
# Parse cron expression and command
cron_expr=$(echo "$line" | awk '{print $1,$2,$3,$4,$5}')
command=$(echo "$line" | cut -d' ' -f6-)
# Get human-readable explanation
explanation=$(cron-explain "$cron_expr")
# Get next 3 run times
next_runs=$(cron-explain "$cron_expr" --next 3 --format json | \
jq -r '.nextRuns[].formatted' | sed 's/^/ - /')
# Append to documentation
cat >> "$OUTPUT" << EODOC
## $(basename "$command")
**Schedule:** \`$cron_expr\`
**Description:** $explanation
**Command:** \`$command\`
**Next 3 runs:**
$next_runs
---
EODOC
done
echo "✅ Documentation generated: $OUTPUT"
# Commit to git
git add "$OUTPUT"
git commit -m "docs: Update cron jobs documentation [auto-generated]"Run this weekly:
0 9 * * 1 /scripts/document-crons.shAdvanced Tips & Tricks
Tip 1: Testing Cron Schedules Before Deployment
Dry-run your cron schedules:
#!/bin/bash
# Test cron schedule without waiting
CRON_SCHEDULE="*/5 * * * *" # Every 5 minutes
COMMAND="/path/to/script.sh"
echo "Testing cron: $CRON_SCHEDULE"
cron-explain "$CRON_SCHEDULE" --next 5
# Simulate next 5 runs instantly
for i in {1..5}; do
echo ""
echo "=== Simulated run #$i at $(date) ==="
$COMMAND
echo "Exit code: $?"
sleep 1 # Instead of waiting full interval
doneTip 2: Cron to systemd Timer Conversion
Migrate from cron to systemd timers:
#!/bin/bash
# Convert cron expression to systemd timer
CRON_EXPR="0 3 * * *"
SERVICE_NAME="backup"
# Get explanation
DESCRIPTION=$(cron-explain "$CRON_EXPR")
# Generate systemd timer unit
cat > "/etc/systemd/system/${SERVICE_NAME}.timer" << EOF
[Unit]
Description=$DESCRIPTION
Requires=${SERVICE_NAME}.service
[Timer]
# Original cron: $CRON_EXPR
OnCalendar=daily
# More precise: OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
echo "✅ Created ${SERVICE_NAME}.timer"
echo "💡 Enable with: systemctl enable --now ${SERVICE_NAME}.timer"
echo "📅 Check schedule: systemctl list-timers"Tip 3: Overlap Detection
Prevent long-running jobs from overlapping:
#!/bin/bash
# Prevent concurrent runs of the same cron job
LOCKFILE="/var/lock/backup.lock"
CRON_SCHEDULE="0 */6 * * *" # Every 6 hours
# Check if previous run is still going
if [ -f "$LOCKFILE" ]; then
# How long has it been running?
LOCK_AGE=$(($(date +%s) - $(stat -f %m "$LOCKFILE")))
# Get expected interval from cron
INTERVAL=$(cron-explain "$CRON_SCHEDULE" --next 2 --format json | \
jq -r '(.nextRuns[1].timestamp | fromdateiso8601) - (.nextRuns[0].timestamp | fromdateiso8601)')
if [ $LOCK_AGE -gt $INTERVAL ]; then
echo "⚠️ Previous run exceeded interval ($LOCK_AGE > $INTERVAL seconds)"
echo "🚨 Possible stuck process! Check logs."
# Send alert
else
echo "ℹ️ Previous run still in progress (${LOCK_AGE}s). Skipping."
fi
exit 0
fi
# Acquire lock
touch "$LOCKFILE"
trap "rm -f $LOCKFILE" EXIT
# Run your job
/path/to/backup.shTip 4: Holiday and Business Day Handling
Skip jobs on holidays:
#!/bin/bash
# scripts/business-days-only.sh
CRON_SCHEDULE="0 9 * * 1-5" # Weekdays at 9 AM
HOLIDAYS_FILE="/etc/holidays.txt" # List of YYYY-MM-DD dates
# Check if today is a holiday
TODAY=$(date +%Y-%m-%d)
if grep -q "^$TODAY$" "$HOLIDAYS_FILE"; then
echo "🎉 Today is a holiday ($TODAY). Skipping job."
exit 0
fi
# Check next run isn't on a holiday
NEXT_RUN=$(cron-explain "$CRON_SCHEDULE" --next 1 --format json | \
jq -r '.nextRuns[0].timestamp' | cut -d'T' -f1)
if grep -q "^$NEXT_RUN$" "$HOLIDAYS_FILE"; then
echo "📅 Next run ($NEXT_RUN) is a holiday. Consider rescheduling."
fi
# Run job
/path/to/script.shTip 5: Intelligent Retry Mechanism
Retry failed cron jobs with exponential backoff:
#!/bin/bash
# Cron job wrapper with retry logic
CRON_SCHEDULE="0 2 * * *"
MAX_RETRIES=3
RETRY_DELAY=300 # 5 minutes
function run_with_retry() {
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
echo "Attempt $attempt/$MAX_RETRIES at $(date)"
if /path/to/job.sh; then
echo "✅ Success on attempt $attempt"
return 0
fi
if [ $attempt -lt $MAX_RETRIES ]; then
delay=$((RETRY_DELAY * attempt))
echo "⚠️ Failed. Retrying in ${delay}s..."
sleep $delay
fi
((attempt++))
done
echo "❌ Failed after $MAX_RETRIES attempts"
# Alert ops team
NEXT_RUN=$(cron-explain "$CRON_SCHEDULE" --next 1)
echo "Next scheduled run: $NEXT_RUN" | \
mail -s "Cron Job Failed: $(basename $0)" [email protected]
return 1
}
run_with_retryTip 6: Cron Visualization Dashboard
Generate a visual schedule:
#!/bin/bash
# scripts/cron-dashboard.sh - Generate HTML visualization
OUTPUT="dashboard/cron-schedule.html"
mkdir -p dashboard
cat > "$OUTPUT" << 'EOHTML'
<!DOCTYPE html>
<html>
<head>
<title>Cron Schedule Dashboard</title>
<style>
body { font-family: monospace; padding: 20px; }
.job { margin: 20px 0; padding: 15px; background: #f5f5f5; border-left: 4px solid #4CAF50; }
.next-runs { margin-left: 20px; }
.timeline { margin: 20px 0; }
</style>
</head>
<body>
<h1>🕐 Cron Schedule Dashboard</h1>
<p>Generated: $(date)</p>
EOHTML
crontab -l | while IFS= read -r line; do
[[ "$line" =~ ^#.*$ ]] || [[ -z "$line" ]] && continue
cron_expr=$(echo "$line" | awk '{print $1,$2,$3,$4,$5}')
command=$(echo "$line" | cut -d' ' -f6-)
description=$(cron-explain "$cron_expr")
next_runs=$(cron-explain "$cron_expr" --next 5 --format json | \
jq -r '.nextRuns[] | " <li>\(.formatted) <em>(\(.fromNow))</em></li>"')
cat >> "$OUTPUT" << EOJOB
<div class="job">
<h3>$(basename "$command")</h3>
<p><strong>Schedule:</strong> <code>$cron_expr</code></p>
<p><strong>Description:</strong> $description</p>
<p><strong>Next 5 runs:</strong></p>
<ul class="next-runs">
$next_runs
</ul>
</div>
EOJOB
done
cat >> "$OUTPUT" << 'EOFHTML'
</body>
</html>
EOFHTML
echo "✅ Dashboard generated: $OUTPUT"
open "$OUTPUT" # macOS; use xdg-open on LinuxTip 7: Audit Trail for Cron Changes
Track who changed what in crontab:
#!/bin/bash
# /usr/local/bin/crontab-wrapper
# Wrap crontab command to log changes
AUDIT_LOG="/var/log/crontab-changes.log"
ORIGINAL_CRONTAB="/tmp/crontab.before.$$"
NEW_CRONTAB="/tmp/crontab.after.$$"
# Backup current crontab
crontab -l > "$ORIGINAL_CRONTAB" 2>/dev/null || touch "$ORIGINAL_CRONTAB"
# Run original crontab command
/usr/bin/crontab "$@"
EXIT_CODE=$?
# Capture new crontab
crontab -l > "$NEW_CRONTAB" 2>/dev/null || touch "$NEW_CRONTAB"
# Log changes
if ! diff -q "$ORIGINAL_CRONTAB" "$NEW_CRONTAB" > /dev/null 2>&1; then
cat >> "$AUDIT_LOG" << EOLOG
================================================================================
Date: $(date)
User: $(whoami)
Host: $(hostname)
Command: crontab $@
Changes:
$(diff -u "$ORIGINAL_CRONTAB" "$NEW_CRONTAB")
New schedule breakdown:
EOLOG
# Explain all new cron expressions
grep -v '^#' "$NEW_CRONTAB" | while read line; do
[[ -z "$line" ]] && continue
cron_expr=$(echo "$line" | awk '{print $1,$2,$3,$4,$5}')
echo " $cron_expr → $(cron-explain "$cron_expr")" >> "$AUDIT_LOG"
done
echo "" >> "$AUDIT_LOG"
fi
# Cleanup
rm -f "$ORIGINAL_CRONTAB" "$NEW_CRONTAB"
exit $EXIT_CODEInstall:
sudo mv /usr/bin/crontab /usr/bin/crontab.real
sudo ln -s /usr/local/bin/crontab-wrapper /usr/bin/crontabPerformance Tips
Tip 1: Understand Step Values Performance
# Efficient: Runs at predictable times
*/15 * * * * # Every 15 minutes (:00, :15, :30, :45)
# Inefficient: Non-divisible step
*/13 * * * * # Only at :00 (wastes cron checks)
# Check actual execution times:
cron-explain "*/13 * * * *" --next 10Tip 2: Batch Jobs Instead of High Frequency
# Instead of every minute (1440 runs/day):
* * * * * /path/to/check.sh
# Consider every 5 minutes (288 runs/day):
*/5 * * * * /path/to/check.sh
# Or queue-based approach (run when needed):
# Use inotify, message queue, or webhook insteadTip 3: Spread Load Across Time
# Don't schedule everything at midnight:
0 0 * * * backup1.sh
0 0 * * * backup2.sh
0 0 * * * backup3.sh
# Spread them out:
0 0 * * * backup1.sh
30 0 * * * backup2.sh
0 1 * * * backup3.sh
# Visualize with:
cron-explain "0 0 * * *" --next 5
cron-explain "30 0 * * *" --next 5
cron-explain "0 1 * * *" --next 5Tip 8: Resource-Aware Scheduling
Schedule resource-intensive jobs during off-peak hours:
#!/bin/bash
# Auto-schedule based on system load
CRON_SCHEDULE="0 */2 * * *" # Try every 2 hours
LOAD_THRESHOLD=2.0
# Check current load
CURRENT_LOAD=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | tr -d ',')
if (( $(echo "$CURRENT_LOAD > $LOAD_THRESHOLD" | bc -l) )); then
echo "⏸️ System load too high ($CURRENT_LOAD > $LOAD_THRESHOLD). Deferring."
# Calculate next low-load window (historically 3-5 AM)
DEFER_TO="0 3 * * *"
NEXT_WINDOW=$(cron-explain "$DEFER_TO" --next 1)
echo "📅 Will retry at $NEXT_WINDOW"
exit 0
fi
# Proceed with job
/path/to/heavy-job.shChangelog
See CHANGELOG.md for version history.
Roadmap
- [ ] Support for extended cron syntax (seconds field)
- [ ] More natural language patterns
- [ ] Visual calendar view of schedule
- [ ] Conflict detection (overlapping jobs)
- [ ] Integration with crontab editing
- [ ] Web UI for visual builder
- [ ] Export to systemd timers
- [ ] Support for Quartz cron syntax
- [ ] Mobile app for quick cron checks
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Development Setup
# Clone the repository
git clone https://github.com/muin-company/cli-tools.git
cd cli-tools/packages/cron-explain
# Install dependencies
npm install
# Build
npm run build
# Test
npm test
# Run locally
node dist/cli.js "0 9 * * 1-5"FAQ
Q: Does this modify my crontab?
A: No! It only reads and explains. Use --save flag if you want to add to crontab (coming soon).
Q: Can it explain non-standard cron formats?
A: Currently supports standard 5-field cron. Support for 6-field (seconds) coming soon.
Q: What about Windows Task Scheduler?
A: Not supported. This is specifically for Unix/Linux cron syntax.
Q: Can I use this in scripts?
A: Yes! Use --format json for machine-readable output.
Q: Does it support all cron implementations?
A: Supports standard cron syntax (vixie-cron, ISC cron, etc.). Some advanced features may vary.
Q: Can it generate code to calculate cron times?
A: Not yet, but you can use --format json and parse the nextRuns array.
Q: Is there an API or library version?
A: Not yet, but it's planned. Follow @muin_company for updates.
License
MIT © MUIN
Related Projects
- @muin/curl-to-code - Convert curl commands to code
- @muin/json-to-types - Convert JSON to TypeScript/Zod/Python types
- cron-parser - Low-level cron parsing library
- More MUIN tools
Support
Acknowledgments
- Inspired by crontab.guru - Great web tool for cron
- Built with cron-parser
- Uses chalk for colored output
- Timezone support via moment-timezone
Made with ❤️ by MUIN - Building AI-powered developer tools
