Hacker News new | past | comments | ask | show | jobs | submit login
Schedule cronjob for the first Monday of every month, the funky way (healthchecks.io)
125 points by todsacerdoti 12 months ago | hide | past | favorite | 38 comments

I think this just speaks to a design wart in the seemingly simple crontab specification. I'm personally more of a fan of iCal-style (RFC5545) recurrence specification. Just check out the examples in https://www.rfc-editor.org/rfc/rfc5545#section-

Here are some thing that's possible:

    Weekly on Tuesday and Thursday for five weeks
    Every other week on Monday, Wednesday, and Friday until December 24, 1997, starting on Monday, September 1, 1997
    Every other month on the first and last Sunday of the month for 10 occurrences
    Every 18 months on the 10th thru 15th of the month
(Almost all of those are well supported by the UI on Apple Calendar and Google Calendar.)

I only wish it could incorporate astronomical phenomena such as phases of the moon into account. Wouldn't it be great if it can express concepts like "the first Sunday after the full moon that occurs on or after the spring equinox" (no I'm not making this example up)?

There is a trend towards turing-complete configuration languages, so why not. Of course there might be a race condition if the computation takes longer than the remaining time to the computed date.

This is so easy, just interpret a few hundred words :)


… and you would still be wrong, depending in which country it gets calculated.

The things that are possible that are specified in the the parent is not how you actually write them fyi

> I only wish it could incorporate astronomical phenomena such as phases of the moon into account

You probably could hack something up around rfc 7529 (non-gregorian recurrence)

Phases of the moon are still important if you live near a body of water with strong tides. They also used to be important as a source of light.

At that point you might as well put a code interpreter in your scheduler that just gets current date and returns next data of event, because whole code to cover that would take more code than cron itself.

Teach GPT-3 cron syntax, done.

"What do you mean we need some NVIDIA cards to run our job scheduler ?"

I guess you can just say "Easter 2023"

The idea is not to hard code the computation of specific holidays, but to make the specification flexible enough.

Once dates get too complex it is easier to just have an external script figure out whether today is the day and have it run every day:

  0 9 * * * anvandare is-today-the-day.py && celebrate.sh
With is-today-the-day.py being:

  #!/usr/bin/env python3
  import sys
  from dateutil.easter import easter
  from datetime import datetime as dt
  today = dt.today().date()
  (easter(2023) - today).days == 0 and sys.exit(0) or sys.exit(1)
Or, if we didn't have the easter function already (just adding this because it was fun to work it out):

  #!/usr/bin/env python3
  import sys, ephem as e, datetime as dt
  setup = '2022/09/25'
  date = (e.next_full_moon(e.next_equinox(setup))).datetime()
  easter2023 = (date + dt.timedelta(7 - date.isoweekday())).date()
  today = dt.datetime.today().date()
  (easter2023 - today).days == 0 and sys.exit(0) or sys.exit(1)

Orthodox or Catholic?

gauß doesn't care, the changes are minimal.

No one said it was 2023

I wrote an RFC5545 parser and event generator once. No thanks.

So you saved all your library users the work needed for a powerful event generator system, what's wrong with that?

That was 1/3 of the problem domain of scheduling things. The other 2/3 was made considerably more difficult by it.

Unfortunately the crontab englishfier doesn't do a great job of describing it,


This is a nice hack, but readability has gone out the window and it's really just much safer to fail early in the job itself.

> At 00:00 on every 100th day-of-month and every day-of-month from 1 through 7 if it's on Monday.

I guess I’m confused. Isn’t that exactly correct?

It reads like the “if” clause only pertains to the second “every”, but it also applies to the first. In this instance it doesn’t make a real difference, but presumably the Englishfier doesn’t know that.

It is correct, but a lot wordier than "At midnight on the first Monday of every month."

Instead of tricking the syntax, I just check in the command. I have a cron entry set up to run every Monday, but I limit it to the start of the month with: [ $(date +\%d) -le 07 ] && <actual command>

Given the odd quirks and number of cron “translation” sites, it strikes me that the cron syntax might not be the best approach to scheduling tasks.

There is surely a better DSL for this.

Personally I like ruby’s whenever gem, that transpiles to cron, where you can do things like

  every :sunday, at: '12pm' do
But I guess we are just stuck with crontab

With systemd if I'm not mistaken this is just:

    OnCalendar=Mon *-*-* 12:00:00
Here's the list of examples from man systemd.time:

       The following special expressions may be used as shorthands for longer normalized forms:

               minutely → *-*-* *:*:00
                 hourly → *-*-* *:00:00
                  daily → *-*-* 00:00:00
                monthly → *-*-01 00:00:00
                 weekly → Mon *-*-* 00:00:00
                 yearly → *-01-01 00:00:00
              quarterly → *-01,04,07,10-01 00:00:00
           semiannually → *-01,07-01 00:00:00

       Examples for valid timestamps and their normalized form:

             Sat,Thu,Mon..Wed,Sat..Sun → Mon..Thu,Sat,Sun *-*-* 00:00:00
                 Mon,Sun 12-*-* 2,1:23 → Mon,Sun 2012-*-* 01,02:23:00
                               Wed *-1 → Wed *-*-01 00:00:00
                      Wed..Wed,Wed *-1 → Wed *-*-01 00:00:00
                            Wed, 17:48 → Wed *-*-* 17:48:00
           Wed..Sat,Tue 12-10-15 1:2:3 → Tue..Sat 2012-10-15 01:02:03
                           *-*-7 0:0:0 → *-*-07 00:00:00
                                 10-15 → *-10-15 00:00:00
                   monday *-12-* 17:00 → Mon *-12-* 17:00:00
             Mon,Fri *-*-3,1,2 *:30:45 → Mon,Fri *-*-01,02,03 *:30:45
                  12,14,13,12:20,10,30 → *-*-* 12,13,14:10,20,30:00
                       12..14:10,20,30 → *-*-* 12..14:10,20,30:00
             mon,fri *-1/2-1,3 *:30:45 → Mon,Fri *-01/2-01,03 *:30:45
                        03-05 08:05:40 → *-03-05 08:05:40
                              08:05:40 → *-*-* 08:05:40
                                 05:40 → *-*-* 05:40:00
                Sat,Sun 12-05 08:05:40 → Sat,Sun *-12-05 08:05:40
                      Sat,Sun 08:05:40 → Sat,Sun *-*-* 08:05:40
                      2003-03-05 05:40 → 2003-03-05 05:40:00
            05:40:23.4200004/3.1700005 → *-*-* 05:40:23.420000/3.170001
                        2003-02..04-05 → 2003-02..04-05 00:00:00
                  2003-03-05 05:40 UTC → 2003-03-05 05:40:00 UTC
                            2003-03-05 → 2003-03-05 00:00:00
                                 03-05 → *-03-05 00:00:00
                                hourly → *-*-* *:00:00
                                 daily → *-*-* 00:00:00
                             daily UTC → *-*-* 00:00:00 UTC
                               monthly → *-*-01 00:00:00
                                weekly → Mon *-*-* 00:00:00
               weekly Pacific/Auckland → Mon *-*-* 00:00:00 Pacific/Auckland
                                yearly → *-01-01 00:00:00
                              annually → *-01-01 00:00:00
                                 *:2/3 → *-*-* *:02/3:00

> With systemd if I'm not mistaken this is just:

i'm not familiar with the systemd syntax but according to the desc you provided, wouldnt that be a weekly monday job, not "first monday of the month" the article wanted?

Here's "first monday of every month" in systemd.time(7) format:

    $ systemd-analyze calendar 'Mon *-*-1..7 10:00'
      Original form: Mon *-*-1..7 10:00
    Normalized form: Mon *-*-01..07 10:00:00
        Next elapse: Mon 2022-10-03 10:00:00 BST
           (in UTC): Mon 2022-10-03 09:00:00 UTC
           From now: 1 week 1 day left
Credit: https://www.redpill-linpro.com/sysadvent/2016/12/07/systemd-...

systemd timers have a good chance to do what you want the first time, unlike cron.

Big time upvote for informing me systemd has timers.

systemd can also manage running your cron scripts itself, with `systemd-cron`


Systemd whitens teeth, strengthens bridges and stabilizes the cosmos. It's also good on sandwiches and has limitless applications for everything. It's the most viable explanation for gravity by far. It deserves our prayers. It permeates the aether. Nothing prevents it from ruling the universe.

If you can write the DSL and a reference parser, you might have a chance at adoption. Even better if you can convert from and to crontab.

an excellent example of how not to do things. if you need something esoteric enough that no one is going to grok the crontab without thought then you use something else.

Sometimes you have constraints and are limited to crontabs. For example when you are using autoscaling on AWS EC2 (https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-au...).

I know, with eventbridge and lambdas this problem could be alleviated a bit, since eventbridge uses cron syntax but with some added bells and whistles ( https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-...) which would make the expression for the first monday of each month much easier to write.

A bit OT but I’ve been using healthchecks.io for my backup crons on their free tier and it’s been very easy and straightforward.

I’m not affiliated, just a happy user. Having alerts when your cron fails is quite nice.

their software is also very easy to host yourself. It's a great tool.

In ISO 8601 syntax this would be something like


Applications are open for YC Winter 2024

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact