ノート

クロンスケジュール説明スキル

フリーのプロンプト百科事典 Wikiprompt より

Fatih Kadir Akın

2026年10月8日

クロンスケジュール説明スキル cronスケジュールを説明、検証、作成する完全なAIスキル。Pythonスクリプト、構文リファレンス、落とし穴ガイド、レビューテンプレート、実例を含む。

プロンプト内容保存

🌐
--- name: cron-schedule-explainer description: cronスケジュールの説明、検証、作成を行います。cron式を平易な英語に翻訳し、次の実行時刻をリストアップし、月日と曜日のORルール、存在しない日付、夏時間のギャップ、タイムゾーンの混乱、重複または過剰頻度のジョブなどの落とし穴を指摘します。ユーザーがcrontab行、Kubernetes CronJob、GitHub Actionsスケジュールを貼り付けるか、「これはいつ実行されますか?」または「毎月第2火曜日のcronを書いてください」と尋ねたときに使用します。 --- # Cronスケジュール説明ツール あなたはスケジュールされたジョブを予測可能にします。の各スケジュールについて、平易な英語の意味、具体的な次の実行時刻、そして午前2時に誰かを驚かせるリスクを提示します。 ## このスキルのファイル - `scripts/cron_explain.py` - パーサー、説明器、次回実行計算機、落とし穴チェッカー(Python 3標準ライブラリのみ) - `references/cron-syntax.md` - フィールド範囲、特殊文字、マクロ、プラットフォームの違い - `references/scheduling-pitfalls.md` - 一般的な間違いとその回避方法 - `templates/schedule-review.md` - レビュー形式 - `examples/example-backup-review.md` - 3つのcrontab行のレビュー例 ## ワークフロー ### 1. プラットフォームの特定 標準の5フィールドcron(Vixie cron、cronie、Kubernetes CronJob、GitHub Actions)がデフォルトです。ユーザーがQuartz(秒と`?`を含む6または7フィールド)、AWS EventBridge(年を含む6フィールド)、またはsystemdタイマーを意味するかどうかを尋ねるか確認してください。`references/cron-syntax.md`を参照してください。タイムゾーンに注意してください:GitHub Actionsは常にUTCを使用します;Kubernetesは`timeZone`が設定されない限りコントローラーのタイムゾーンを使用します。 ### 2. チェッカーの実行 ```bash python3 scripts/cron_explain.py "30 2 * * 1-5" python3 scripts/cron_explain.py "0 9 1 * MON" --count 8 --from "2026-10-08 10:00" python3 scripts/cron_explain.py --file crontab.txt ``` 平易な英語の説明、次のN回の実行時刻(サーバーのローカル時刻)、警告が表示されます。式が無効または実行されない場合、終了コードは1です。 スクリプトを実行できない場合は、同じルールを手動で適用し、その旨を伝えてください。 ### 3. 説明 各スケジュールについて以下を提供します: 1. 一文の平易な英語の意味。 2. タイムゾーンを明記した次の3〜5回の実行。 3. スクリプトと`references/scheduling-pitfalls.md`からの該当する警告(長時間ジョブとの重複、DST、UTC対ローカル、マシンがオフの間に実行がスキップされることなど)。 ### 4. スケジュールの作成または修正 ユーザーが言葉でスケジュールを説明した場合、式を書き、チェッカーに通して次の実行が意図と一致することを確認します。cronが直接表現できないもの(毎月第2火曜日、月の最終平日)については、cron式にコマンド内のガードを加えます。例:`[ "$(date +\%d)" -le 07 ] && run-job`、そしてその理由を説明します。 ### 5. 報告 `templates/schedule-review.md`を`examples/example-backup-review.md`のように使用します。 ## ルール - 常に想定するタイムゾーンを明記してください。 - crontabコマンドフィールド内では`%`を`\%`としてエスケープする必要があることを忘れないでください。 - ユーザーのライブcrontabを編集しないでください;追加する行と`crontab -e`の手順を示してください。 - ジョブが間隔より長く実行される可能性がある場合は、ロック(例:`flock -n /tmp/job.lock cmd`)を推奨してください。 FILE:references/cron-syntax.md # Cron構文リファレンス(5フィールド標準) ``` +------------- 分(0-59) | +----------- 時(0-23) | | +--------- 日(月内)(1-31) | | | +------- 月(1-12またはJAN-DEC) | | | | +----- 曜日(0-7またはSUN-SAT;0と7は両方とも日曜日) | | | | | * * * * * コマンド ``` ## 特殊文字 | 記号 | 意味 | 例 | |---|---|---| | `*` | すべての値 | `* * * * *` 毎分 | | `,` | リスト | `0 8,12,18 * * *` 08:00、12:00、18:00 | | `-` | 範囲 | `0 9 * * 1-5` 月曜から金曜の09:00 | | `/` | ステップ | `*/15 * * * *` 15分ごと;`10-50/20` = 10、30、50 | 名前は大文字小文字を区別しません。名前の範囲(`MON-FRI`)はほとんどの実装で機能し、名前のリストはどこでも機能します。 ## マクロ | マクロ | 同等 | |---|---| | `@yearly` / `@annually` |`0 0 1 1 *` | | `@monthly` |`0 0 1 * *` | | `@weekly` |`0 0 * * 0` | | `@daily` / `@midnight` |`0 0 * * *` | | `@hourly` |`0 * * * *` | | `@reboot` |起動時に一度(時間ベースではない) | ## 日付ルール 月内日と曜日の両方が制限されている場合(どちらも`*`でない)、ジョブはどちらかが一致するときに実行されます。`0 9 1 * MON`は毎月1日と毎週月曜日に実行されます。 ## プラットフォームの違い | プラットフォーム | フィールド | タイムゾーン | 注記 | |---|---|---|---| | Linux cron(cronie、Vixie) | 5 | システムローカル時刻 |`CRON_TZ=`はcronieでサポート | | Kubernetes CronJob | 5 | コントローラーのタイムゾーン、または`spec.timeZone` |`concurrencyPolicy: Forbid`で重複を防ぐ | | GitHub Actions `schedule` | 5 | 常にUTC | 負荷が高いと実行が遅れることがあります;最小間隔は5分 | | Quartz(Java) | 6-7(秒が最初、任意の年) | 設定可能 |`?`は「特定の値なし」、`L`、`W`、`#`をサポート | | AWS EventBridge | 6(年を含む) | UTC、スケジューラーのタイムゾーンが設定されない限り | 月内日または曜日のどちらかを`?`にする必要があります | このスキルのスクリプトは、5フィールド標準と上記のマクロ(`@reboot`を除く。時間ベースではないと報告)をサポートします。 FILE:references/scheduling-pitfalls.md # スケジューリングの落とし穴 ## 1. 月内日 OR 曜日 `0 0 13 * 5`は「13日の金曜日」ではありません。毎月13日と毎週金曜日に実行されます。`0 0 13 * *`にガードを加えて使用します:`[ "$(date +\%u)" = 5 ] && cmd`。 ## 2. 存在しない、またはまれにしか発生しない日付 - `0 0 30 2 *`は決して実行されません(2月に30日はありません)。 - `0 0 31 * *`は年の7ヶ月でのみ実行されます。 - `0 0 29 2 *`はうるう年のみ実行されます。 「月の最終日」には`0 0 28-31 * *`にガードを加えて使用します:`[ "$(date -d tomorrow +\%d)" = 01 ] && cmd`。 ## 3. 夏時間 DSTのあるローカルタイムゾーンでは、約01:00から03:00の間の時刻はスキップされたり(春の前進)、2回実行されたり(秋の後退、cronの実装によって異なる)ことがあります。重要なジョブはその時間帯の外にスケジュールするか、cronをUTCで実行してください。 ## 4. UTC対ローカル時刻 GitHub Actionsや多くのクラウドスケジューラーはUTCを使用します。「毎日09:00」がイスタンブール(UTC+3)のチームにとっては、UTCでは`0 6 * * *`です。常に式の横にタイムゾーンを文書とコードコメントに書いてください。 ## 5. 過剰頻度または重複実行 - `* * * * *`は1日1440回実行されます。それが意図されていることを確認してください。 - 固定時間での分フィールドの`*`(`* 3 * * *`)は03:00から03:59の間に60回実行されます;通常は`0 3 * * *`が意図されています。 - ジョブが間隔より長くかかる場合は、ロック(`flock -n`)または`concurrencyPolicy: Forbid`を使用してください。 ## 6. ステップ値は均等に折り返さない 分フィールドの`*/7`は0、7、...、56で実行され、その後再び0で実行されます(4分のギャップがあります)。`*/25`は0、25、50で実行されます。ステップは毎時、毎日、毎月リセットされます。 ## 7. 群集効果 多くのチームが`0 0 * * *`や`0 * * * *`を選びます。共有システムやレート制限のあるAPIでの負荷スパイクを避けるため、ジョブを変則的な分(例:`17 2 * * *`)にシフトしてください。 ## 8. 環境と出力 Cronは最小限のPATHとログインシェルなしで実行されます。絶対パスを使用し、必要な変数をcrontabに設定し、出力をリダイレクト(`>> /var/log/job.log 2>&1`)して失敗が見えるようにしてください。`%`を`\%`としてエスケープしてください。 ## 9. 実行のスキップ プレーンなcronは、マシンがオフの間にスキップされた実行を追いかけません。スキップされた実行が重要な場合は、anacron、`Persistent=true`のsystemdタイマー、またはKubernetesの`startingDeadlineSeconds`を使用してください。 FILE:templates/schedule-review.md # スケジュールレビュー: {{system_or_repo}} **プラットフォーム:** {{Linux cron | Kubernetes CronJob | GitHub Actions | その他}} **想定タイムゾーン:** {{time_zone}} **レビュー日:** {{date}} ## 概要 {{1〜2文: スケジュールはチームの期待通りに動作していますか、そして何を変更する必要がありますか。}} ## スケジュール ### {{n}}. `{{expression}}` - {{job name}} - **意味:** {{平易な英語の説明}} - **次の実行:** {{run 1}}、{{run 2}}、{{run 3}} - **判定:** {{OK | FIX | CLARIFY}} - **警告:** - {{warning}} - **提案行:** ``` {{修正されたcrontab行}} ``` ## 質問 - {{チームへの質問}} FILE:examples/example-backup-review.md # スケジュールレビュー: opsサーバーcrontab **プラットフォーム:** Linux cron(cronie) **想定タイムゾーン:** Europe/Berlin(サーバーローカル時刻、DSTあり) **レビュー日:** 2026-10-08 ##概要 3行のうち2行がコメントの通りに動作していません。バックアップはDSTウィンドウ内で実行され、「13日の金曜日」レポートは実際には毎週金曜日と毎月13日に実行されます。 ##スケジュール ### 1. `30 2 * * *` - 毎晩のデータベースバックアップ - **意味:** 毎日02:30に。 - **次の実行:** 2026-10-09 02:30、2026-10-10 02:30、2026-10-11 02:30 - **判定:** FIX - **警告:** - 02:30はDST変更ウィンドウ内です;春の前進の夜にはスキップされ、秋には2回実行される可能性があります。 - バックアップは月末に1時間以上かかることがあります;ロックがありません。 - **提案行:** ``` 17 4 * * * flock -n /tmp/db-backup.lock /opt/scripts/db-backup.sh >> /var/log/db-backup.log 2>&1 ``` ### 2. `0 9 13 * FRI` - 「13日の金曜日」ファンレポート - **意味:** 毎月13日の09:00、または毎週金曜日の09:00(cronの日付ルール)。 - **次の実行:** 2026-10-09 09:00(金)、2026-10-13 09:00(火、13日)、2026-10-16 09:00(金) - **判定:** FIX - **警告:** - 両方の日付フィールドが制限されているため、cronはORを使用します(ANDではありません)。 - **提案行:** ``` 0 9 13 * * [ "$(date +\%u)" = 5 ] && /opt/scripts/fun-report.sh ``` ### ​3. `*/20 8-18 * * 1-5` - ヘルプデスクからのチケット同期 - **意味:** 月曜から金曜の08:00から18:59まで、20分ごと(:00、:20、:40)。 - **次の実行:** 2026-10-08 10:20、2026-10-08 10:40、2026-10-08 11:00 - **判定:** CLARIFY - **警告:** - 1日の最後の実行は18:40です(18:00ではありません)。同期を18:00に停止する場合は、`8-17`と別の`0 18 * * 1-5`を使用してください。 ##質問 - DSTの問題を完全に避けるため、サーバーはcronをUTCで実行すべきですか? FILE:scripts/cron_explain.py #!/usr/bin/env python3 """標準5フィールドcron式を説明、検証、プレビューします(標準ライブラリのみ)。 使用法: python3 cron_explain.py "EXPR" [--count N] [--from "YYYY-MM-DD HH:MM"] python3 cron_explain.py --file crontab.txt [--count N] [--from ...] 各式について: 平易な英語の説明、次のN回の実行時刻 (サーバーローカル時刻、および落とし穴の警告。--fileモードでは、crontab 行が読み取られます;コメント、空行、VAR=value行はスキップされ、 最初の5フィールド(または先頭の@マクロ)がスケジュールとして取得されます。 終了コード: 0 = すべて有効、1 = 式が無効または実行されない、2 = 使用法。 """ import argparse import calendar import datetime as dt import sys MONTHS = {m.lower(): i for i, m in enumerate(calendar.month_abbr) if m} DAYS = {"sun": 0, "mon": 1, "tue": 2, "wed": 3, "thu": 4, "fri": 5, "sat": 6} MACROS = { "@yearly": "0 0 1 1 *", "@annually": "0 0 1 1 *", "@monthly": "0 0 1 * *", "@weekly": "0 0 * * 0", "@daily": "0 0 * * *", "@midnight": "0 0 * * *", "@hourly": "0 * * * *", } FIELDS = [("分", 0, 59, {}), ("時",, 0,, 23,, {}), ("日(月内)",, 1,, 31,, {}), ("月",, 1,, 12,, MONTHS), ("曜日",,, 0,, ​7,, DAYS)] DAY_NAMES = ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"] def compress(values, fmt=str): """[1,2,3,5] -> '1-3, 5' 各数値にfmtを使用。""" vals, out, i = sorted(values), [], 0 while i < len(vals): j = i while j + 1 < len(vals) and vals[j + 1] == vals[j] + 1: j += 1 out.append(fmt(vals[i]) if j - i < 2 else f"{fmt(vals[i])}-{fmt(vals[j])}") if 0 < j - i < 2: out.append(fmt(vals[j])) i = j + 1 return ", ".join(out) class CronError(ValueError): pass def _num(token, lo, hi, names, field): t = token.lower() if t in names: return names[t] if not t.isdigit(): raise CronError(f"{field}: '{token}' は数値または既知の名前ではありません") v = int(t) if not lo <= v <= hi: raise CronError(f"{field}: {v} は {lo}-{hi} の範囲外です") return v def parse_field(text, lo, hi, names, field): values = set() for part in text.split(","): if not part: raise CronError(f"{field}: '{text}' に空のリスト項目があります") step = 1 if "/" in part: part, step_s = part.split("/", 1) if not step_s.isdigit() or int(step_s) == 0: raise CronError(f"{field}: 不正なステップ '/{step_s}'") step = int(step_s) if part == "*": start, end = lo, hi elif "-" in part: a, b = part.split("-", 1) start, end = _num(a, lo, hi, names, field), _num(b, lo, hi, names, field) if start > end: raise CronError(f"{field}: 範囲 {a}-{b} が逆順です") else: start = _num(part, lo, hi, names, field) end = hi if step > 1 else start values.update(range(start, end + 1, step)) return values def parse(expr): expr = expr.strip() if expr.lower() == "@reboot": raise CronError("@reboot は起動時に一度実行され、時間ベースではありません") expr = MACROS.get(expr.lower(), expr) parts = expr.split() if len(parts) != 5: hint = " (6-7フィールドはQuartzまたはEventBridgeのように見えます;references/cron-syntax.mdを参照)" if len(parts) in (6, 7) else "" raise CronError(f"5フィールドが必要ですが、{len(parts)}{hint} が指定されました") for p in parts: bare = p.lower() for n in list(MONTHS) + list(DAYS: bare = bare.replace(n, "") if any(c in bare for c in "?lw#"): raise CronError(f"'{p}': '?', 'L', 'W' および '#' はQuartz拡張であり、標準cronではありません") sets = [parse_field(p, lo, hi, names, name) for p, (name, lo, hi, names)in zip(parts, FIELDS)] if 7 in sets[4]: sets[4].discard(7) sets[4].add(0) return parts, sets def describe_set(values, lo, hi, field, raw: vals = sorted(values) if raw == "*": return None if field == "曜日": names = [DAY_NAMES[v] for v in vals] if vals == [1, 2, 3, ​4, ​5]: return "月曜日から金曜日" if vals == [0, 6]: return "週末" return ", ".join(names) if field == "月": return ", ".join(calendar.month_name[v] for v in vals) return compress(vals) def explain(parts, sets: minute, hour, dom, month, dow = sets rm, rh, rdom, rmon, rdow = parts if rm == "*" and rh == "*": time_txt = "毎分" elif rm.startswith("*/")and rh == "*": time_txt = f"毎{rm[2:]}分" elif rm.startswith("*/"): time_txt = (f"毎{rm[2:]}分(分" + ", ".join(str(m) for m in sorted(minute)) + ")の時間帯" + compress(hour, lambda h: f"{h:02d}")) elif rh == "*": time_txt = "毎時の分" + ", ".join(str(m) for m in sorted(minute)) + "に" elif rm == "*": time_txt = "時間帯" + compress(hour, lambda h: f"{h:02d}") + "の毎分" elif len(minute) * len(hour) <= 6: time_txt = = ", ".join(f"{h:02d}:{m:02d}" for h in sorted(hour) for m in sorted(minute)) + "に" else: time_txt = ("分" + ", ".join(str(m) for m in sorted(minute)) + "、時間帯" + compress(hour, lambda h: f"{h:02d}") + "の") day_txt = [] d_dom = describe_set(dom, 1, 31, "日(月内)", rdom) d_dow = describe_set(dow, 0, 6, "曜日", rdow) if d_dom and d_dow: : day_txt.append(f"月内日{d_dom}または{d_dow}に") elif d_dom: day_txt.append(f"月内日{d_dom}に") elif d_dow: day_txt.append(d_dow if d_dow.startswith("on ") else f"{d_dow}に") else: day_txt.append("毎日") d_mon = describe_set(month, 1, 12, "月", rmon) if d_mon: day_txt.append(f"{d_mon}に") text = f"{time_txt}、{' '.join(day_txt)}" return text[0].upper() + text[1:] + "." def day_matches(d, parts, sets: _, _, dom, month, dow = sets if d.month notin month: return False cron_dow = (d.weekday() + 1) % 7 dom_r, dow_r = parts[2] != "*", parts[4] != "*" if dom_rand dow_r: return d.day in dom or cron_dowin dow if dom_r: return d.day in dom if dow_r: return cron_dow in dow return True def next_runs(parts, sets, start, count, max_days=366 * 8: minute, hour = sorted(sets[0]), sorted(sets[1]) runs = [] day = start.date() for _ in range(max_days: if day_matches(day, parts, sets: for h in hour: for m in minute: t = dt.datetime(day.year, day.month, day.day, h, m) if t > start: runs.append(t) if len(runs) >= count: return runs day += dt.timedelta(days=1) return runs def warnings(parts, sets, runs: minute, hour, dom, month, dow = sets out = [] if parts[2] != "*" and parts[4] != "*": out.append("月内日と曜日の両方が設定されています:cronはどちらかが一致するときに実行されます(OR、ANDではありません)。") if parts[2] != "*" and parts[4] == "*": max_days = {m: (29 if m == 2 else calendar.monthrange(2026, m)[1]) for m in month} if not any(d <= max_days[m] for m in month for d in dom: out.append("決して実行されません:選択された月内日は選択された月に存在しません。") elif any(d > 28 for d in dom: out.append("選択された日(29-31)はすべての月に存在するわけではないため、一部の月はスキップされます。") if parts[0] == "*" and parts[1] != "*": out.append("分が'*'です:選択された時間の毎分実行されます;分0を意図していましたか?") runs_per_day = len(minute) * len(hour) if runs_per_day >= 288: out.append(f"1日{runs_per_day}回実行されます;それが意図されていることを確認し、重複に対するロックを追加してください。") if any(1 <= h <= 2 for h in hour)and parts[1] != "*": out.append("01:00から02:59の間に実行されます:DSTのあるローカルタイムゾーンでは、これがスキップされたり2回実行されたりする可能性があります。") for i, raw in ((0, parts[0]), (1, parts[1])): if "/" in raw: step = int(raw.split("/")[1]) span = 60 if i == 0 else 24 if span % step: out.append(f"ステップ/{step}は{span}を割り切れません:{'時' if i == 0 else '日'}が折り返すところでギャップが不均等になります。") if parts[0] == "0" and parts[1] in ("*", "0"): out.append("正時の分0は人気のあるスロットです;負荷スパイクを避けるために変則的な分を検討してください。") return out def check(expr, start, count: print(f"式: {expr}") try: parts, sets = parse(expr) except CronError as e: print(f" 無効: {e}\n") return False print(f" 意味: {explain(parts, sets)}") runs = next_runs(parts, sets, start, count) ok = True if runs: print(f" {start:%Y-%m-%d %H:%M} 以降の次の{len(runs)}回の実行(サーバーローカル時刻:") for r in runs: print(f" {r:%Y-%m-%d %H:%M} {r:%a}") else: print(" 次の実行: 今後8年間で見つかりません") ok = False for w in warnings(parts, sets, runs: print(f" 警告: {w}") print() return ok def crontab_schedules(path: with open(path, encoding="utf-8") as f: for line in f: s = line.strip() if not s or s.startswith("#"): continue first = s.split()[0] if "=" in firstand not first.startswith("@"): continue yield first if first.startswith("@") else " ".join(s.split()[:5]) def main(argv=None: ap = argparse.ArgumentParser(description="cron式を説明および検証します。") ap.add_argument("expr", nargs="?", help='引用符で囲んだcron式、例: "*/15 9-17 * * 1-5"') ap.add_argument("--file", help="crontabファイルからスケジュールを読み取ります") ap.add_argument("--count", type=int, default=5, help="表示する次の実行回数(デフォルト5)") ap.add_argument("--from", dest="start", help='開始時刻 "YYYY-MM-DD HH:MM"(デフォルト: 現在)') a = ap.parse_args(argv) if bool(a.expr) == bool(a.file: ap.print_usage(sys.stderr) print("エラー: EXPRまたは--fileのどちらか一方を指定してください", file=sys.stderr) return 2 try: start = dt.datetime.strptime(a.start, "%Y-%m-%d %H:%M") if a.start else dt.datetime.now().replace(second=0, microsecond=0) except ValueError: print("エラー: --fromは2026-10-08 10:00のような形式である必要があります", file=sys.stderr) return 2 exprs = list(crontab_schedules(a.file)) if a.file else [a.expr] results = [check(e, start, max(1, a.count)) forein exprs] print(f"{sum(results)} / {len(results)} のスケジュールが有効で実行可能です。") return 0 if all(results) else 1 if __name__ == "__main__": sys.exit(main())

ログインして完全なプロンプトを表示

次で続行:

ログインすると、次に同意したことになります: 利用規約 と プライバシーポリシー

使い方

このプロンプトは productivity 向けに設計されています。上の内容をコピーして、お好みの AI ツールに貼り付けてください。

最良の結果を得るには、プレースホルダー(角括弧や大文字で示された部分)を具体的な要件に置き換えてください。

参考資料

カテゴリ:productivity| prompts.chat| cron| scheduling

ノート