Java | Java 詳細・モダン文法:日付・時刻 API – Clock

Java Java
スポンサーリンク

Clock を一言でいうと

Clock
「“今”をどう決めるかをカプセル化するためのオブジェクト」
です。

Instant.now()LocalDateTime.now() が、裏側で「どの時計を使うか」を決めるために使うのが Clock
アプリ側で Clock を自分で持つことで、「テストしやすい現在時刻」「タイムゾーンを意識した現在時刻」を扱えるようになります。


なぜ Clock が必要になるのか

直接 now() を呼ぶと何が困るか

素朴に書くと、こうなります。

LocalDateTime now = LocalDateTime.now();
Java

一見これで十分に見えますが、問題は「テスト」と「再現性」です。

今この瞬間にテストを実行したときと、1 分後に実行したときで、now の値が変わってしまいます。
「今日が締切日かどうか」「今が営業時間内かどうか」といったロジックをテストしたいとき、
now() を直接呼んでいると、テストが「そのときの実時間」に依存してしまいます。

ここで Clock の出番です。
Clock を使うと、「今」を外から差し込めるようになります。


Clock の基本:now(Clock) という形にする

Clock を渡して現在時刻を決める

LocalDateTimeInstant には、now(Clock clock) というオーバーロードがあります。

Clock clock = Clock.systemDefaultZone(); // システムデフォルトのタイムゾーンを使う時計

LocalDateTime now = LocalDateTime.now(clock);
Instant instantNow = Instant.now(clock);
Java

ここで重要なのは、「どの Clock を使うか」を自分で選べることです。
本番では「本物の時計」、テストでは「固定された時計」を渡す、という切り替えができます。

Clock をフィールドとして持つ設計

サービスクラスなどで、こういう形にしておくと強いです。

class BillingService {
    private final Clock clock;

    BillingService(Clock clock) {
        this.clock = clock;
    }

    LocalDate today() {
        return LocalDate.now(clock);
    }
}
Java

本番コードでは Clock.systemDefaultZone()Clock.systemUTC() を渡し、
テストコードでは Clock.fixed(...) を渡す。

これだけで、「時間に依存するロジック」を、テスト可能な形にできます。


代表的な Clock 実装

systemDefaultZone と systemUTC

Clock.systemDefaultZone() は、「システムのデフォルトタイムゾーン」を使う時計です。

Clock systemClock = Clock.systemDefaultZone();
ZonedDateTime now = ZonedDateTime.now(systemClock);
Java

Clock.systemUTC() は、「UTC 固定」の時計です。

Clock utcClock = Clock.systemUTC();
Instant nowUtc = Instant.now(utcClock);
Java

アプリ全体で「内部は UTC で扱う」と決めるなら、systemUTC() を使うのが筋が良いです。

fixed:テスト用の「止まった時計」

Clock.fixed(Instant, ZoneId) は、「常に同じ時刻を返す時計」です。

Instant fixedInstant = Instant.parse("2025-01-18T10:00:00Z");
Clock fixedClock = Clock.fixed(fixedInstant, ZoneId.of("Asia/Tokyo"));

Instant now1 = Instant.now(fixedClock);
Instant now2 = Instant.now(fixedClock);

System.out.println(now1); // どちらも 2025-01-18T10:00:00Z
System.out.println(now2);
Java

テストで「今が 2025-01-18T10:00:00Z だと仮定して、このロジックがどう動くか」を検証したいときに使います。
これにより、テスト結果が「実行した時間」に左右されなくなります。

offset:基準の Clock からズラした時計

Clock.offset(Clock base, Duration offset) は、「ある時計から一定時間ずらした時計」です。

Clock base = Clock.systemUTC();
Clock plusOneHour = Clock.offset(base, Duration.ofHours(1));

Instant baseNow = Instant.now(base);
Instant plusNow = Instant.now(plusOneHour);
Java

「1 時間後の世界をシミュレーションしたい」といったテストで使えます。


Clock を使うとテストがどう楽になるか

直接 now() を呼んでいるコードのつらさ

例えば、こんなメソッドを考えます。

boolean isToday(LocalDate date) {
    return LocalDate.now().equals(date);
}
Java

このメソッドをテストしたいとき、「テスト実行時の“今日”」に依存してしまいます。
明日になったらテストコードを書き換えないといけない、という最悪の状態です。

Clock を注入する形に変える

Clock を使うと、こう書き換えられます。

class DateChecker {
    private final Clock clock;

    DateChecker(Clock clock) {
        this.clock = clock;
    }

    boolean isToday(LocalDate date) {
        return LocalDate.now(clock).equals(date);
    }
}
Java

テストでは、こうします。

Instant fixedInstant = Instant.parse("2025-01-18T00:00:00Z");
Clock fixedClock = Clock.fixed(fixedInstant, ZoneId.of("Asia/Tokyo"));

DateChecker checker = new DateChecker(fixedClock);

assert checker.isToday(LocalDate.of(2025, 1, 18)); // true
assert !checker.isToday(LocalDate.of(2025, 1, 19)); // false
Java

「テストの中で“今日”を自由に決められる」ようになるのが、Clock 最大の価値です。


設計としての一番大事なポイント:「時間を依存性として扱う」

「時間も依存性注入する」という発想

データベース接続や外部 API クライアントを DI するのと同じように、
「現在時刻を決める仕組み(Clock)」も DI する、という発想がモダンな設計です。

時間を直接 now() で取ってしまうと、そのメソッドは「環境にべったり依存したコード」になります。
Clock を受け取るようにしておけば、

本番環境では「本物の時計」
テスト環境では「固定された時計」

を差し替えられる、柔軟な設計になります。

「どこまで Clock を伝播させるか」を決める

現実的には、アプリ全体で 1 個の Clock を管理し、
サービス層やドメイン層にコンストラクタ経由で渡していく、という形が多いです。

コントローラ層で Clock を new してしまうのではなく、
DI コンテナ(Spring など)で Clock を Bean として管理し、
必要なクラスに注入する、というスタイルにすると、設計がさらにきれいになります。


まとめ:Clock を自分の言葉で説明するなら

あなたの言葉で Clock を説明するなら、こうです。

Clock は、“今”をどう決めるかをオブジェクトとして切り出したもの。
LocalDateTime.now() のように直接現在時刻を取るのではなく、
LocalDateTime.now(clock) のように Clock を渡すことで、
本番では本物の時計、テストでは固定された時計を使い分けられる。
時間を依存性として扱うことで、“時間に依存するロジック”をテストしやすく、再現性のあるコードにできる。」

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