Java Tips | 日付・時間:スケジューラ補助

Java Java
スポンサーリンク

スケジューラ補助とは何を助けるユーティリティなのか

スケジューラ補助とは、
「決まった時間に処理を実行したい」
「一定間隔で繰り返し処理したい」
「次の実行時刻を計算したい」
といった“時間に基づく処理”を安全・正確に行うためのユーティリティです。

業務システムでは、バッチ処理、定期メンテナンス、外部APIの定期取得、ログローテーションなど、
「時間で動く処理」が大量にあります。

Java には ScheduledExecutorServiceTimer などの仕組みがありますが、
それらを“正しく”“安全に”“実務で使える形”にするには、
日付・時間の計算を補助するユーティリティが欠かせません。


スケジューラで最も重要な考え方:ズレない時間計算

「今から◯分後」ではなく「次の実行時刻」を計算する

初心者がやりがちなミスは、
「5分ごとに実行したいから、毎回 5分 sleep する」
という書き方です。

これはズレます。

なぜなら、処理時間が 1秒でも増えると、
次の実行が 5分+1秒後になり、
その次は 5分+2秒後…と、どんどんズレていくからです。

正しい考え方は、
「次の実行時刻を計算し、その時刻まで待つ」
という方式です。

Java の java.time API は、この“次の実行時刻”を計算するのに非常に向いています。


次の実行時刻を計算するユーティリティ

毎時00分に実行したい場合

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class SchedulerUtils {

    public static LocalDateTime nextEveryHour(LocalDateTime now) {
        LocalDateTime next = now.truncatedTo(ChronoUnit.HOURS).plusHours(1);
        return next;
    }
}
Java

使い方の例:

LocalDateTime now = LocalDateTime.now();
LocalDateTime next = SchedulerUtils.nextEveryHour(now);

System.out.println("現在時刻: " + now);
System.out.println("次の実行: " + next);
Java

ここで深掘りしたいのは、
truncatedTo(ChronoUnit.HOURS) で「分・秒・ナノ秒を切り捨てて、ちょうどの時間に揃える」
というテクニックです。

これにより、
「毎時00分に実行する」という要件を正確に満たせます。


一定間隔のスケジュールをズレなく計算する

「毎5分」などの固定間隔スケジュール

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class SchedulerUtils {

    public static LocalDateTime nextEveryMinutes(LocalDateTime now, int intervalMinutes) {
        long minute = now.getMinute();
        long nextMinute = ((minute / intervalMinutes) + 1) * intervalMinutes;

        LocalDateTime base = now.truncatedTo(ChronoUnit.HOURS);
        return base.plusMinutes(nextMinute);
    }
}
Java

例:

LocalDateTime now = LocalDateTime.of(2025, 3, 26, 10, 7, 30);
LocalDateTime next = SchedulerUtils.nextEveryMinutes(now, 5);

System.out.println("現在: " + now);   // 10:07:30
System.out.println("次回: " + next); // 10:10:00
Java

ここでの重要ポイントは、
「現在時刻から5分後」ではなく、「次の5分区切り」を計算している
という点です。

これにより、処理時間が長くてもスケジュールがズレません。


ScheduledExecutorService と組み合わせる

Java の標準スケジューラに「次の実行時刻」を渡す

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

public class SchedulerExample {

    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

        Runnable task = () -> {
            LocalDateTime now = LocalDateTime.now();
            System.out.println("実行: " + now);

            LocalDateTime next = SchedulerUtils.nextEveryMinutes(now, 5);
            long delay = Duration.between(now, next).toMillis();

            scheduler.schedule(thisTask(), delay, java.util.concurrent.TimeUnit.MILLISECONDS);
        };

        task.run();
    }

    private static Runnable thisTask() {
        return () -> {}; // 実際は上の task を返す
    }
}
Java

ここで深掘りしたいのは、
「固定 delay」ではなく「次の実行時刻までの delay」を計算している
という点です。

固定 delay だとズレますが、
「次の実行時刻」を計算して delay にする方式ならズレません。


毎日決まった時刻に実行するスケジュール

例:毎日 03:00 に実行したい

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class SchedulerUtils {

    public static LocalDateTime nextDaily(LocalTime targetTime) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime todayTarget = LocalDateTime.of(LocalDate.now(), targetTime);

        if (now.isBefore(todayTarget)) {
            return todayTarget;
        } else {
            return todayTarget.plusDays(1);
        }
    }
}
Java

例:

LocalDateTime next = SchedulerUtils.nextDaily(LocalTime.of(3, 0));
System.out.println("次の実行: " + next);
Java

ここでのポイントは、
「今日の 03:00 が過ぎていたら、明日の 03:00 にする」
というロジックです。

業務バッチでは非常によく使います。


セキュリティ・運用の観点から見たスケジューラ補助

スケジューラのズレは“障害”につながる

スケジューラがズレると、次のような問題が起きます。

・バッチが重複実行される
・外部APIを短時間に叩きすぎてレート制限に引っかかる
・ログローテーションがずれてディスクが埋まる
・バックアップが重なってシステムが重くなる

これらはセキュリティインシデントや障害の原因にもなります。

だからこそ、
「次の実行時刻を正確に計算するユーティリティ」
は、実務では非常に重要です。

タイムゾーンを意識しないと危険

特に「毎日◯時」は、タイムゾーンを意識しないと危険です。

サマータイムのある地域では、
「03:00 が存在しない日」「25:00 になる日」があります。

Java の ZonedDateTime を使えば、
こうした“時間の歪み”を安全に扱えます。


スケジューラ補助ユーティリティのまとめ

実務で使える形にまとめると、次のようなユーティリティになります。

import java.time.*;

public class SchedulerUtils {

    public static LocalDateTime nextEveryHour(LocalDateTime now) {
        return now.truncatedTo(ChronoUnit.HOURS).plusHours(1);
    }

    public static LocalDateTime nextEveryMinutes(LocalDateTime now, int intervalMinutes) {
        long minute = now.getMinute();
        long nextMinute = ((minute / intervalMinutes) + 1) * intervalMinutes;
        return now.truncatedTo(ChronoUnit.HOURS).plusMinutes(nextMinute);
    }

    public static LocalDateTime nextDaily(LocalTime targetTime) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime todayTarget = LocalDateTime.of(LocalDate.now(), targetTime);
        return now.isBefore(todayTarget) ? todayTarget : todayTarget.plusDays(1);
    }
}
Java

これを使えば、
「次の実行時刻」を正確に計算でき、
スケジューラのズレを防ぎ、
業務システムの安定性が大きく向上します。


まとめ:スケジューラ補助で身につけてほしい感覚

スケジューラ補助は、単なる「時間計算」ではありません。
業務システムの安定性を支える、とても重要な技術です。

次の実行時刻を計算するのが基本。
「今から◯分後」ではなく「次の◯分区切り」を計算する。
毎時・毎日・一定間隔などのパターンをユーティリティ化する。
タイムゾーンやサマータイムを意識する。
スケジューラのズレは障害につながるので、正確な計算が重要。

もしあなたのプロジェクトで、
「なんかスケジュールがズレる」「バッチが重複する」
といった問題があるなら、
まずは“次の実行時刻を計算するユーティリティ”を導入してみてください。

それだけで、システムの安定性が大きく変わります。

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