EventBridge の cron 式とは、cron(分 時 日 月 曜日 年) の6フィールドで実行日時を指定する書き方です。 EventBridge ルールでは時刻を UTC で書くので、日本時間(JST)から9時間引いて指定します。たとえば毎日 9:00 JST は cron(0 0 * * ? *) です。

この記事では、JST → UTC 変換済みの cron 式を43例並べ、0:00〜8:59 JST の指定で起きる「曜日・日付のずれ」を表にしました。あわせて、put-rule / put-targets と、タイムゾーンをそのまま指定できる EventBridge Scheduler のコマンドを実行出力つきで載せます。

確認環境:Ubuntu 24.04 / AWS CLI 1.46.1 / Python 3.11。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しました。掲載するコマンドは実際の AWS でそのまま使える形にしてあります。早見表の式は、UTC の式と JST の式が同じ日時に動くことを本記事のスクリプトで突き合わせています。

EventBridge cron 式の早見表

項目 内容
書式 cron(分 時 日 月 曜日 年)(6フィールド、空白区切り)
タイムゾーン ルールは UTC。JST から9時間引く(0:00〜8:59 JST は前日になる)
日と曜日 どちらか片方を必ず ? にする
曜日の数字 1=日、2=月、3=火、4=水、5=木、6=金、7=土(SUN〜SAT も可)
毎日 9:00 JST cron(0 0 * * ? *)
平日 18:00 JST cron(0 9 ? * MON-FRI *)
平日 8:00 JST cron(0 23 ? * SUN-THU *)(曜日が1日前にずれる)
月末 23:00 JST cron(0 14 L * ? *)
第1月曜 9:00 JST cron(0 0 ? * 2#1 *)
一定間隔 rate(1 minute)、rate(5 minutes)、rate(1 hour)、rate(1 day)
JST のまま書きたい EventBridge Scheduler で --schedule-expression-timezone "Asia/Tokyo"

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「EventBridge cron式の書き方|JST変換と例の早見表」

cron 式の6フィールドと記号

公式ドキュメント(Amazon EventBridge ユーザーガイドのスケジュールルールのページ)に載っているフィールドと、使える記号です。

位置 フィールド 値 使える記号
1 分 0-59 , - * /
2 時 0-23 , - * /
3 日 1-31 , - * ? / L W
4 月 1-12 または JAN-DEC , - * /
5 曜日 1-7 または SUN-SAT , - * ? L #
6 年 1970-2199 , - * /
記号 意味 例
, 複数の値 時に 0,9 → 0時と9時
- 範囲 曜日に MON-FRI → 月〜金
* すべての値 時に * → 毎時
/ 増分 分に 0/15 → 0分から15分ごと
? 指定しない(日か曜日のどちらかに置く) 日に ?、曜日に MON
L 月の最終日、または最後の曜日 日に L → 月末、曜日に 6L → 最終金曜
W 指定日に最も近い平日 日に 1W → 1日に最も近い平日
# 第n何曜日 曜日に 2#1 → 第1月曜

Linux の crontab と比べると、次の点が違います。

比べる点 Linux の crontab EventBridge の cron 式
フィールド数 5(分 時 日 月 曜日) 6(年が加わる)
囲み なし cron( ) で囲む
日と曜日 両方に * を書ける 片方を ? にする
曜日の数字 0(または7)=日、1=月 1=日、2=月、…、7=土
タイムゾーン サーバーの設定 UTC(Scheduler は指定可)

曜日は数字が1つずれるので、MON のように名前で書くと読み間違いが減ります。

JST → UTC 変換済み cron 式の早見表(43例)

「ルール用(UTC)」は aws events put-rule に、「Scheduler 用(Asia/Tokyo 指定)」は aws scheduler create-schedule --schedule-expression-timezone "Asia/Tokyo" にそのまま使えます。

No. 実行したい日時(JST) ルール用(UTC) Scheduler 用(Asia/Tokyo 指定)
1 毎日 9:00 cron(0 0 * * ? *) cron(0 9 * * ? *)
2 毎日 0:00 cron(0 15 * * ? *) cron(0 0 * * ? *)
3 毎日 8:30 cron(30 23 * * ? *) cron(30 8 * * ? *)
4 毎日 12:00 cron(0 3 * * ? *) cron(0 12 * * ? *)
5 毎日 18:00 cron(0 9 * * ? *) cron(0 18 * * ? *)
6 毎日 23:59 cron(59 14 * * ? *) cron(59 23 * * ? *)
7 毎日 9:00 と 18:00 cron(0 0,9 * * ? *) cron(0 9,18 * * ? *)
8 毎時 0分 cron(0 * * * ? *) cron(0 * * * ? *)
9 毎時 15分と45分 cron(15,45 * * * ? *) cron(15,45 * * * ? *)
10 5分ごと cron(0/5 * * * ? *) cron(0/5 * * * ? *)
11 15分ごと cron(0/15 * * * ? *) cron(0/15 * * * ? *)
12 毎日 9:00〜18:00 の毎時0分 cron(0 0-9 * * ? *) cron(0 9-18 * * ? *)
13 毎日 9:00〜17:55 の5分ごと cron(0/5 0-8 * * ? *) cron(0/5 9-17 * * ? *)
14 3時間ごと(0時, 3時, 6時 …) cron(0 0/3 * * ? *) cron(0 0/3 * * ? *)
15 2時間ごと(0時, 2時, 4時 …) cron(0 1/2 * * ? *) cron(0 0/2 * * ? *)
16 平日 9:00 cron(0 0 ? * MON-FRI *) cron(0 9 ? * MON-FRI *)
17 平日 18:00 cron(0 9 ? * MON-FRI *) cron(0 18 ? * MON-FRI *)
18 平日 8:00 cron(0 23 ? * SUN-THU *) cron(0 8 ? * MON-FRI *)
19 平日 0:00 cron(0 15 ? * SUN-THU *) cron(0 0 ? * MON-FRI *)
20 平日 9:00〜18:00 の毎時0分 cron(0 0-9 ? * MON-FRI *) cron(0 9-18 ? * MON-FRI *)
21 平日 9:00〜17:50 の10分ごと cron(0/10 0-8 ? * MON-FRI *) cron(0/10 9-17 ? * MON-FRI *)
22 毎週月曜 9:00 cron(0 0 ? * MON *) cron(0 9 ? * MON *)
23 毎週月曜 7:00 cron(0 22 ? * SUN *) cron(0 7 ? * MON *)
24 毎週日曜 3:00 cron(0 18 ? * SAT *) cron(0 3 ? * SUN *)
25 土日 10:00 cron(0 1 ? * SAT,SUN *) cron(0 10 ? * SAT,SUN *)
26 月・水・金 20:00 cron(0 11 ? * MON,WED,FRI *) cron(0 20 ? * MON,WED,FRI *)
27 毎月1日 9:00 cron(0 0 1 * ? *) cron(0 9 1 * ? *)
28 毎月1日 0:00 cron(0 15 L * ? *) cron(0 0 1 * ? *)
29 毎月15日 10:00 cron(0 1 15 * ? *) cron(0 10 15 * ? *)
30 毎月10日と25日 9:00 cron(0 0 10,25 * ? *) cron(0 9 10,25 * ? *)
31 毎月25日 6:00 cron(0 21 24 * ? *) cron(0 6 25 * ? *)
32 月末 23:00 cron(0 14 L * ? *) cron(0 23 L * ? *)
33 月末 9:00 cron(0 0 L * ? *) cron(0 9 L * ? *)
34 月末 8:00 (1つの式では書けない) cron(0 8 L * ? *)
35 第1月曜 9:00 cron(0 0 ? * 2#1 *) cron(0 9 ? * 2#1 *)
36 第2火曜 10:00 cron(0 1 ? * 3#2 *) cron(0 10 ? * 3#2 *)
37 第1月曜 8:00 (1つの式では書けない) cron(0 8 ? * 2#1 *)
38 最終金曜 17:00 cron(0 8 ? * 6L *) cron(0 17 ? * 6L *)
39 1日に最も近い平日 9:00 cron(0 0 1W * ? *) cron(0 9 1W * ? *)
40 1・4・7・10月の1日 9:00 cron(0 0 1 1,4,7,10 ? *) cron(0 9 1 1,4,7,10 ? *)
41 毎年1月1日 0:00 cron(0 15 31 12 ? *) cron(0 0 1 1 ? *)
42 毎年4月1日 9:00 cron(0 0 1 4 ? *) cron(0 9 1 4 ? *)
43 2027年の毎日 9:00 cron(0 0 * * ? 2027) cron(0 9 * * ? 2027)

No.34 と No.37 は、UTC にすると「月末の前日」「第1月曜の前日の日曜(前の月に入ることがある)」になり、ルールの1つの式では表せません。こうした指定は EventBridge Scheduler でタイムゾーンを指定して書くのが確実です。

rate 式は基準の時刻を持たないので、変換は不要です。

間隔 式
1分ごと rate(1 minute)
5分ごと rate(5 minutes)
1時間ごと rate(1 hour)
12時間ごと rate(12 hours)
1日ごと rate(1 day)
7日ごと rate(7 days)

「毎時ちょうど」のように時計の時刻に合わせたいときは cron 式(cron(0 * * * ? *))、開始時点からの間隔でよいときは rate 式が向いています。

日付をまたぐときの曜日・日付のずれ

0:00〜8:59 JST は、UTC では前日の 15:00〜23:59 です。 時刻だけでなく、曜日や日も1つ前に書き換えます。

実行したい日時(JST) そのまま9時間引いただけの式 動く日時(JST) 正しい式
平日 8:00 cron(0 23 ? * MON-FRI *) 火〜土の 8:00 cron(0 23 ? * SUN-THU *)
毎週月曜 7:00 cron(0 22 ? * MON *) 火曜 7:00 cron(0 22 ? * SUN *)
毎週日曜 3:00 cron(0 18 ? * SUN *) 月曜 3:00 cron(0 18 ? * SAT *)
毎月1日 0:00 cron(0 15 1 * ? *) 2日 0:00 cron(0 15 L * ? *)
毎月25日 6:00 cron(0 21 25 * ? *) 26日 6:00 cron(0 21 24 * ? *)
毎年1月1日 0:00 cron(0 15 1 1 ? *) 1月2日 0:00 cron(0 15 31 12 ? *)

書き換えの対応は次のとおりです。

JST での指定 UTC の式での書き方
月〜金(MON-FRI) SUN-THU
月曜(MON) SUN
日曜(SUN) SAT
n日 n−1日
1日 前月の最終日(L)
1月1日 12月31日

9:00 JST 以降の時刻は同じ日のままなので、曜日・日はそのまま書けます。時間帯が 9:00 をまたぐ場合は、ルールを2つに分けます。たとえば「平日 8:00〜20:00 の毎時0分」は次の2つです(2つを合わせた実行日時が、JST の cron(0 8-20 ? * MON-FRI *) と一致することをスクリプトで確かめました)。

cron(0 23 ? * SUN-THU *)     ← 8:00 JST の分
cron(0 0-11 ? * MON-FRI *)   ← 9:00〜20:00 JST の分

put-rule と put-targets でルールを登録する

ルールを作る

aws events put-rule \
  --name daily-report-0900jst \
  --schedule-expression "cron(0 0 * * ? *)" \
  --description "every day 09:00 JST"
{
    "RuleArn": "arn:aws:events:ap-northeast-1:123456789012:rule/daily-report-0900jst"
}

式は必ず二重引用符で囲みます。 囲まないと、シェルが * や ? や括弧を先に解釈します。

ターゲットを付ける

ルールだけでは何も起動しません。put-targets で呼び出し先(ここでは SQS キュー)を付けます。

aws events put-targets \
  --rule daily-report-0900jst \
  --targets "Id=report-queue,Arn=arn:aws:sqs:ap-northeast-1:123456789012:report-queue"
{
    "FailedEntryCount": 0,
    "FailedEntries": []
}

FailedEntryCount が 0 なら登録できています。ターゲット側で EventBridge からの呼び出しを許可する設定(Lambda のリソースベースポリシーなど)は、ターゲットの種類に応じて別に行います。

登録内容を確かめる

aws events describe-rule --name daily-report-0900jst
{
    "Name": "daily-report-0900jst",
    "Arn": "arn:aws:events:ap-northeast-1:123456789012:rule/daily-report-0900jst",
    "ScheduleExpression": "cron(0 0 * * ? *)",
    "State": "ENABLED",
    "Description": "every day 09:00 JST",
    "EventBusName": "default",
    "CreatedBy": "123456789012"
}
aws events list-targets-by-rule --rule daily-report-0900jst
{
    "Targets": [
        {
            "Id": "report-queue",
            "Arn": "arn:aws:sqs:ap-northeast-1:123456789012:report-queue"
        }
    ]
}

ルールを3つ登録したあとの一覧です。

aws events put-rule --name weekday-1800jst --schedule-expression "cron(0 9 ? * MON-FRI *)"
aws events put-rule --name every-5min --schedule-expression "rate(5 minutes)"
aws events list-rules --query 'Rules[].[Name,ScheduleExpression,State]' --output table
----------------------------------------------------------------
|                           ListRules                          |
+-----------------------+---------------------------+----------+
|  weekday-1800jst      |  cron(0 9 ? * MON-FRI *)  |  ENABLED |
|  every-5min           |  rate(5 minutes)          |  ENABLED |
|  daily-report-0900jst |  cron(0 0 * * ? *)        |  ENABLED |
+-----------------------+---------------------------+----------+

止める・消す

aws events disable-rule --name every-5min
aws events describe-rule --name every-5min --query State
"DISABLED"

ターゲットが付いたままのルールを消そうとすると、次のメッセージが返ります。

aws events delete-rule --name daily-report-0900jst
An error occurred (ValidationException) when calling the DeleteRule operation: Rule can't be deleted since it has targets.

先に remove-targets でターゲットを外してから消します。

aws events remove-targets --rule daily-report-0900jst --ids report-queue
aws events delete-rule --name daily-report-0900jst
{
    "FailedEntryCount": 0,
    "FailedEntries": []
}

(delete-rule は成功時に何も出力しません。)

EventBridge Scheduler なら JST のまま書ける

公式ドキュメントでは、スケジュールルールは従来からの機能と位置づけられ、スケジュール実行には EventBridge Scheduler が推奨されています。Scheduler は --schedule-expression-timezone でタイムゾーンを指定できるので、9時間の引き算も曜日の書き換えも不要です。

aws scheduler create-schedule \
  --name daily-report-0900 \
  --schedule-expression "cron(0 9 * * ? *)" \
  --schedule-expression-timezone "Asia/Tokyo" \
  --flexible-time-window '{"Mode":"OFF"}' \
  --target '{"Arn":"arn:aws:sqs:ap-northeast-1:123456789012:report-queue","RoleArn":"arn:aws:iam::123456789012:role/scheduler-sqs-role","Input":"{\"job\":\"daily-report\"}"}'
{
    "ScheduleArn": "arn:aws:scheduler:ap-northeast-1:123456789012:schedule/default/daily-report-0900"
}
aws scheduler get-schedule --name daily-report-0900
{
    "Arn": "arn:aws:scheduler:ap-northeast-1:123456789012:schedule/default/daily-report-0900",
    "CreationDate": 1791180364.975877,
    "FlexibleTimeWindow": {
        "Mode": "OFF"
    },
    "GroupName": "default",
    "LastModificationDate": 1791180364.975877,
    "Name": "daily-report-0900",
    "ScheduleExpression": "cron(0 9 * * ? *)",
    "ScheduleExpressionTimezone": "Asia/Tokyo",
    "State": "ENABLED",
    "Target": {
        "Arn": "arn:aws:sqs:ap-northeast-1:123456789012:report-queue",
        "Input": "{\"job\":\"daily-report\"}",
        "RetryPolicy": {
            "MaximumEventAgeInSeconds": 86400,
            "MaximumRetryAttempts": 185
        },
        "RoleArn": "arn:aws:iam::123456789012:role/scheduler-sqs-role"
    }
}

1回だけ実行する at() 式も Scheduler で使えます。

aws scheduler create-schedule \
  --name once \
  --schedule-expression "at(2026-12-31T23:00:00)" \
  --schedule-expression-timezone "Asia/Tokyo" \
  --flexible-time-window '{"Mode":"OFF"}' \
  --target '{"Arn":"arn:aws:sqs:ap-northeast-1:123456789012:report-queue","RoleArn":"arn:aws:iam::123456789012:role/scheduler-sqs-role"}'
{
    "ScheduleArn": "arn:aws:scheduler:ap-northeast-1:123456789012:schedule/default/once"
}

ルールと Scheduler の違い

比べる点 EventBridge ルール(aws events) EventBridge Scheduler(aws scheduler)
タイムゾーン UTC 固定 --schedule-expression-timezone で指定(既定は UTC)
式の種類 cron()、rate() cron()、rate()、at()(1回だけ)
rate の単位 minute / minutes / hour / hours / day / days minutes / hours / days
ターゲットの指定 put-targets で別に登録 create-schedule の --target に含める(RoleArn が必要)
必須の引数 --name と式 --name、式、--target、--flexible-time-window

update-schedule は省略した項目が既定値に戻る

update-schedule は、指定した値でスケジュール全体を置き換えます。公式リファレンスにも「リクエストで指定しなかった任意項目は、更新後にシステムの既定値になる」と書かれています。タイムゾーンを付けずに時刻だけを変えると、次のようになりました。

aws scheduler update-schedule \
  --name daily-report-0900 \
  --schedule-expression "cron(30 9 * * ? *)" \
  --flexible-time-window '{"Mode":"OFF"}' \
  --target '{"Arn":"arn:aws:sqs:ap-northeast-1:123456789012:report-queue","RoleArn":"arn:aws:iam::123456789012:role/scheduler-sqs-role"}'
aws scheduler get-schedule --name daily-report-0900 \
  --query '[ScheduleExpression,ScheduleExpressionTimezone]'
{
    "ScheduleArn": "arn:aws:scheduler:ap-northeast-1:123456789012:schedule/default/daily-report-0900"
}
[
    "cron(30 9 * * ? *)",
    "UTC"
]

タイムゾーンが UTC に戻り、9:30 JST のつもりが 18:30 JST の実行になります。 更新するときも --schedule-expression-timezone "Asia/Tokyo" を毎回付けます。

登録時に止まる書き方と直し方

API から返るメッセージ

cron( ) で囲み忘れた場合と、使えない単位を書いた場合は、put-rule が次のメッセージを返しました。

aws events put-rule --name bad1 --schedule-expression "0 0 * * ? *"
An error occurred (ValidationException) when calling the PutRule operation: Parameter ScheduleExpression is not valid.
aws events put-rule --name bad2 --schedule-expression "rate(90 seconds)"
An error occurred (ValidationException) when calling the PutRule operation: Parameter ScheduleExpression is not valid.

メッセージは「ScheduleExpression が正しくない」という内容だけで、どこが違うかは示されません。 そのため、式のルールを先に知っておくことが近道になります。

ルールにタイムゾーンを付けようとした場合は、CLI が引数を受け付けません。

aws events put-rule --name bad3 --schedule-expression "cron(0 0 * * ? *)" \
  --schedule-expression-timezone Asia/Tokyo
…(省略)…
Unknown options: --schedule-expression-timezone, Asia/Tokyo

Scheduler で確かめたメッセージは次の3つです。

操作 メッセージ 直し方
同じ名前で create-schedule An error occurred (ConflictException) when calling the CreateSchedule operation: Schedule daily-report-0900 already exists. update-schedule を使うか、名前を変える
--flexible-time-window を省略 aws: error: the following arguments are required: --flexible-time-window --flexible-time-window '{"Mode":"OFF"}' を付ける
無い名前を get-schedule An error occurred (ResourceNotFoundException) when calling the GetSchedule operation: Schedule daily-report-0900 does not exist. 名前とグループを確認する

公式ドキュメントに書かれている式のルール

式の中身については、公式ドキュメントに次のルールが明記されています。

ルール 合わない書き方 直した書き方
フィールドは6個(年まで書く) cron(0 0 * * ?) cron(0 0 * * ? *)
日と曜日を同時に指定できない。片方に値か * を書いたら、もう片方は ? cron(0 0 * * * *) cron(0 0 * * ? *)
rate の値が1なら単位は単数形 rate(1 minutes) rate(1 minute)
rate の値が2以上なら単位は複数形 rate(5 minute) rate(5 minutes)
# を使うとき、曜日に書ける式は1つ cron(0 0 ? * 2#1,6#3 *) ルールを2つに分ける
1分より短い間隔は指定できない rate(90 seconds) rate(2 minutes) など

登録前に手元で確かめるスクリプト

上のルールを登録前に確かめられるよう、Python の標準ライブラリだけで動くチェック用スクリプトを用意しました。式がルールに合っているかを調べ、cron 式なら次に動く日時を3回分、JST で表示します。

"""EventBridge の cron 式 / rate 式を、登録前に手元で確かめるスクリプト。
使い方: python3 ebcron.py "cron(0 0 ? * MON-FRI *)"            # UTC の式として次回3回を JST で表示
        python3 ebcron.py "cron(0 9 ? * MON-FRI *)" Asia/Tokyo  # Scheduler 用(タイムゾーン指定)
"""
import calendar
import re
import sys
from datetime import date, datetime, timedelta
from zoneinfo import ZoneInfo

MON = {m: i for i, m in enumerate("JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC".split(), 1)}
DOW = {d: i for i, d in enumerate("SUN MON TUE WED THU FRI SAT".split(), 1)}  # 1=日 … 7=土
JA = "月火水木金土日"


def num(tok, names, lo, hi, label):
    v = names.get(tok.upper()) if names else None
    if v is None:
        if not tok.isdigit():
            raise ValueError(f"{label}: '{tok}' は使えません")
        v = int(tok)
    if not lo <= v <= hi:
        raise ValueError(f"{label}: {v} は範囲外です({lo}-{hi})")
    return v


def expand(field, lo, hi, label, names=None):
    """ , - * / を展開して値の集合にする """
    out = set()
    for part in field.split(","):
        rng, _, step = part.partition("/")
        if step and not step.isdigit():
            raise ValueError(f"{label}: '{part}' の増分が数字ではありません")
        if rng == "*":
            a, b = lo, hi
        elif "-" in rng:
            a, b = (num(x, names, lo, hi, label) for x in rng.split("-", 1))
        else:
            a = num(rng, names, lo, hi, label)
            b = hi if step else a
        if a > b:
            raise ValueError(f"{label}: '{rng}' は開始が終了より大きい範囲です")
        out.update(range(a, b + 1, int(step) if step else 1))
    return out


def parse(expr):
    """式を検査する。cron は (種類, 判定関数群)、rate は (種類, 分数) を返す"""
    m = re.fullmatch(r"(cron|rate)\((.*)\)", expr.strip())
    if not m:
        raise ValueError("cron(...) か rate(...) で囲みます")
    kind, f = m.group(1), m.group(2).split()
    if kind == "rate":
        if len(f) != 2 or not f[0].isdigit() or int(f[0]) < 1:
            raise ValueError("rate(値 単位) の形で、値は1以上の整数にします")
        if f[1] not in ("minute", "minutes", "hour", "hours", "day", "days"):
            raise ValueError(f"単位 '{f[1]}' は使えません(minute(s) / hour(s) / day(s))")
        if (int(f[0]) == 1) != (not f[1].endswith("s")):
            raise ValueError("値が1なら単数形(minute)、2以上なら複数形(minutes)にします")
        return "rate", int(f[0]) * {"m": 1, "h": 60, "d": 1440}[f[1][0]]
    if len(f) != 6:
        raise ValueError(f"フィールドは6個必要です(分 時 日 月 曜日 年)。いまは{len(f)}個")
    mi, hr, dom, mo, dow, yr = f
    if (dom == "?") == (dow == "?"):
        raise ValueError("日と曜日は、どちらか片方だけを ? にします")
    mins, hours = expand(mi, 0, 59, "分"), expand(hr, 0, 23, "時")
    months, years = expand(mo, 1, 12, "月", MON), expand(yr, 1970, 2199, "年")
    if dow == "?":
        if dom == "L":
            day_ok = lambda d: d.day == calendar.monthrange(d.year, d.month)[1]
        elif dom.upper().endswith("W"):
            n = num(dom[:-1], None, 1, 31, "日")
            day_ok = lambda d: d == nearest_weekday(d.year, d.month, n)
        else:
            days = expand(dom, 1, 31, "日")
            day_ok = lambda d: d.day in days
    else:
        aws = lambda d: d.isoweekday() % 7 + 1  # 日=1 … 土=7
        if "#" in dow:
            if "," in dow:
                raise ValueError("曜日: # を使うときは1つだけ指定します")
            w, k = dow.split("#")
            w, k = num(w, DOW, 1, 7, "曜日"), num(k, None, 1, 5, "曜日の#")
            day_ok = lambda d: aws(d) == w and (d.day - 1) // 7 + 1 == k
        elif dow.upper().endswith("L"):
            w = num(dow[:-1], DOW, 1, 7, "曜日") if len(dow) > 1 else 7
            day_ok = lambda d: aws(d) == w and d.day + 7 > calendar.monthrange(d.year, d.month)[1]
        else:
            wd = expand(dow, 1, 7, "曜日", DOW)
            day_ok = lambda d: aws(d) in wd
    return "cron", (mins, hours, months, years, day_ok)


def nearest_weekday(y, m, n):
    last = calendar.monthrange(y, m)[1]
    d = date(y, m, min(n, last))
    if d.isoweekday() == 6:      # 土曜 → 前日の金曜(月をまたぐなら翌週の月曜)
        d = d - timedelta(1) if d.day > 1 else d + timedelta(2)
    elif d.isoweekday() == 7:    # 日曜 → 翌日の月曜(月をまたぐなら前の金曜)
        d = d + timedelta(1) if d.day < last else d - timedelta(2)
    return d


def fires(expr, tz="UTC", start=None, days=800):
    """式が動く日時を、指定タイムゾーンの式として順に返す"""
    kind, spec = parse(expr)
    if kind != "cron":
        return
    mins, hours, months, years, day_ok = spec
    zone = ZoneInfo(tz)
    start = start or datetime.now(zone)
    for i in range(days):
        d = start.date() + timedelta(i)
        if d.month in months and d.year in years and day_ok(d):
            for h in sorted(hours):
                for mi in sorted(mins):
                    t = datetime(d.year, d.month, d.day, h, mi, tzinfo=zone)
                    if t > start:
                        yield t


if __name__ == "__main__":
    expr = sys.argv[1]
    tz = sys.argv[2] if len(sys.argv) > 2 else "UTC"
    try:
        kind, spec = parse(expr)
    except ValueError as e:
        sys.exit(f"NG  {expr}\n    {e}")
    print(f"OK  {expr}  ({tz} の式として解釈)")
    if kind == "rate":
        print(f"    {spec} 分ごとに実行")
    else:
        for n, t in zip(range(3), fires(expr, tz)):
            j = t.astimezone(ZoneInfo("Asia/Tokyo"))
            print(f"    {j:%Y-%m-%d}({JA[j.weekday()]}) {j:%H:%M} JST  =  {t.astimezone(ZoneInfo('UTC')):%m-%d %H:%M} UTC")

ルールに合わない式を渡すと、どこが合わないかを表示します。

python3 ebcron.py "cron(0 0 * * * *)"
python3 ebcron.py "cron(0 0 * * ?)"
python3 ebcron.py "rate(1 minutes)"
python3 ebcron.py "rate(5 minute)"
python3 ebcron.py "cron(0 9 ? * 0 *)"
python3 ebcron.py "cron(0 0 ? * 2#1,6#3 *)"
NG  cron(0 0 * * * *)
    日と曜日は、どちらか片方だけを ? にします
NG  cron(0 0 * * ?)
    フィールドは6個必要です(分 時 日 月 曜日 年)。いまは5個
NG  rate(1 minutes)
    値が1なら単数形(minute)、2以上なら複数形(minutes)にします
NG  rate(5 minute)
    値が1なら単数形(minute)、2以上なら複数形(minutes)にします
NG  cron(0 9 ? * 0 *)
    曜日: 0 は範囲外です(1-7)
NG  cron(0 0 ? * 2#1,6#3 *)
    曜日: # を使うときは1つだけ指定します

合っている式では、次回の実行日時が出ます(2026-10-05 に実行した出力)。曜日ずれの確認にそのまま使えます。

python3 ebcron.py "cron(0 23 ? * SUN-THU *)"
python3 ebcron.py "cron(0 15 L * ? *)"
python3 ebcron.py "cron(0 0 ? * 2#1 *)"
python3 ebcron.py "cron(0 8 ? * 6L *)"
OK  cron(0 23 ? * SUN-THU *)  (UTC の式として解釈)
    2026-10-06(火) 08:00 JST  =  10-05 23:00 UTC
    2026-10-07(水) 08:00 JST  =  10-06 23:00 UTC
    2026-10-08(木) 08:00 JST  =  10-07 23:00 UTC
OK  cron(0 15 L * ? *)  (UTC の式として解釈)
    2026-11-01(日) 00:00 JST  =  10-31 15:00 UTC
    2026-12-01(火) 00:00 JST  =  11-30 15:00 UTC
    2027-01-01(金) 00:00 JST  =  12-31 15:00 UTC
OK  cron(0 0 ? * 2#1 *)  (UTC の式として解釈)
    2026-11-02(月) 09:00 JST  =  11-02 00:00 UTC
    2026-12-07(月) 09:00 JST  =  12-07 00:00 UTC
    2027-01-04(月) 09:00 JST  =  01-04 00:00 UTC
OK  cron(0 8 ? * 6L *)  (UTC の式として解釈)
    2026-10-30(金) 17:00 JST  =  10-30 08:00 UTC
    2026-11-27(金) 17:00 JST  =  11-27 08:00 UTC
    2026-12-25(金) 17:00 JST  =  12-25 08:00 UTC

第2引数にタイムゾーンを渡すと、Scheduler 用の式として確かめられます。ルールの1つの式では書けなかった「第1月曜 8:00 JST」も、JST のままなら素直に書けます。

python3 ebcron.py "cron(0 8 ? * 2#1 *)" Asia/Tokyo
OK  cron(0 8 ? * 2#1 *)  (Asia/Tokyo の式として解釈)
    2026-11-02(月) 08:00 JST  =  11-01 23:00 UTC
    2026-12-07(月) 08:00 JST  =  12-06 23:00 UTC
    2027-01-04(月) 08:00 JST  =  01-03 23:00 UTC

このスクリプトは公式ドキュメントのフィールド表とルールをもとにした手元用の確認ツールです。W が月の境目にかかる場合など細かな挙動は、登録後に実行履歴でも確かめてください。

よくある質問

Q. 毎日 9:00 JST に動かす cron 式は? A. EventBridge ルールなら cron(0 0 * * ? *) です。EventBridge Scheduler なら cron(0 9 * * ? *) に --schedule-expression-timezone "Asia/Tokyo" を付けます。

Q. 平日の朝 8:00 JST に動かしたのに、土曜にも動きます。 A. 8:00 JST は UTC では前日の 23:00 です。曜日も1日前にして cron(0 23 ? * SUN-THU *) と書きます。MON-FRI のままだと火〜土の 8:00 JST に動きます。

Q. 日と曜日の両方に * を書けますか? A. 書けません。公式ドキュメントに、片方に値か * を書いたらもう片方は ? にする、とあります。毎日なら cron(0 0 * * ? *) の形にします。

Q. cron 式と rate 式はどう使い分けますか? A. 「毎日 9:00」「毎時0分」のように時計の時刻で決めたいなら cron 式、「5分おき」のように間隔だけ決めたいなら rate 式です。rate は値が1のとき単数形(rate(1 hour))にします。

まとめ

  • cron 式は cron(分 時 日 月 曜日 年) の6フィールド。日と曜日は片方を ? にする
  • ルールの時刻は UTC。JST から9時間引き、0:00〜8:59 JST は曜日・日も1つ前に書き換える
  • 曜日の数字は 1=日〜7=土。MON-FRI のように名前で書くと読みやすい
  • 月末は L、第n曜日は #、最も近い平日は W
  • rate 式は 値が1なら単数形、2以上なら複数形
  • 月末の朝や第1月曜の朝など、UTC にすると1つの式で書けない指定は EventBridge Scheduler+Asia/Tokyo で書く
  • update-schedule では タイムゾーンを毎回指定する

参考・出典

  • Amazon EventBridge User Guide「Creating a scheduled rule (legacy) in Amazon EventBridge」(cron 式・rate 式のリファレンス): https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html
  • EventBridge Scheduler User Guide「Schedule types in EventBridge Scheduler」: https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html
  • AWS CLI Command Reference「scheduler update-schedule」: https://docs.aws.amazon.com/cli/v1/reference/scheduler/update-schedule.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「EventBridge cron式の書き方|JST変換と例の早見表」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?