クロンスケジュール説明スキル
フリーのプロンプト百科事典 Wikiprompt より
クロンスケジュール説明スキル 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 ツールに貼り付けてください。
最良の結果を得るには、プレースホルダー(角括弧や大文字で示された部分)を具体的な要件に置き換えてください。
ノート
0 件のコメント