おはようございます。まさきんです。
家のリマインダーや AI の定期タスクを、cron の書式で組むことが増えてきました。そのなかで「毎月第1土曜」を書こうとして、見事に罠を踏んでいたことに気づきました。同じ書き方をしている人は意外と多いかもしれないので、仕様と回避策を整理しておきます。
※記事公開時点の情報です。最新の条件は公式サイトでご確認ください。
- cron で日と曜日を両方指定すると、AND ではなく OR で判定される
- なので「0 9 1-7 * 6」は第1土曜ではなく、1〜7日の毎日と毎週土曜に動く
- 回避策は曜日だけで起動し、日付の判定はスクリプト側でやること
- 一部の実行環境には「第何曜日」を表す拡張構文もあるが、使えるかは環境しだい
- 書いたら次回の実行日を並べて検算し、UTC とのずれも確認する
月1回のつもりが、月初は毎日動く形に
きっかけは、月初の土曜に送る買い出しのリマインダーでした。月に1回だけ届けばいいので、次のように書いていました。
0 9 1-7 * 6
「1〜7日」かつ「土曜」なら、月の最初の土曜が1日だけ決まるはずです。人間の感覚だと、そう読めますよね。
ただ、実際の動きは違いました。毎月1〜7日の毎日と、毎週の土曜に発火する形になっていたんです。月1回のつもりが、月の前半は毎朝届くリマインダーになっていました。
5つの欄のおさらい
cron の書式は、スペースで区切った5つの欄でできています。左から順に次の意味です。
分 時 日 月 曜日
0 9 1-7 * 6
「*」は「どれでもいい」という意味です。「1-7」のような範囲や、「1,15」のような列挙も書けます。曜日は多くの環境で 0 が日曜、6 が土曜です。
ここまでは素直な仕組みです。問題は「日」と「曜日」の2つの欄の関係にあります。
日と曜日は OR で判定される
POSIX の crontab の仕様には、次の趣旨が書かれています。日の欄と曜日の欄が両方とも具体的に指定されている場合の話です。そのときは、どちらかに一致した日が対象になるとされています。
Linux でよく使われる cronie の man ページにも、同じ説明があります。両方の欄が「*」でないときは、どちらかが一致すれば実行されるという書き方です。例として「30 4 1,15 * 5」は、毎月1日と15日に加えて毎週金曜の4時30分に動くと説明されていました。
つまり、日と曜日を両方書いた時点で「かつ」ではなく「または」になります。個人的には、ここが cron で一番直感に反するところだと思います。
- 日の欄と曜日の欄の両方に「*」以外を書くと、どちらかに一致すれば動く
- 「1-7日かつ土曜」のつもりで書いても、「1-7日または土曜」になる
- 片方が「*」なら、もう片方だけで判定されるので問題は起きない
実際にどう動くか並べてみる
2026年10月で試しに数えてみます。「0 9 1-7 * 6」が動く日を並べると、こうなります。
| 書いた式 | 意図 | 2026年10月に動く日 | 回数 |
|---|---|---|---|
| 0 9 1-7 * 6 | 第1土曜だけ | 1〜7日の毎日と、10・17・24・31日 | 11回 |
| 0 9 * * 6 | 毎週土曜 | 3・10・17・24・31日 | 5回 |
| 0 9 * * 6 + 日付判定 | 第1土曜だけ | 3日 | 1回 |
意図は月1回なのに、11回も届く計算です。1〜7日の中にある土曜は重なるので、単純な足し算よりは少し減ります。
リマインダーは通知が来るのが当たり前です。なので、回数が多すぎても違和感を持ちにくいのかもしれません。
主要な実行環境での扱い
cron の書式は、実行する環境によって少しずつ違います。公式ドキュメントで確認できた範囲を表にしました。
| 実行環境 | 曜日の数え方 | 日と曜日の両指定 | 拡張構文 | 時刻の基準 |
|---|---|---|---|---|
| POSIX の crontab | 0〜6(0が日曜) | どちらかに一致で実行 | 仕様にはなし | 仕様での明記なし |
| Linux の cronie | 0〜7(0と7が日曜) | どちらかに一致で実行 | 明記なし | CRON_TZ で指定できる |
| Cloudflare Workers の Cron Triggers | 1〜7(1が日曜) | 明記なし | L・W・# に対応 | UTC |
| GitHub Actions の schedule | 0〜6(0が日曜) | POSIX の書式を使うと記載 | @daily などは非対応 | 標準は UTC、timezone 指定も可 |
注意したいのは Cloudflare の曜日です。1が日曜で7が土曜なので、他の環境と1つずれます。公式でも、まぎらわしいので SAT のような3文字の略記を使う方法をすすめていました。
GitHub Actions は、POSIX の cron 書式を使うと書かれています。日と曜日の関係まではページに書かれていません。なので、POSIX と同じ OR になる前提で組んでおくのが無難かなと思います。
回避策は「曜日で起動して日付を見る」
いちばん確実な回避策は、片方の欄を「*」にすることです。曜日だけで起動して、日付の判定はスクリプトの中でやります。
- cron は「0 9 * * 6」にして、毎週土曜の9時に起動する
- 起動したら、今日の日付を取得する
- 日付が1〜7日なら、リマインダーを送る
- 8日以降なら、何もせずに終了する
第1土曜は、必ず1〜7日のどこかに入ります。なので「土曜かつ日付が7以下」と判定すれば、月1回だけ動くことになります。AND の条件は cron に任せず、自分のコードで書くイメージですね。
この考え方は応用できます。第2土曜なら8〜14日、第3土曜なら15〜21日です。月末の土曜なら「7日後が翌月か」で判定する形になります。
シェルで書く場合の例
Linux の crontab に直接書くなら、例えば次のような形になります。
0 9 * * 6 [ "$(date +\%d)" -le 7 ] && /path/to/remind.sh
ここで「%」の前にバックスラッシュが入っているのがポイントです。cronie の man ページでは、コマンド中の「%」は改行として扱われると書かれています。エスケープしないと、意図しない形でコマンドが切れてしまいます。
行が長くなるなら、判定ごとスクリプトの中に入れるほうが読みやすいかもしれません。crontab 側は「毎週土曜に起動」だけにしておくと、あとから見ても迷いにくいと思います。
AI の定期タスクで使う場合
AI に定期タスクを任せている場合も、考え方は同じです。スケジュールは「毎週土曜9時」にしておきます。そのうえで、指示文の最初に「今日が1〜7日でなければ何もせず終了」と書きます。
Python で判定するなら、例えば次のような形です。
from datetime import datetime
from zoneinfo import ZoneInfo
today = datetime.now(ZoneInfo("Asia/Tokyo"))
if today.day > 7:
raise SystemExit(0) # 第1土曜ではないので何もしない
ここで日本時間を明示しているのが大事です。実行環境の時計は UTC のことが多いので、日付を取り違えないようにしています。
- cron 側は片方の欄を必ず「*」にする
- 第何曜日かの判定は、スクリプトか指示文の側で書く
- 日付の取得はタイムゾーンを明示して行う
拡張構文が使える環境もある
実行環境によっては、「第何曜日」を1つの式で書ける拡張があります。Cloudflare Workers の Cron Triggers は、曜日の欄で「#」が使えると表に載っていました。Quartz という scheduler に近い拡張に対応している、という説明です。
ただ、公式ページには「#」を使った具体例までは載っていませんでした。Cloudflare では曜日の数字が1始まりなので、書き方には特に注意が必要です。使うなら、デプロイ前に想定した日に動くか確かめるのがよさそうです。
一方で、POSIX の仕様や cronie の man ページには「#」は出てきません。GitHub Actions の表にも載っていない記号です。環境を移したときに動かなくなることも考えると、日付判定をコードに寄せる方法のほうが持ち運びやすいかもしれません。
| 方法 | 書きやすさ | 環境を移したとき | 向いている場面 |
|---|---|---|---|
| 曜日で起動し日付を判定 | コードが少し増える | そのまま動きやすい | 家庭の自動化全般 |
| 「#」などの拡張構文 | 1行で書ける | 対応していない環境では使えない | 対応が明記された環境だけで使う |
| 日と曜日を両方書く | 見た目は簡単 | どこでも OR になりやすい | 使わないほうがよい |
次回の実行日を並べて検算する
cron の式を書いたら、次に動く日時をいくつか並べて確かめるのがおすすめです。今回の罠も、次の数回分を並べれば一目でわかります。
crontab.guru のように、式を自然な言葉に言い換えてくれるサイトもあります。GitHub Actions の公式ドキュメントでも、書式づくりと実行時刻の確認に使えるツールとして紹介されていました。ただ、第三者のツールです。表示が実行環境の実際の動きと必ず一致するとは限りません。拡張構文の扱いなどは、環境ごとの公式ドキュメントとあわせて見るのが安心だと思います。
自分で確かめるなら、1か月分の日付を回して条件に合う日を数えるだけでも十分です。この記事の10月の表も、日付を1日ずつ並べて数えたものです。数行のコードで済むので、式を変えるたびに回す習慣にしてもいいかもしれません。
UTC のずれにも注意
もう1つの落とし穴がタイムゾーンです。Cloudflare の Cron Triggers は UTC で動くと明記されています。GitHub Actions も標準は UTC で、timezone を指定する方法も用意されていました。
日本時間は UTC より9時間進んでいます。なので日本時間の朝9時は、UTC だとちょうど0時です。これより早い時刻だと話が変わります。例えば日本時間の土曜8時は、UTC では金曜の23時です。
この場合、cron の曜日の欄は土曜ではなく金曜にする必要があります。さらにスクリプト側の日付判定も、日本時間で行わないとずれてしまいます。曜日と日付の両方が1日ずれる可能性がある、と覚えておくとよさそうです。
家計の自動化で踏みやすい場面
この罠は、月1回のリマインドを作るときに特に踏みやすいと感じています。例えば次のような場面です。
- 月初の土曜に、まとめ買いの買い出しを思い出す
- 第1週の週末に、カードの請求額をチェックする
- 月の最初の休日に、サブスクの更新や解約を見直す
- 第2土曜に、子どもの学校関係の支払いを確認する
どれも「第何週の何曜日」という形になりやすいものです。通知が多すぎると、だんだん無視するようになってしまいます。大事な請求確認の通知まで流し読みするようになると、自動化の意味が薄れてしまうかもしれません。
家計まわりの自動チェックは、通知の頻度そのものが使い勝手を左右します。自動化で浮いた時間で、毎月の固定費も一緒に見直しておくと効果が大きいかもしれません。
僕の着地と、もう1つのつまずき
結局「毎週土曜」に変えた
ここまで回避策を書いてきましたが、僕自身は少し違う着地をしました。運用を見直して、リマインダー自体を毎週土曜の9時に変えたんです。
0 9 * * 6
月1回にこだわる理由を考えると、実はそれほど強くありませんでした。毎週届くなら、そのとき必要かどうかを自分で判断すればいいだけです。仕組みをシンプルにするのも、1つの答えかなと思います。
定期タスクの置き場所がバラバラだった
もう1つ、今回つまずいたことがあります。直そうとした定期タスクが、なかなか見つからなかったんです。
定期タスクは、クラウドのスケジュールと Mac のローカルアプリの2か所に分かれていました。名前で探しても片方にしか無く、どこにあるかを探すのに手間取りました。
置き場所ごとに一覧を作っておくと、こういうときに少し楽になるかもしれません。
よくある失敗と落とし穴
cron で定期タスクを組むときに、踏みやすい失敗をまとめておきます。
- 日と曜日を両方書き、「かつ」のつもりで「または」になる
- Cloudflare で曜日を数字で書き、1つずれた曜日に動く
- UTC と日本時間の差で、曜日や日付が前日にずれる
- crontab のコマンド中の「%」をエスケープし忘れる
- 書いたあとに次回の実行日を確かめず、数日たって気づく
- 定期タスクの置き場所が複数あり、直すべきものが見つからない
僕が踏んだのは1つ目と6つ目です。どちらも、一度知ってしまえば防ぎやすい種類の失敗だと思います。
始める前に確認すること
- 日の欄と曜日の欄のどちらかが「*」になっている
- 使う実行環境で、曜日が0始まりか1始まりかを確認した
- 実行環境のタイムゾーンがUTCか日本時間かを確認した
- 第何曜日の判定は、スクリプトか指示文の側に書いた
- 次の数回分の実行日を並べて、意図どおりか確かめた
- 定期タスクがどこに置いてあるか一覧にした
よくある質問
Q. 日と曜日を両方書くと、必ず OR になりますか?
POSIX の仕様と cronie の man ページでは、どちらかに一致すれば実行されると書かれています。一方で、実行環境によっては扱いがページに書かれていないこともあります。なので、両方を書かない形で組んでおくのがいちばん安全だと思います。
Q. 第1土曜を1行で書く方法はありませんか?
Cloudflare Workers の Cron Triggers のように、曜日の欄で「#」が使える環境もあります。ただ、標準の cron にはない拡張です。使う環境の公式ドキュメントで対応を確かめてから使うのがよさそうです。
Q. 月の最後の土曜はどう書けばいいですか?
Cloudflare では「L」を使った最終曜日の例が公式に載っています。標準の cron で書くなら、毎週土曜に起動して判定する形になります。「7日後が翌月なら最終土曜」と判定するのがわかりやすいかもしれません。
Q. 日付の判定は1〜7日で本当に大丈夫ですか?
月の最初の土曜は、必ず1日から7日のどこかに入ります。なので、土曜に起動して日付が7以下かを見れば第1土曜だけに絞れます。ただ、日付は日本時間で取得しないとずれることがあるので注意してください。
Q. 次回の実行日はどうやって確かめればいいですか?
crontab.guru のようなサイトで、式の意味を確認する方法があります。GitHub Actions の公式ドキュメントでも紹介されていました。ただ第三者のツールなので、最終的には実行環境の公式情報とあわせて確認するのが安心です。
Q. GitHub Actions で日本時間を指定できますか?
公式ドキュメントでは、標準は UTC とされています。そのうえで、IANA 形式のタイムゾーンを指定できると書かれていました。例えば Asia/Tokyo のような指定です。夏時間のある地域での注意点も書かれていましたが、日本時間なら影響は少ないと思います。
参考にした公式情報
- crontab(The Open Group Base Specifications)
- crontab(5) - Linux manual page(man7.org)
- Cron Triggers(Cloudflare Workers docs)
- Events that trigger workflows(GitHub Docs)
- Workflow syntax for GitHub Actions(GitHub Docs)
まとめ
cron で日と曜日を両方指定すると、AND ではなく OR で判定されます。なので「0 9 1-7 * 6」は第1土曜ではなく、月初の毎日と毎週土曜に動いてしまいます。曜日だけで起動して日付をコードで見れば、月1回にきちんと絞れます。
書いたあとは、次の数回分の実行日を並べて確かめるのがおすすめです。UTC とのずれや、実行環境ごとの曜日の数え方にも気をつけておくと安心です。
定期実行の時刻の決め方は、学校の連絡を自動チェックしていた話でも書いています。家計の自動チェックについては、毎晩のカード利用を自動チェックする仕組みも参考になるかもしれません。
月1回のリマインダーを組んでいる方は、一度だけ式を見直してみてください。
押すと楽天のログインページが開きます。ログイン後にキャンペーン内容が表示されます。
本記事にはアフィリエイト広告を含みます。当サイトのリンクを経由して商品・サービスへお申し込みいただいた場合、提携先企業から成果報酬を受け取ることがあります。 また、運営者は楽天グループに所属する社員であり、紹介プログラムを通じた報酬を受け取る場合があります。記事内容・評価は広告の有無にかかわらず、運営者の実体験と調査に基づき作成していますが、 上記の関係性をご理解のうえお読みください。詳しくは免責事項・アフィリエイト表記をご覧ください。
楽天モバイルの通信エリア・電波状況について気になることがある方は、公式の電波改善・調査依頼フォームからご相談いただけます。