Java Tips | 日付・時間:ミリ秒変換

Java Java
スポンサーリンク

「ミリ秒変換」で何をしたいのかイメージする

「処理時間をミリ秒で計測したい」「DBには long のミリ秒で保存されている」「外部APIが“1970年からのミリ秒”で送ってくる」。
こういうときに必要になるのが、日付・時間と「ミリ秒」の相互変換です。

Java では、昔から System.currentTimeMillis() がよく使われてきました。
これは「1970-01-01T00:00:00Z(UTC)」からの経過ミリ秒、いわゆる“エポックミリ秒”です。
今の Java 日付・時間API(InstantLocalDateTime など)と、このエポックミリ秒をどう行き来するかを整理しておくと、業務コードがかなり書きやすくなります。


エポックミリ秒とは何か

1970-01-01T00:00:00Z を基準にした「経過時間」

エポックミリ秒は、「1970-01-01T00:00:00Z(UTC)」からの経過時間をミリ秒単位で表したものです。
例えば、0 はちょうど 1970-01-01T00:00:00Z。
1_000 は 1 秒後。
1_000 * 60 * 60 * 24 は 1 日後、という具合です。

System.currentTimeMillis() は、まさにこのエポックミリ秒を返します。

public class CurrentTimeMillisExample {

    public static void main(String[] args) {
        long millis = System.currentTimeMillis();
        System.out.println("現在のエポックミリ秒: " + millis);
    }
}
Java

ここで重要なのは、「この値は“UTC基準”であり、タイムゾーンの影響を受けない」ということです。
ローカルタイム(JST など)に変換するかどうかは、あとで別途決めます。


Instant とミリ秒の相互変換

Instant → ミリ秒

Instant は「UTC 上の瞬間」です。
これをエポックミリ秒に変換するのはとても簡単で、toEpochMilli() を使います。

import java.time.Instant;

public class InstantToMillis {

    public static void main(String[] args) {
        Instant now = Instant.now();

        long millis = now.toEpochMilli();

        System.out.println("Instant : " + now);
        System.out.println("ミリ秒  : " + millis);
    }
}
Java

ここで押さえておきたいのは、「内部では Instant を使い、保存や通信のときだけミリ秒にする」というスタイルです。
Instant はナノ秒精度を持っていますが、toEpochMilli() するとミリ秒に丸められます。

ミリ秒 → Instant

逆に、エポックミリ秒から Instant を作るときは Instant.ofEpochMilli を使います。

import java.time.Instant;

public class MillisToInstant {

    public static void main(String[] args) {
        long millis = 1_742_950_000_000L; // 例

        Instant instant = Instant.ofEpochMilli(millis);

        System.out.println("ミリ秒  : " + millis);
        System.out.println("Instant: " + instant);
    }
}
Java

この二つを覚えておけば、「ミリ秒 ⇔ Instant」の変換はほぼ怖くなくなります。


ミリ秒とローカル日時(JSTなど)の変換

ミリ秒 → 日本時間の LocalDateTime

DB にエポックミリ秒が入っていて、それを日本時間の日時として表示したい、というケースです。

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MillisToJstDateTime {

    public static void main(String[] args) {
        long millis = 1_742_950_000_000L; // 例

        Instant instant = Instant.ofEpochMilli(millis);

        ZoneId jst = ZoneId.of("Asia/Tokyo");
        ZonedDateTime jstZdt = instant.atZone(jst);

        LocalDateTime jstLocal = jstZdt.toLocalDateTime();

        System.out.println("ミリ秒          : " + millis);
        System.out.println("Instant(UTC)   : " + instant);
        System.out.println("JST ZonedDateTime: " + jstZdt);
        System.out.println("JST LocalDateTime: " + jstLocal);
    }
}
Java

流れとしては、
ミリ秒 → Instant → JST の ZonedDateTimeLocalDateTime
というステップです。

ここで深掘りしたいのは、「ミリ秒はあくまで UTC 基準の“瞬間”であり、JST かどうかは後から決める」という感覚です。
タイムゾーンを変えれば、同じミリ秒でも別の国の時刻として表示できます。

日本時間の LocalDateTime → ミリ秒

今度は逆に、「画面で入力された日本時間の日時をミリ秒で保存したい」ケースです。

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class JstDateTimeToMillis {

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

        ZoneId jst = ZoneId.of("Asia/Tokyo");
        ZonedDateTime jstZdt = jstLocal.atZone(jst);

        long millis = jstZdt.toInstant().toEpochMilli();

        System.out.println("JST LocalDateTime: " + jstLocal);
        System.out.println("JST ZonedDateTime: " + jstZdt);
        System.out.println("エポックミリ秒  : " + millis);
    }
}
Java

ここでのポイントは、「LocalDateTime にタイムゾーンをかぶせてから Instant にし、そのうえでミリ秒にする」ということです。
LocalDateTime 単体では「どこの国の時間か」が分からないので、必ず ZoneId を通してあげます。


処理時間計測とミリ秒

開始時刻と終了時刻の差をミリ秒で出す

「この処理に何ミリ秒かかったか」を測りたいときにも、ミリ秒変換はよく使われます。

public class ElapsedMillisExample {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        doHeavyWork();

        long end = System.currentTimeMillis();

        long elapsed = end - start;

        System.out.println("処理時間(ミリ秒): " + elapsed);
    }

    private static void doHeavyWork() {
        try {
            Thread.sleep(500); // 0.5秒だけ待つ
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Java

System.currentTimeMillis() は「壁時計の時間」なので、
OS の時刻調整の影響を受ける可能性があります。
純粋に経過時間だけを測りたい場合は、System.nanoTime() を使う方が安全です。

public class ElapsedNanosExample {

    public static void main(String[] args) {
        long start = System.nanoTime();

        doHeavyWork();

        long end = System.nanoTime();

        long elapsedNanos = end - start;
        long elapsedMillis = elapsedNanos / 1_000_000L;

        System.out.println("処理時間(ミリ秒換算): " + elapsedMillis);
    }

    private static void doHeavyWork() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Java

ここで深掘りしたいのは、「時刻そのもの」と「経過時間」は別物だ、ということです。
時刻は InstantSystem.currentTimeMillis()、経過時間は DurationSystem.nanoTime() といった具合に、用途に応じて使い分けるとバグが減ります。


Duration とミリ秒の変換

Duration → ミリ秒

Duration は「時間の長さ」を表すクラスです。
これをミリ秒に変換するには toMillis() を使います。

import java.time.Duration;

public class DurationToMillis {

    public static void main(String[] args) {
        Duration d = Duration.ofMinutes(2).plusSeconds(30); // 2分30秒

        long millis = d.toMillis();

        System.out.println("Duration : " + d);
        System.out.println("ミリ秒   : " + millis); // 150000
    }
}
Java

ミリ秒 → Duration

逆に、ミリ秒から Duration を作るときは Duration.ofMillis を使います。

import java.time.Duration;

public class MillisToDuration {

    public static void main(String[] args) {
        long millis = 150_000L;

        Duration d = Duration.ofMillis(millis);

        System.out.println("ミリ秒   : " + millis);
        System.out.println("Duration: " + d); // PT2M30S
    }
}
Java

「処理時間をミリ秒で測るけれど、内部では Duration で扱う」といった設計にすると、
「秒に直したい」「分に直したい」といった変換も簡単になります。


セキュリティ・ログ設計とミリ秒

ログの“粒度”をどうするか

セキュリティインシデント調査では、ログの時刻が「どれくらい細かいか」が重要になります。
ミリ秒まで記録しておけば、イベントの順序をかなり正確に追えますが、
一方で「ユーザーの行動パターンを非常に細かく追跡できてしまう」という側面もあります。

設計としては、例えば次のような考え方があります。

内部的には Instant(ナノ秒精度)で持つ。
ログ出力はミリ秒までにする。
画面表示は秒まで、あるいは分までに丸める。

こうして「内部の精度」と「外に見せる精度」を分けておくと、
必要なときには高精度な情報を使いつつ、普段は過剰な情報を出さない、というバランスが取りやすくなります。

ミリ秒変換ユーティリティを用意しておき、「ログに出すときは必ずここを通す」と決めておくと、
粒度の統一や変更もやりやすくなります。


ミリ秒変換ユーティリティとしてまとめる

「Instant ⇔ ミリ秒」「JST ⇔ ミリ秒」を一か所に閉じ込める

ミリ秒変換も、あちこちで同じようなコードを書きがちなので、ユーティリティにまとめておくと実務でかなり楽になります。

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MillisUtils {

    private static final ZoneId JST = ZoneId.of("Asia/Tokyo");

    public static long instantToMillis(Instant instant) {
        return instant.toEpochMilli();
    }

    public static Instant millisToInstant(long millis) {
        return Instant.ofEpochMilli(millis);
    }

    public static long jstLocalToMillis(LocalDateTime localJst) {
        ZonedDateTime zdt = localJst.atZone(JST);
        return zdt.toInstant().toEpochMilli();
    }

    public static LocalDateTime millisToJstLocal(long millis) {
        Instant instant = Instant.ofEpochMilli(millis);
        return instant.atZone(JST).toLocalDateTime();
    }
}
Java

呼び出し側は、例えばこう書けます。

LocalDateTime jst = LocalDateTime.of(2025, 3, 26, 10, 5, 30);
long millis = MillisUtils.jstLocalToMillis(jst);

LocalDateTime back = MillisUtils.millisToJstLocal(millis);
Java

「ミリ秒を触るときは必ず MillisUtils を通す」と決めてしまえば、
エポックの基準やタイムゾーンの扱いを間違えるリスクが一気に減ります。


まとめ:ミリ秒変換で身につけてほしい感覚

ミリ秒変換は、「エポックミリ秒」と「日時オブジェクト(Instant や LocalDateTime)」を行き来することです。

エポックミリ秒は「1970-01-01T00:00:00Z からの経過時間」であり、UTC 基準である。
“瞬間”として扱うなら Instant を使い、toEpochMilliofEpochMilli でミリ秒と相互変換する。
ローカル時間(JST など)とは、ZoneIdZonedDateTime を経由して変換する。
処理時間の計測には、System.currentTimeMillis() だけでなく System.nanoTime()Duration も選択肢に入れる。

もしあなたのコードのどこかに、「1970年からのミリ秒らしい long を、直接割り算して日付を計算している」ような箇所があれば、
そこを一度 InstantZonedDateTime ベースのミリ秒変換に置き換えられないか眺めてみてください。

その小さな置き換えが、
“時間にもセキュリティにも強いエンジニア”への、かなり実務的で確かな一歩になります。

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