Skip to content

Crontab calendar generator

(for want of a better name).

Link to github repo:

The problem: a crontab file containing hundreds of entries which execute jobs at many different times, and we want to know what runs when, or, perhaps more interestingly, what will run when. All displayed in a calendar-like format, so we can see that on day X, job Y will run at 9:30 and job Z will run at 00:00, 06:00, 12:00 and 18:00 (for example).
Obviously, "manually" extracting this information by looking at the crontab file itself is going to be an exercise in frustration if there are more than a handful entries (and even then, depending on how they are defined, it would probably require some messing around).

We'd like to have some program that takes a time range (start and end date or start date and duration) and a crontab file to read, and automagically produces the calendar output for the given period. Example follows to better illustrate the concept.

# Sample crontab file for this example

# runs at 5:00 every day
0 5 * * * job1

# runs at 00:00 every day
@daily job2

# runs every 7 hours every day, between 7 and 17, that is at 07:00 and 14:00
0 7-17/7 * * * job3

# runs at 17:30 on day 10 of each month
30 17 10 * * job4

We want to see the job timeline for the time range between 2012-06-09 00:00 and 2012-06-12 00:00. So we run it thus:

$ -s '2012-06-09 00:00' -e '2012-06-12 00:00' -f /path/to/crontab
2012-06-09 00:00|job2
2012-06-09 05:00|job1
2012-06-09 07:00|job3
2012-06-09 14:00|job3
2012-06-10 00:00|job2
2012-06-10 05:00|job1
2012-06-10 07:00|job3
2012-06-10 14:00|job3
2012-06-10 17:30|job4
2012-06-11 00:00|job2
2012-06-11 05:00|job1
2012-06-11 07:00|job3
2012-06-11 14:00|job3

That's basically the idea of the program described in this article. Running it with -h will print a summary of the options it accepts. Output can be in icalendar format (so the timeline can be visually seen with any calendar application), plain as above, or we can just print how many jobs would run at each time. When using the plain or icalendar formats, mostly as a debugging aid, it's possible to print the job scheduling spec as was originally found in the crontab file. This is done with the -x switch, example follows:

$ -s '2012-06-09 00:00' -e '2012-06-12 00:00' -f /path/to/crontab -x
2012-06-09 00:00|@daily|job2
2012-06-09 05:00|0 5 * * *|job1
2012-06-09 07:00|0 7-17/7 * * *|job3
2012-06-09 14:00|0 7-17/7 * * *|job3
2012-06-10 00:00|@daily|job2
2012-06-10 05:00|0 5 * * *|job1
2012-06-10 07:00|0 7-17/7 * * *|job3
2012-06-10 14:00|0 7-17/7 * * *|job3
2012-06-10 17:30|30 17 10 * *|job4
2012-06-11 00:00|@daily|job2
2012-06-11 05:00|0 5 * * *|job1
2012-06-11 07:00|0 7-17/7 * * *|job3
2012-06-11 14:00|0 7-17/7 * * *|job3

This should help confirm that the job should indeed run at the time shown in the first column (or not: there may be bugs!). Since the program reads from standard input if an explicit filename is not specified, it's possible to output the timeline resulting from multiple crontab files, for example by doing

cat crontab1 crontab2 crontabN | [ options ]

Ok, semi-UUOC but I was too lazy to implement multiple -f options.

Final words of caution:

  • if you run the program over a long period of time and have cron jobs that run very often, like "* * * * *" or similar, that will produce a lot of output.
  • The program was written as a quick and dirty way to solve a specific need, "works for me", is not optimized and does not try to be particularly smart of flexible. It may not be exactly what you were looking for, or may not do what you want, or in the way you want. That's life. Hopefully, it may still be useful to someone.


  1. Andre says:

    I had to fix latency issues on a server without access to many log files.

    So croncal generated the missing data from a larger crontab, and I correlated these events with events from another logfile with latency info through remote monitoring.

    Helped me alot, thanks!

  2. Will says:

    Consider this more demand :) Given that one user of the script already set up a repo, it doesn't seem like much effort. In fact it takes about 60 seconds. Two bugs fixed in a year is much better than no bugs fixed at all.

    You're missing the point that by putting it in github, you would allow others to more easily fork it, make improvements and updates themselves, and submit pull requests back to you -- free additional development at little effort from you!

    The bug reports and feature requests you say you welcome would fit perfectly into github as that's specifically what it's designed for.

  3. Andy C says:

    Thanks for this utility. It's almost perfect for me. I was actually looking for (a) a utility do what this does, but then part (b) was to find a way to analyse cron to look for scheduled mainteance/down times to perform server restarts. If anyone has modified this code or has a suggestion to format the output of this to identify when jobs are not actually running that would be great. You can probably guess our crontab is maxed out and has stuff running almost constantly. I am trying to find the needle in the haystack.

    • waldner says:

      Maybe I don't understand your need, but can't you get what you need by running grep on the output of croncal to only show entries that you want to see?

  4. A GitHub repo would be most welcome.

  5. Uwe says:
    --- /tmp/     2014-10-27 09:34:53.650287908 +0100
    +++  2014-10-27 09:34:01.182754504 +0100
    @@ -357,6 +357,7 @@
       # expand stars
    +  if(not defined($field)) { $field = '' };
       $field =~ s/\*/$min-$max/g;
       # replace dow and month names with numbers
    @@ -484,7 +485,8 @@
       # same as specifying a real star. It's easy to enhance the code to
       # include those cases, though.
    -  my ($stardom, $stardow) = ($parsed{dom_orig} eq '*', $parsed{dow_orig} eq '*');
    +  my ($stardom, $stardow) = ((defined($parsed{"dom_orig"}) and ($parsed{"dom_orig"} eq '*')),
    +         (defined($parsed{"dow_orig"}) and ($parsed{"dow_orig"} eq '*')));
       return (exists($parsed{min}{$min}) and
               exists($parsed{hour}{$hour}) and
    • waldner says:

      Thanks. I'd say the fix here is rather

      ---	2014-04-21 23:55:41.000000000 +0200
      +++	2014-10-27 19:05:17.409707290 +0100
      @@ -152,8 +152,10 @@
             %raw = ('min' => '0', 'hour' => '*', 'dom' => '*', 'month' => '*', 'dow' => '*', 'command' => $3);
           } elsif ($type eq 'reboot') {
             print STDERR "Warning, skipping \@reboot entry at line $.: $_\n";
      +      next;
           } else {
             print STDERR "Unknown line/cannot happen at line $., skipping: $_\n";
      +      next;
         } elsif (/^((\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+))\s+(.*)/) {

      Or do you have some other sample input that leads to having $field undefined at the point where you added the check for definedness?

      • Uwe says:

        This fixes the undef warnings, too. They were caused by a @reboot line.

        • waldner says:

          Yes, that's what my patch fixes, but it just prevents it from becoming undef in a single place, rather than checking a posteriori in several places.

  6. mabbrew says:

    I think this is great. Pretty much exactly what i needed. Only wish is that the >/dev/null 2>&1 stuff that typically follows the job wasn't included.

    • waldner says:

      I think it depends. In most environments I've seen, "typically" commands are not followed by that stuff. "Typically", when commands executed in cron are followed by those redirections, it's a symptom that someone is trying to sweep the dust under the carpet. Redirecting everything to /dev/null is a good way to make it impossible or very difficult to detect whether the program is running correctly or not.
      Commands that are executed by cron should behave correctly (that is, not output anything if there's no error), so redirection should normally not be necessary.

      And anyway, if we go down that path we must implement a shell syntax parser, which is not the goal for the program. You can always postprocess the output of croncal to remove those parts yourself. Thanks.

  7. Joey says:

    Thank you for this script! I have been heavily modifying it to output in an XML format that I can integrate into a web application.

    Today I found it wasn't parsing jobs that are specified on day 7 properly. There is code to silently convert day 7 to 0, but it never gets called.

    On this line, you declare the minimum and maximum

    ($min, $max, $desc) = (0, 6, 'day of week');    # 0 or 7 is sunday, 7 is silently converted to 0

    And later in the script you first check to see if the value is between the min/max, but then, within that if statement, you do the conversion from 7 to 0.

    if ($item >= $min and $item <= $max) {
            if ($type eq 'dow' and $item eq '7') {
              $item = '0';

    The code in the 2nd if statement never runs due to the first check that checks to see if the value is within the allowed range. Anyway, the fix I implemented is to just change the maximum declaration to 7 instead of 6, then it actually does silently convert it and I stop getting errors from a user's invalid cron jobs scheduled for day 7.

  8. Nelson says:

    Great work, thanks for sharing!

    I have used it with large crontabs coming from several servers, and found a couple of small things that I had to change on my files for this script to work. Just in case you are interested on improving it, they were:
    - zeros to the left on the minute/hour columns makes those records to be ignored
    - repeating expressions for hour/minute (*/N) are not recognized and those records are ignored


    • waldner says:

      The first one is indeed a bug, which is now fixed, thanks! There was another bug as well, the program wasn't checking for interval step 0 eg */0 which shouldn't occur in practice but better to be robust.

      However, regarding your second issue, intervals work fine for me:

      echo '*/15 */6 * * * foobar' | -s '2012-06-09 00:00' -e '2012-06-10 00:00'
      2012-06-09 00:00|foobar
      2012-06-09 00:15|foobar
      2012-06-09 00:30|foobar
      2012-06-09 00:45|foobar
      2012-06-09 06:00|foobar
      2012-06-09 06:15|foobar
      2012-06-09 06:30|foobar
      2012-06-09 06:45|foobar
      2012-06-09 12:00|foobar
      2012-06-09 12:15|foobar
      2012-06-09 12:30|foobar
      2012-06-09 12:45|foobar
      2012-06-09 18:00|foobar
      2012-06-09 18:15|foobar
      2012-06-09 18:30|foobar
      2012-06-09 18:45|foobar

      Do you have a particular input that triggers the issue?

  9. Anni says:

    Another crontab syntax generator is at

  10. minWi says:

    Thank you SO MUCH for this little gem.
    It helped me a LOT :)

    You should create a github project for it and publish a little, because it's great!

    • bl4sphemy says:


    • Carl Cravens says:

      Ditto. I've just deployed this to all my production Linux servers, and I'd like to know when bug fixes/enhancements are added, and github would be ideal.

      • waldner says:

        Well, given the low traffic I don't know if setting up a github repo is worth the effort, also considering that in about one and a half year there have been exactly zero new features added and a whopping two bugs fixed. In other words, don't hold your breath :-)
        This doesn't mean, of course, that bug reports and/or feature requests are not welcome; rather the contrary.
        If there is more demand I may consider setting up a github repo. Thanks!