Java Tips | 日付・時間:CRON生成

Java Java
スポンサーリンク

CRON生成とは何をするユーティリティか

ここまで「CRON解析(読む側)」をやってきましたが、今回は逆方向です。
CRON生成とは、アプリ側で「こういうスケジュールで動かしたい」という条件から、
0 3 * * * のような CRON 文字列を安全に組み立てるユーティリティのことです。

業務システムでは、管理画面で「毎日 3:00 に実行」「平日 9:00 に実行」「5分ごとに実行」などを選ばせて、
その設定を CRON 形式で保存したり、ジョブスケジューラに渡したりすることがよくあります。
このとき、文字列を手書きで組み立てるとバグや誤設定が起きやすいので、
「CRON生成ユーティリティ」を用意しておくと、実務でかなり安心できます。


CRONの基本形をもう一度整理する

5フィールドのCRON

一般的な(UNIX系の)CRONは、次の5フィールドで構成されます。

分 時 日 月 曜日

例として、よく見るものを挙げます。

0 3 * * * … 毎日 03:00
*/5 * * * * … 5分ごと
0 0 1 * * … 毎月1日の 00:00
0 9 * * 1-5 … 平日 09:00

CRON生成ユーティリティは、
「人間にとって分かりやすい指定(毎日、平日、毎月、◯分ごと…)」から、
この5フィールドの文字列を組み立てる役割を持ちます。


まずは「パターン」を決めてしまう

よくある業務パターンを型にする

いきなり「自由入力で CRON を作る」のは難しいので、
実務でよく出てくるパターンを“型”として決めてしまうのが現実的です。

例えば、次のような列挙型を用意します。

public enum ScheduleType {
    EVERY_DAY,      // 毎日
    EVERY_WEEKDAY,  // 平日
    EVERY_WEEK,     // 毎週◯曜日
    EVERY_MONTH,    // 毎月◯日
    EVERY_MINUTES   // ◯分ごと
}
Java

そして、「型+パラメータ」から CRON を生成するクラスを作ります。
こうしておくと、「この画面ではこのパターンしか選べない」と制約できるので、
危険な CRON(例えば毎秒実行など)を防ぎやすくなります。


毎日・平日のCRONを生成する

毎日◯時◯分に実行するCRON

「毎日 03:00」のような CRON を生成するメソッドです。

public class CronGenerator {

    public static String everyDayAt(int hour, int minute) {
        validateHour(hour);
        validateMinute(minute);
        return String.format("%d %d * * *", minute, hour);
    }

    private static void validateHour(int hour) {
        if (hour < 0 || hour > 23) {
            throw new IllegalArgumentException("hour は 0〜23 で指定してください: " + hour);
        }
    }

    private static void validateMinute(int minute) {
        if (minute < 0 || minute > 59) {
            throw new IllegalArgumentException("minute は 0〜59 で指定してください: " + minute);
        }
    }
}
Java

使い方の例です。

String cron = CronGenerator.everyDayAt(3, 0);
System.out.println(cron); // 0 3 * * *
Java

ここで重要なのは、「必ずバリデーションを通す」ということです。
CRONは文字列なので、間違った値を入れてもコンパイルエラーになりません。
だからこそ、ユーティリティ側で「0〜23」「0〜59」のチェックを必ず行うべきです。

平日◯時◯分に実行するCRON

平日(一般的には月〜金)だけ動かしたい場合です。

public class CronGenerator {

    public static String everyWeekdayAt(int hour, int minute) {
        validateHour(hour);
        validateMinute(minute);
        // 曜日フィールド: 1-5 (多くのCRON実装で 1=月, 5=金)
        return String.format("%d %d * * 1-5", minute, hour);
    }
}
Java

例です。

String cron = CronGenerator.everyWeekdayAt(9, 0);
System.out.println(cron); // 0 9 * * 1-5
Java

ここで深掘りしたいのは、「曜日の数値は実装によって違うことがある」という点です。
多くの実装では 0=日, 1=月, … ですが、0と7の扱いなど微妙な差があります。
そのため、「どのCRONエンジンを使うか」を決めたうえで、その仕様に合わせて生成する必要があります。


毎週・毎月のCRONを生成する

毎週◯曜日◯時◯分に実行

例えば「毎週月曜の 10:00」のようなパターンです。

public class CronGenerator {

    // dayOfWeek: 1=月, 7=日 という前提(使うエンジンに合わせて調整)
    public static String everyWeekAt(int dayOfWeek, int hour, int minute) {
        validateHour(hour);
        validateMinute(minute);
        validateDayOfWeek(dayOfWeek);
        return String.format("%d %d * * %d", minute, hour, dayOfWeek);
    }

    private static void validateDayOfWeek(int dayOfWeek) {
        if (dayOfWeek < 0 || dayOfWeek > 7) {
            throw new IllegalArgumentException("曜日は 0〜7 で指定してください: " + dayOfWeek);
        }
    }
}
Java

例です。

String cron = CronGenerator.everyWeekAt(1, 10, 0); // 月曜 10:00
System.out.println(cron); // 0 10 * * 1
Java

ここでのポイントは、「曜日の数値を呼び出し側に生で書かせない」ことです。
本当は DayOfWeekjava.time.DayOfWeek)を受け取って、内部で数値に変換する方が安全です。

毎月◯日◯時◯分に実行

「毎月1日の 00:00」のようなパターンです。

public class CronGenerator {

    public static String everyMonthOn(int dayOfMonth, int hour, int minute) {
        validateHour(hour);
        validateMinute(minute);
        validateDayOfMonth(dayOfMonth);
        return String.format("%d %d %d * *", minute, hour, dayOfMonth);
    }

    private static void validateDayOfMonth(int dayOfMonth) {
        if (dayOfMonth < 1 || dayOfMonth > 31) {
            throw new IllegalArgumentException("日付は 1〜31 で指定してください: " + dayOfMonth);
        }
    }
}
Java

例です。

String cron = CronGenerator.everyMonthOn(1, 0, 0);
System.out.println(cron); // 0 0 1 * *
Java

ここで深掘りしたいのは、「存在しない日付(例えば2月30日)」の扱いです。
CRON自体は「31日」と書けてしまいますが、実際の月によっては存在しない日になります。
このあたりは「CRONエンジン側の仕様」に依存するので、
ユーティリティ側で「31日は許可しない」などのポリシーを決めることもあります。


「◯分ごと」「◯時間ごと」のCRONを生成する

◯分ごとに実行するCRON

「5分ごと」「10分ごと」などのパターンです。

public class CronGenerator {

    public static String everyMinutes(int intervalMinutes) {
        if (intervalMinutes <= 0 || intervalMinutes > 59) {
            throw new IllegalArgumentException("intervalMinutes は 1〜59 で指定してください: " + intervalMinutes);
        }
        return String.format("*/%d * * * *", intervalMinutes);
    }
}
Java

例です。

String cron = CronGenerator.everyMinutes(5);
System.out.println(cron); // */5 * * * *
Java

ここでの重要ポイントは、「危険な値を禁止する」ことです。
例えば「1分ごと」は許可するが、「1秒ごと」は許可しない、などです。
CRONエンジンによっては秒フィールドもあるので、
「秒フィールド付きCRON」を使う場合は、さらに慎重な制限が必要です。

◯時間ごとに実行するCRON

「1時間ごと」「6時間ごと」などのパターンです。

public class CronGenerator {

    public static String everyHours(int intervalHours) {
        if (intervalHours <= 0 || intervalHours > 23) {
            throw new IllegalArgumentException("intervalHours は 1〜23 で指定してください: " + intervalHours);
        }
        return String.format("0 */%d * * *", intervalHours);
    }
}
Java

例です。

String cron = CronGenerator.everyHours(6);
System.out.println(cron); // 0 */6 * * *
Java

ここでも、「24時間ごと」は「毎日◯時」と同じ意味になるので、
everyDayAt を使わせる、などのルールを決めておくと設計がきれいになります。


CRON生成ユーティリティを一つにまとめる

ここまでのメソッドをまとめた、シンプルな CRON生成ユーティリティの例です。

public class CronGenerator {

    public static String everyDayAt(int hour, int minute) {
        validateHour(hour);
        validateMinute(minute);
        return String.format("%d %d * * *", minute, hour);
    }

    public static String everyWeekdayAt(int hour, int minute) {
        validateHour(hour);
        validateMinute(minute);
        return String.format("%d %d * * 1-5", minute, hour);
    }

    public static String everyWeekAt(int dayOfWeek, int hour, int minute) {
        validateHour(hour);
        validateMinute(minute);
        validateDayOfWeek(dayOfWeek);
        return String.format("%d %d * * %d", minute, hour, dayOfWeek);
    }

    public static String everyMonthOn(int dayOfMonth, int hour, int minute) {
        validateHour(hour);
        validateMinute(minute);
        validateDayOfMonth(dayOfMonth);
        return String.format("%d %d %d * *", minute, hour, dayOfMonth);
    }

    public static String everyMinutes(int intervalMinutes) {
        if (intervalMinutes <= 0 || intervalMinutes > 59) {
            throw new IllegalArgumentException("intervalMinutes は 1〜59 で指定してください: " + intervalMinutes);
        }
        return String.format("*/%d * * * *", intervalMinutes);
    }

    public static String everyHours(int intervalHours) {
        if (intervalHours <= 0 || intervalHours > 23) {
            throw new IllegalArgumentException("intervalHours は 1〜23 で指定してください: " + intervalHours);
        }
        return String.format("0 */%d * * *", intervalHours);
    }

    private static void validateHour(int hour) {
        if (hour < 0 || hour > 23) {
            throw new IllegalArgumentException("hour は 0〜23 で指定してください: " + hour);
        }
    }

    private static void validateMinute(int minute) {
        if (minute < 0 || minute > 59) {
            throw new IllegalArgumentException("minute は 0〜59 で指定してください: " + minute);
        }
    }

    private static void validateDayOfWeek(int dayOfWeek) {
        if (dayOfWeek < 0 || dayOfWeek > 7) {
            throw new IllegalArgumentException("曜日は 0〜7 で指定してください: " + dayOfWeek);
        }
    }

    private static void validateDayOfMonth(int dayOfMonth) {
        if (dayOfMonth < 1 || dayOfMonth > 31) {
            throw new IllegalArgumentException("日付は 1〜31 で指定してください: " + dayOfMonth);
        }
    }
}
Java

このユーティリティを使えば、
画面側では「毎日」「平日」「毎週」「毎月」「◯分ごと」「◯時間ごと」といった選択肢を出し、
選ばれたパターンと時刻・間隔から CRON を安全に生成できます。


セキュリティ・運用の観点から見た CRON生成

CRON生成は、単に「文字列を作る」だけではありません。
セキュリティと運用の観点から、次のようなことを意識する必要があります。

危険なスケジュール(毎秒、1分未満の間隔など)を禁止する。
夜間バッチと重なる時間帯を避けるように制約する。
曜日や日付の範囲を、業務ルールに合わせて制限する。
生成した CRON をログに残し、「誰がいつどんなスケジュールを設定したか」を追跡できるようにする。

CRON生成ユーティリティに「バリデーション」と「制約」をしっかり埋め込んでおくことで、
誤設定や悪意ある設定からシステムを守ることができます。


まとめ:CRON生成で身につけてほしい感覚

CRON生成は、「人間に分かりやすいスケジュール指定」から「機械が解釈できる CRON 文字列」を作る橋渡しです。

よく使うパターン(毎日・平日・毎週・毎月・◯分ごと・◯時間ごと)を“型”として決める。
時刻・日付・曜日・間隔には必ずバリデーションをかける。
危険なスケジュールはユーティリティ側で禁止する。
CRONの仕様(曜日の数値、秒フィールドの有無など)は、使うエンジンに合わせて明確にする。

もし今、あなたのシステムで「CRON文字列を画面からそのまま入力させている」ような箇所があるなら、
そこを一度、「CRON生成ユーティリティ+選択式UI」に置き換えられないか考えてみてください。

それだけで、設定ミスや運用トラブルのリスクがかなり減り、
“実務で使えるスケジューラ”に一歩近づきます。

タイトルとURLをコピーしました