EventBridgeのcron式のパーサーを作った話とDay of weekの増分の仕様

追記3

もろもろ作り直した

github.com

追記2

いろいろ間違っていた…

追記

1-10/3 とか MON-FRI/3 とかを忘れていたので作り直し…


cron式をパースするGolangのライブラリを書いた。

github.com

モチベーションとしては、Amazon EventBridgeのcron式を書くときに毎回JSTからUTCに変換するのがめんどくさくて「いつの日かterraformにユーザー定義関数が追加されるときに備えて(くるのか?)タイムゾーンをシフトするライブラリを書こう」という感じで始めた。

パーサー自体は alecthomas/participle でサクサク書けたが

github.com

時間をシフトする処理は難しくて諦めた。

たとえば 0 5-10 ? * FRI * のようなJSTのcron式をUTCに直すと 0 20-23 ? * THU *0 0-1 ? * FRI * の2つに分かれる。cron式の分解と合成のうまいやり方が思いつかなかった。自分が無知なだけで良い方法があるのかも


EventBridgeの仕様を参考にしたが、いくつか知らない仕様があって勉強になった。

たとえば

  • 0 0 ? * 3#2 *: 毎月の第二水曜日の00:00に実行
  • 0 0 8W * ? *: 毎月8日に一番近い平日に実行
    • 2022/10だと10/8は土曜日なので10/7に実行される
  • 0 0 L * ? *: 毎月の最終日の00:00に実行
  • 0 0 ? * L *: 毎週の最終日、つまり土曜日の00:00に実行

など

オラクルのドキュメントでも同様の記号が書かれていたので、AWSだけの仕様ではない…ような気がする(オラクルのほうのCの意味はわからなかった)

Day of weekの増分の仕様

時間のシフトを計算する処理は諦めたが、ASTだけあってもあまり有用ではなさそうなので、ついでとある時刻がcronのスケジュールにマッチするかどうか判別する機能も付けてみた。しかし曜日(Day of week)フィールドの処理で結構ハマってしまった…

増分の表現は、分子≒オフセット・分母≒増分と、分数のようなかたちで書く。(改めて書くほどのことでもないが)

  • 分・時フィールドの場合 */30/3 と同じで、実行される時間は 0 3 6 9 …となる。1/3の場合は1 4 7 10 …
  • 日(Day of month)フィールドだと、*/31/3 と同じ。実行される日付は 1 4 7 10 13…
  • さらに年フィールドの場合は、*/31970/3 と同じになる模様(AWSコンソール調べ)

曜日(Day of week)フィールドの場合、*/31/3 と同じ結果になる。

曜日の数値 1-7 は MON-SUN にマップされているので MON(1) THU(4) SUN(7)… となると思いきや、実はSUN始まりで SUM(0) WED(3) SAT(6)… となる。

曜日(Day of week)を考えないと、時刻をnとしたときに n % (分母) == (分子) という計算でその時刻がcronを実行する時刻なのか判別できるが、曜日(Day of week)は他と分子の扱いが違うので特別な処理を挟む必要があって、だいぶ実装に時間が取られてしまった。

これLinuxでも同様の動作なのかな

あと、EventBridgeだとMON-SUNが1-7に対して、プログラム上だとSUN-SATが0-6になっているので細々した変換が必要で、そちらもすこしめんどくさかった。