Java Tips | 日付・時間:時間丸め

Java Java
スポンサーリンク

「時間丸め」とは何をするものか

「10:03 を 5 分単位に丸めて 10:05 にしたい」「ログの時刻を“分単位”にそろえたい」「勤務時間を 15 分単位で計算したい」
こういうときに出てくるのが「時間丸め」です。

時間丸めは、“ある時刻を、指定した単位(5 分・15 分・1 時間など)の“きりのいいところ”に揃える処理” です。
切り捨て(floor)、切り上げ(ceil)、四捨五入(round)の 3 パターンを意識しておくと整理しやすくなります。

情報セキュリティの観点では、
「ログの時刻をあえて丸めて保存して、個人の行動を“秒単位”で追えないようにする」
といった“匿名化・マスキング”のテクニックとしても使われます。


基本方針:秒や分を「数値」として扱って丸める

なぜ「LocalDateTime のまま」丸めようとしない方がよいか

LocalDateTime は「年月日+時分秒」をまとめて持つクラスです。
これをそのまま丸めようとすると、
「分を丸めた結果 60 分になったらどうする?」「日付をまたいだら?」といった細かい処理が増えます。

そこで、一度“分”や“秒”を数値として取り出して丸め、最後に時刻に戻すという方針を取ると、
ロジックがかなりスッキリします。


分単位の丸め(5 分単位・15 分単位など)

分を切り捨てる(floor)

例:10:07 を 5 分単位で切り捨て → 10:05
10:14 を 15 分単位で切り捨て → 10:00

import java.time.LocalDateTime;

public class RoundDownMinutes {

    public static LocalDateTime floorToMinutes(LocalDateTime dt, int unitMinutes) {
        int minute = dt.getMinute();
        int floored = (minute / unitMinutes) * unitMinutes;

        return dt.withMinute(floored)
                 .withSecond(0)
                 .withNano(0);
    }

    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2025, 3, 26, 10, 7, 30);

        LocalDateTime floor5  = floorToMinutes(dt, 5);
        LocalDateTime floor15 = floorToMinutes(dt, 15);

        System.out.println("元   : " + dt);       // 2025-03-26T10:07:30
        System.out.println("5分切捨て : " + floor5);  // 2025-03-26T10:05
        System.out.println("15分切捨て: " + floor15); // 2025-03-26T10:00
    }
}
Java

ここで重要なのは、
(minute / unitMinutes) * unitMinutes で「丸め後の分」を計算し、
秒・ナノ秒は 0 にリセットしていることです。

「丸めた結果の時刻は、“その単位の先頭”に揃える」という感覚を持ってください。


分を切り上げる(ceil)

例:10:07 を 5 分単位で切り上げ → 10:10
10:00 ちょうどはそのまま → 10:00

import java.time.LocalDateTime;

public class RoundUpMinutes {

    public static LocalDateTime ceilToMinutes(LocalDateTime dt, int unitMinutes) {
        int minute = dt.getMinute();
        int mod = minute % unitMinutes;

        if (mod == 0 && dt.getSecond() == 0 && dt.getNano() == 0) {
            // すでにきりのいい時間ならそのまま
            return dt.withSecond(0).withNano(0);
        }

        int up = minute + (unitMinutes - mod);
        if (up >= 60) {
            // 60分を超えたら、1時間進めて分を0に
            dt = dt.plusHours(1);
            up = 0;
        }

        return dt.withMinute(up)
                 .withSecond(0)
                 .withNano(0);
    }

    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2025, 3, 26, 10, 7, 30);

        LocalDateTime ceil5 = ceilToMinutes(dt, 5);

        System.out.println("元   : " + dt);      // 2025-03-26T10:07:30
        System.out.println("5分切上げ: " + ceil5); // 2025-03-26T10:10
    }
}
Java

ここで深掘りしたいポイントは、
「切り上げは“次のきりのいい時間”に進める処理であり、60 分を超えたら時間も進める必要がある」ことです。

日付をまたぐケース(23:58 を 5 分切り上げ → 翌日 0:00)も同じ考え方で処理できます。


分を四捨五入する(round)

例:10:07 を 5 分単位で四捨五入 → 10:05(7 分は 5 に近い)
10:08 を 5 分単位で四捨五入 → 10:10(8 分は 10 に近い)

import java.time.LocalDateTime;

public class RoundNearestMinutes {

    public static LocalDateTime roundToMinutes(LocalDateTime dt, int unitMinutes) {
        int minute = dt.getMinute();
        int second = dt.getSecond();
        int nano   = dt.getNano();

        // 秒・ナノ秒も「分の小数部分」として考慮したい場合はここを工夫する
        double totalMinutes = minute + (second / 60.0) + (nano / 1_000_000_000.0 / 60.0);

        int rounded = (int) Math.round(totalMinutes / unitMinutes) * unitMinutes;

        if (rounded >= 60) {
            dt = dt.plusHours(1);
            rounded = 0;
        }

        return dt.withMinute(rounded)
                 .withSecond(0)
                 .withNano(0);
    }

    public static void main(String[] args) {
        LocalDateTime dt1 = LocalDateTime.of(2025, 3, 26, 10, 7, 0);
        LocalDateTime dt2 = LocalDateTime.of(2025, 3, 26, 10, 8, 0);

        System.out.println("10:07 → " + roundToMinutes(dt1, 5)); // 10:05
        System.out.println("10:08 → " + roundToMinutes(dt2, 5)); // 10:10
    }
}
Java

ここでのキモは、
「分+秒+ナノ秒を“分の小数”として扱い、Math.round で丸めている」ことです。

業務によっては「秒は無視して分だけで四捨五入する」などのルールもあるので、
「何を丸めの対象にするか(秒を含めるかどうか)」を仕様として決めることが大事です。


時間単位(1 時間・2 時間単位など)の丸め

時を切り捨てる(floor)

例:10:30 を 1 時間単位で切り捨て → 10:00
10:30 を 2 時間単位で切り捨て → 10:00
23:30 を 2 時間単位で切り捨て → 22:00

import java.time.LocalDateTime;

public class RoundDownHours {

    public static LocalDateTime floorToHours(LocalDateTime dt, int unitHours) {
        int hour = dt.getHour();
        int floored = (hour / unitHours) * unitHours;

        return dt.withHour(floored)
                 .withMinute(0)
                 .withSecond(0)
                 .withNano(0);
    }

    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2025, 3, 26, 10, 30);

        System.out.println("1時間切捨て: " + floorToHours(dt, 1)); // 10:00
        System.out.println("2時間切捨て: " + floorToHours(dt, 2)); // 10:00
    }
}
Java

考え方は分単位と同じで、
「時を数値として扱い、単位で割って掛け直す」だけです。

切り上げ・四捨五入も同じパターンで書けます。


セキュリティ観点での「時間丸め」

ログの時刻を丸めて“追跡精度”を落とす

情報セキュリティの世界では、
「ログを分析すると、ユーザーの行動パターンが秒単位で丸見えになる」という問題があります。

例えば、
「毎日 23:58:12 にアクセスしているユーザー」
という情報は、個人を特定する手がかりになり得ます。

そこで、ログ保存時に時刻を丸めてしまうというテクニックがあります。

例:
・秒以下を 0 にする(分単位まで)
・5 分単位に切り捨てる
・1 時間単位に切り捨てる

import java.time.LocalDateTime;

public class LogTimeMasking {

    public static LocalDateTime maskToMinutes(LocalDateTime dt) {
        // 秒・ナノ秒を0にして「分単位」までにする
        return dt.withSecond(0).withNano(0);
    }

    public static LocalDateTime maskTo5Minutes(LocalDateTime dt) {
        // 5分単位に切り捨て
        int minute = dt.getMinute();
        int floored = (minute / 5) * 5;
        return dt.withMinute(floored).withSecond(0).withNano(0);
    }

    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2025, 3, 26, 10, 7, 42);

        System.out.println("元        : " + dt);                 // 10:07:42
        System.out.println("分単位    : " + maskToMinutes(dt));  // 10:07:00
        System.out.println("5分単位   : " + maskTo5Minutes(dt)); // 10:05:00
    }
}
Java

ここでのポイントは、
「分析に必要な精度」と「プライバシー保護」のバランスを、丸めの単位で調整できることです。

・セキュリティインシデント調査には秒単位が必要か?
・業務分析には分単位で十分か?

こういった問いに答えながら、
「どこまで丸めるか」を設計するのが“情報セキュリティのプロ”の視点です。


時間丸めユーティリティとしてまとめる

「丸め方」と「単位」をパラメータにする

時間丸めは、あちこちで似たようなコードを書きがちなので、
ユーティリティクラスにまとめておくと再利用しやすくなります。

import java.time.LocalDateTime;

public class TimeRoundingUtils {

    public enum Mode {
        FLOOR, CEIL, ROUND
    }

    public static LocalDateTime roundMinutes(LocalDateTime dt, int unitMinutes, Mode mode) {
        return switch (mode) {
            case FLOOR -> floorToMinutes(dt, unitMinutes);
            case CEIL  -> ceilToMinutes(dt, unitMinutes);
            case ROUND -> roundToMinutes(dt, unitMinutes);
        };
    }

    private static LocalDateTime floorToMinutes(LocalDateTime dt, int unitMinutes) {
        int minute = dt.getMinute();
        int floored = (minute / unitMinutes) * unitMinutes;
        return dt.withMinute(floored).withSecond(0).withNano(0);
    }

    private static LocalDateTime ceilToMinutes(LocalDateTime dt, int unitMinutes) {
        int minute = dt.getMinute();
        int mod = minute % unitMinutes;
        if (mod == 0 && dt.getSecond() == 0 && dt.getNano() == 0) {
            return dt.withSecond(0).withNano(0);
        }
        int up = minute + (unitMinutes - mod);
        if (up >= 60) {
            dt = dt.plusHours(1);
            up = 0;
        }
        return dt.withMinute(up).withSecond(0).withNano(0);
    }

    private static LocalDateTime roundToMinutes(LocalDateTime dt, int unitMinutes) {
        int minute = dt.getMinute();
        int second = dt.getSecond();
        int nano   = dt.getNano();
        double totalMinutes = minute + (second / 60.0) + (nano / 1_000_000_000.0 / 60.0);
        int rounded = (int) Math.round(totalMinutes / unitMinutes) * unitMinutes;
        if (rounded >= 60) {
            dt = dt.plusHours(1);
            rounded = 0;
        }
        return dt.withMinute(rounded).withSecond(0).withNano(0);
    }
}
Java

こうしておけば、呼び出し側は

LocalDateTime rounded =
    TimeRoundingUtils.roundMinutes(now, 5, TimeRoundingUtils.Mode.FLOOR);
Java

のように、
「何分単位で」「どの丸め方で」という意図を、コードでそのまま表現できます。


まとめ:時間丸めで身につけてほしい感覚

時間丸めは、「時刻を“きりのいい単位”に揃える」処理です。

分単位・時間単位など、まず「どの単位で丸めるか」を決める。
切り捨て・切り上げ・四捨五入の 3 パターンを意識し、
分や秒を“数値”として扱ってから丸め、最後に時刻に戻す。
ログや監査情報では、丸めを使って“必要以上に細かい時刻情報を持たない”というセキュリティ設計もあり得る。
共通ユーティリティにして、「単位」と「丸め方」をパラメータ化すると、実務コードがかなりきれいになる。

もしあなたのコードのどこかに、
「if 文で 0〜4 分は切り捨て、5〜9 分は切り上げ…」のようなゴリゴリの分岐があれば、
そこを一度「数値として丸める」スタイルに置き換えられないか眺めてみてください。

その小さな整理が、
“時間にもセキュリティにも強いエンジニア”への、確かな一歩になります。

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