できない.dev

現在時刻を取得するには

各言語で現在時刻を取得する基本形を示す。
ローカル時刻と UTC のどちらが返るか、タイムゾーン情報を持つ値と持たない値の違いまでを扱う。

公開:

各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。

Python 実行確認済み

from datetime import datetime, timezone
 
now = datetime.now()                  # ローカル時刻(タイムゾーン情報なし)
utc = datetime.now(timezone.utc)      # UTC(タイムゾーン情報あり)
 
print(now.isoformat())
print(utc.isoformat())

datetime.now() を引数なしで呼ぶと、タイムゾーン情報を持たない naive な値が返る。
UTC 基準で扱うなら datetime.now(timezone.utc) のように tz を渡す。
naive と aware を混ぜて比較すると TypeError になるため、どちらで統一するかを先に決めておく。

うまくいかない時: Python で datetime の naive と aware を比較できず TypeError になる

JavaScript 実行確認済み

const now = new Date();
 
console.log(now.toISOString()); // 常に UTC の ISO 8601 文字列
console.log(Date.now());        // エポックからのミリ秒

new Date() は実行環境のタイムゾーンに紐づく「その瞬間」を表す。
toISOString() は必ず UTC へ変換した文字列を返すので、画面に出す地域時刻が欲しいときは toLocaleString を使い分ける。

TypeScript 静的確認

const now: Date = new Date();
const epochMs: number = Date.now();
 
console.log(now.toISOString(), epochMs);

new Date() は Date 型、Date.now() は number 型を返すため、型の上では取り違えを防げる。
ただし number がミリ秒なのか秒なのかは型で表現できず、エポック秒をそのまま new Date() に渡すと 1970 年付近の日時になる。

Go 静的確認

package main
 
import (
	"fmt"
	"time"
)
 
func main() {
	now := time.Now() // ローカルタイムゾーン付き
	fmt.Println(now.Format(time.RFC3339))
	fmt.Println(now.UTC().Format(time.RFC3339))
	fmt.Println(now.Unix()) // エポック秒
}

time.Now() はローカルタイムゾーンを保持した time.Time を返す。
Go の書式指定は %Y ではなく「2006-01-02 15:04:05」という参照時刻を並べる独自方式なので、自前で組み立てるより time.RFC3339 などの定数を使うほうが間違えにくい。

Rust 静的確認

use std::time::{SystemTime, UNIX_EPOCH};
 
fn main() {
    let secs = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("システム時刻がエポック以前")
        .as_secs();
    println!("{secs}");
}

標準ライブラリの SystemTime はエポックからの経過時間しか扱えず、年月日への変換や書式化の機能を持たない。
カレンダー上の日時が必要なら chrono や time クレートを依存に追加する。

Java 実行確認済み

import java.time.Instant;
import java.time.ZonedDateTime;
 
public class Main {
    public static void main(String[] args) {
        Instant now = Instant.now();               // UTC の瞬間
        ZonedDateTime local = ZonedDateTime.now(); // 既定タイムゾーン
        System.out.println(now);
        System.out.println(local);
    }
}

Instant は常に UTC 上の瞬間を表し、タイムゾーンの概念を持たない。
地域時刻として表示したいなら ZonedDateTime を使う。
可変で扱いにくい java.util.Date ではなく、Java 8 以降は java.time パッケージを使うのが標準である。

C# 静的確認

using System;
 
class Program {
    static void Main() {
        DateTime local = DateTime.Now;              // ローカル時刻
        DateTimeOffset utc = DateTimeOffset.UtcNow; // オフセット付きの UTC
        Console.WriteLine(local.ToString("o"));
        Console.WriteLine(utc.ToString("o"));
    }
}

DateTime は Kind プロパティでローカルか UTC かを区別するだけで、UTC からのオフセットそのものは保持しない。
時差を含めて正確に記録したいなら DateTimeOffset を使うほうが安全である。

つまずき

現在時刻まわりの不具合は、値そのものではなくタイムゾーン情報の有無から起きることが多い。
Python の datetime.now() や C# の DateTime.Now のように、既定ではオフセットを持たない値を返す API が少なくない。
こうした値を UTC の値と比較したり DB へ保存したりすると、9 時間ずれる、比較で例外になる、といった形で後から表面化する。
サーバ側では UTC で統一して保持し、表示の直前にだけ地域時刻へ変換する方針にしておくと事故が減る。

非推奨になった API に注意する

UTC を取る手段は言語ごとに更新されている。
Python では datetime.utcnow() が 3.12 で非推奨となり、タイムゾーン情報を持つ datetime.now(timezone.utc) が推奨されている。
Java でも java.util.Date や Calendar ではなく java.time が標準である。
古い記事のコードをそのまま貼る前に、その API が現行版でも推奨されているかを確認したい。