logoalt Hacker News

simoncionyesterday at 4:40 AM1 replyview on HN

> I found the systemd time spec syntax you referenced to be logical and well thought out.

I found this amusing when in combination with

> The only thing that threw me for a loop and seems like "special magic" was

but -regardless- a careful reader notes that I never said that the Timer scheduling syntax was illogical or poorly thought out. It's at least as complicated as crontab-style time syntax, which was my entire point.

Related: Not that it's ether part of the core scheduler syntax or necessarily as nice as having it in the core syntax, but my crontab(5) suggests that one can use things like date(1) to get more fine-grained control over the time of execution:

  As noted above, skip values only operate within the time period they´re attached to.
  For example, specifying "0/35" for the minute field of a crontab entry won´t cause
  that entry to be executed every 35 minutes; instead, it will be executed twice every
  hour, at 0 and 35 minutes past.
  For more fine-grained control you can do something like this:
  * * * * * if [ $(expr \( $(date +%s) / 60 \) % 58) = 0 ]; then echo this runs every 58 minutes; fi
  0 * * * * if [ $(expr \( $(date +%s) / 3600 \) % 23) = 0 ]; then echo this runs every 23 hours on the hour; fi
  Adjust as needed if your date(1) command does not accept "+%s" as the format string
  specifier to output the current UNIX timestamp.
While I expect that you're not one of those people, I know that folks who are accustomed to working with extremely inflexible tools forget (or never learned) that these sorts of things are possible. I'm very aware that people sometimes cut off their own limbs with power tools, but that's not a good reason to ban their use.

Replies

harshrealityyesterday at 6:33 PM

Obviously you can do additional time & date checking in a shell wrapping the script or binary cron ultimately runs, just as you could do the time & date checking in the script or binary itself. That's far more complex.

Systemd enables cleaner, simpler syntax for common cases. Instead of "59 23 * * *", simply "23:59". Instead of "0 0 * * sun[day]", simply "sun[day]". 1st of the month and don't care exactly what time? Instead of "0 0 01 * *", simply "*-*-01".

Systemd started with the principle that they wanted to accept ISO8601 timestamps, then extended that with lists, increments, ranges. They developed that into a superset of basic cron capabilities, while maintaining similar syntax as best they could for increments and lists; they diverged for ranges and nth-from-last because those conflicted with the - date separator. They repurposed the little-used ~ cron randomization operator in the process. (systemd uses a separate RandomizedDelaySec line for that, which is also arguably superior because it randomizes per trigger, not on initial load of the timer)

The alternative is how AWS does it. They have separate cron() and at() syntax. at() takes timestamps. cron() takes cron+quartz syntax, mangled to remove seconds. They also have rate(value units), because apparently cron syntax is too complicated for most people. I'm sure in theory they did it to support "every 7 minutes" cases. I bet if you surveyed actual rate() AWS schedules, 99% would be cases that evenly divide a larger time unit, and could therefore be implemented with cron().

Cron syntax is more of a mess than I thought. While basic system crons don't support any advanced features, AWS and Cloudflare have adopted LW# capabilities, derived from quartz job scheduler syntax (Cloudflare references quartz explicitly). Quartz has extra fields for seconds and year (year optional, so it must go last, which breaks d m Y sequencing) so its syntax has 6 or 7 fields vs the standard 5. CF uses 5 fields, no seconds or years. AWS uses 6 fields, years but not seconds. Whenever you interact with cron-derived systems, you have to remember not just whether they support quartz-style syntax, but whether they support seconds and/or years. Is that really simpler?

The one feature I found interesting, "W", may not work the way I anticipated according to quartz's documentation. I thought "nearest weekday to the 1st of the month" might be useful because it maps to some billing cycles, but quartz (at least through 2.3) couldn't do that. The 2.3 docs say that 1W, if the 1st falls on a Saturday, triggers on the 3rd because it won't jump backwards over a month boundary. That was my only interesting use case for "W". Systemd does everything else, though it requires more verbosity in some cases, it's simpler in many others. 2.4/2.5 are from the last couple of years, and their docs are restructured and don't mention that, so maybe it was finally fixed? I'm not about to install java crud to test.

I didn't find the *-*~01 syntax that strange (last day of every month). The "fri *-*~07/1" syntax for "last friday of every month" is what I found magical, but it makes sense now that I've thought about it more.

What would you prefer for 3rd friday of the month? Cron (5-field, quartz syntax)

    0 0 ? * fri#3  (special syntax is needed because cron uses logical-or to combine day and weekday fields)
or systemd

    fri *-*-15..21 (works because systemd day and weekday are joined with logical-and)

[cf] https://developers.cloudflare.com/workers/configuration/cron...

[aws] https://docs.aws.amazon.com/scheduler/latest/UserGuide/sched...