開発者ツール·6 分

Unix タイムスタンプを理解する:開発者ガイド

Unix タイムスタンプとは何か、その仕組み、閏秒、2038 年問題、実践的な換算。

なぜ開発者が Unix タイムスタンプを理解すべきか

Unix タイムスタンプは、1970 年 1 月 1 日 UTC 00:00:00(Unix エポック)から経過した秒数を表す整数です。コンピューティングにおける時間の共通語です:データベースはインデックス化し、API は返し、ファイルシステムは保存し、NTP や HTTP などのプロトコルは使用します。すべての開発者は最終的に Unix タイムスタンプと人間が読める日付の間で変換を行う必要があります —— また、異なるシステムが時間を異なる方法で表すときに発生する問題をデバッグする必要があります。

このガイドでは、Unix タイムスタンプとは何か、それらが異なる言語でどのように動作するか、タイムゾーンと閏秒の複雑さ、そして今後の 2038 年問題について説明します。

Unix タイムスタンプの仕組み

Unix タイムスタンプは単なる整数の秒数です。タイムゾーン、夏時間、カレンダー算術はありません。タイムスタンプに 86,400 を追加すると、UTC で常に正確に 1 日進みます。

``` 0 = 1970-01-01 00:00:00 UTC (エポック) 1,000 = 1970-01-01 00:16:40 UTC 1,000,000 = 1970-01-12 13:46:40 UTC 1,000,000,000 = 2001-09-09 01:46:40 UTC ("10 億秒") 1,500,000,000 = 2017-07-14 02:40:00 UTC 2,000,000,000 = 2033-05-18 03:33:20 UTC 2,147,483,647 = 2038-01-19 03:14:07 UTC (32 ビット符号付き最大) ```

表現可能な最大値は整数型に依存します:

  • 32 ビット符号付き:2,147,483,647 (2^31 − 1) → 2038-01-19 03:14:07 UTC
  • 32 ビット符号なし:4,294,967,295 (2^32 − 1) → 2106-02-07 06:28:15 UTC
  • 64 ビット符号付き:9,223,372,036,854,775,807 → 292,277,026,596 年

これが2038 年問題が存在する理由です:32 ビット符号付きタイムスタンプはすべて 2038-01-19 にオーバーフローします。64 ビットタイムスタンプは数千億年の間この問題を回避します。

Unix 時間と UTC:閏秒の問題

厳密に言えば、Unix 時間は閏秒を無視します。UTC に閏秒が追加された場合でも、毎日が正確に 86,400 秒であるかのように秒を数えます。27 個の閏秒が挿入された後(2024 年現在)、Unix 時間は「真の」UTC より約 27 秒進んでいます。

実際には、これはほとんど問題になりません。TAI(国際原子時)は完璧な秒を刻み、UTC は閏秒を追加することで UT1(地球の自転時間)の 0.9 秒以内に保たれます。Unix 時間は単に固定オフセットを引いた TAI の秒数を使用します。

厳密な原子時間が必要な場合は、TAI を直接使用してください。それ以外の場合、Unix 時間のわずかな drift は無関係です。

タイムゾーンとオフセット

Unix タイムスタンプは時間の中の単一の瞬間を指します。人間が読める形式で表示するには、タイムゾーンオフセットを適用します。一般的なオフセット:

| ゾーン | 略称 | UTC からのオフセット | |------|------|-----------------| | 協定世界時 | UTC | 0 | | 米国東部(冬) | EST | −5 | | 米国東部(夏) | EDT | −4 | | 米国太平洋(冬) | PST | −8 | | 米国太平洋(夏) | PDT | −7 | | 英国(冬) | GMT | 0 | | 英国(夏) | BST | +1 | | 中央ヨーロッパ(冬) | CET | +1 | | 中央ヨーロッパ(夏) | CEST | +2 | | 日本 | JST | +9 | | 中国 | CST | +8 | | インド | IST | +5:30 | | オーストラリア(シドニー、冬) | AEST | +10 | | オーストラリア(シドニー、夏) | AEDT | +11 |

インドと中国は年間を通じて単一のオフセットを使用します(夏時間なし)。米国、英国、およびほとんどのヨーロッパは夏時間を採用しており、すべてが複雑になります。

一般的なバグ:サーバーが Unix タイムスタンプ(常に UTC)を保存し、ユーザーのローカルオフセットを適用せずに表示すると、ユーザーは数時間ずれた時間を見ます。常に UTC で保存し、表示時にローカルに変換してください。

2038 年問題の詳細

2038 年問題(別名 Y2K38)は、2038-01-19 03:14:07 UTC に発生する 32 ビット符号付き整数のオーバーフローです。次の 1 秒で −2,147,483,648 にオーバーフローし、ほとんどのシステムで 1901-12-13 20:45:52 UTC として解釈されます。Unix タイムスタンプを 32 ビット符号付き整数として依存するものが壊れます:

  • mtime を 32 ビット time_t として保存するファイルシステム
  • 古いデータベース(古い MySQL、一部の SQLite ビルド)
  • 組み込みシステム(ルーター、IoT デバイス、車)
  • 32 ビット時間フィールドを使用するネットワークプロトコル(NTP、DNS、Kerberos、一部の TLS ハンドシェイク)

最新のシステム(64 ビット Linux、64 ビット macOS、最新の Windows、最新のデータベース)はすでに 64 ビットに移行しています。リスクは更新されていないレガシーおよび組み込みコードにあります。

修正:データ型を `time_t`(32 ビット)から `int64_t`(または同等のもの)に変更します。1 つの宣言変更と再コンパイル。課題は 32 ビット仮定が存在するすべての場所を見つけることです —— ファイル形式、ワイヤープロトコル、永続化されたデータ、サードパーティライブラリ。

コードでの Unix タイムスタンプの操作

Python

Python の `datetime` モジュールは正規ツールです。

```python import datetime from zoneinfo import ZoneInfo

# 現在の Unix タイムスタンプ(秒、浮動小数点) import time now = time.time() # 1700000000.123

# Unix タイムスタンプを datetime(UTC)に変換 ts = 1700000000 utc = datetime.datetime.fromtimestamp(ts, tz=datetime.timezone.utc) # 2023-11-14 22:13:20+00:00

# Unix タイムスタンプを datetime(特定のゾーン)に変換 ny = datetime.datetime.fromtimestamp(ts, tz=ZoneInfo("America/New_York")) # 2023-11-14 17:13:20-05:00

# datetime を Unix タイムスタンプに変換 d = datetime.datetime(2023, 11, 14, 22, 13, 20, tzinfo=datetime.timezone.utc) ts = int(d.timestamp()) # 1700000000

# ISO 8601 としてフォーマット print(utc.isoformat()) # 2023-11-14T22:13:20+00:00 ```

注意:`datetime.fromtimestamp(ts)`(`tz` なし)はローカルタイムゾーンを使用します。これはサーバーコンテキストではめったに望まれません。常に明示的なタイムゾーンを渡してください。

JavaScript

JavaScript は秒ではなくミリ秒を使用します。`Date.now()` はエポックからのミリ秒を返します。

```js // 現在の Unix タイムスタンプ(ミリ秒) const now = Date.now(); // 例:1700000000000

// 現在の Unix タイムスタンプ(秒) const nowSec = Math.floor(Date.now() / 1000);

// 秒をミリ秒に変換して Date を作成 const date = new Date(1700000000 * 1000); // 2023-11-14T22:13:20.000Z

// ミリ秒を秒に変換 const ts = Math.floor(date.getTime() / 1000);

// Intl を使用して特定のタイムゾーンで表示 console.log(date.toLocaleString("en-US", { timeZone: "Asia/Tokyo" })); // 11/15/2023, 7:13:20 AM

// ISO 8601 console.log(date.toISOString()); // 2023-11-14T22:13:20.000Z ```

JavaScript の最大の落とし穴:他のすべての言語は秒を使用します。JavaScript はミリ秒を使用します。1000 倍の乗算または除算を忘れることは、無数の「1000 倍ずれる」バグの原因です。

SQL

ほとんどのデータベースは Unix タイムスタンプをネイティブまたは関数経由でサポートします。

```sql -- MySQL SELECT UNIX_TIMESTAMP(NOW()); -- 現在のタイムスタンプ(秒) SELECT FROM_UNIXTIME(1700000000); -- 2023-11-14 22:13:20 SELECT FROM_UNIXTIME(1700000000, '%Y-%m-%d %H:%i:%s');

-- PostgreSQL SELECT EXTRACT(EPOCH FROM NOW()); -- 現在のタイムスタンプ(秒、浮動小数点) SELECT TO_TIMESTAMP(1700000000); -- 2023-11-14 22:13:20+00

-- SQLite SELECT strftime('%Y-%m-%dT%H:%M:%fZ', 'unixepoch', 1700000000); -- 2023-11-14T22:13:20.000Z ```

MySQL の `UNIX_TIMESTAMP()` は秒を返します。PostgreSQL の `EXTRACT(EPOCH FROM ...)` は小数精度付きの秒を返します。SQLite は手動の `strftime` レシピが必要です。

Bash

`date` コマンドはすべての Unix システムで Unix タイムスタンプを処理します。

```bash # 現在の Unix タイムスタンプ date +%s # 1700000000

# Unix タイムスタンプを日付文字列に変換 # Linux(GNU date) date -d @1700000000 # Tue Nov 14 22:13:20 PM UTC 2023

# macOS(BSD date) date -r 1700000000 # Tue Nov 14 22:13:20 UTC 2023

# 日付文字列を Unix タイムスタンプに変換 # Linux date -d "2023-11-14 22:13:20 UTC" +%s # macOS date -j -f "%Y-%m-%d %H:%M:%S" "2023-11-14 22:13:20" +%s

# 特定のタイムゾーンで表示 TZ="Asia/Tokyo" date -d @1700000000 # Wed Nov 15 07:13:20 JST 2023 ```

クイックリファレンス:`date +%s`(「現在を Unix 時刻で」)は、毎週使うワンライナーです。

データベースでのタイムスタンプの保存

データベース保存のベストプラクティス:

  1. 整数(BIGINT)として保存:ネイティブ Unix タイムスタンプは保存が安価で、インデックス化が容易で、比較が簡単です。
  2. 常に UTC を使用:表示時にのみローカルタイムゾーンに変換します。
  3. INT ではなく BIGINT を使用:32 ビット符号付き整数は 2038 年にオーバーフローします。常に 64 ビットを使用してください。
  4. ミリ秒精度を検討:高頻度イベントロギング(取引、ゲーム、テレメトリ)の場合、1 秒の解像度は粗すぎます。BIGINT ミリ秒として保存します。
  5. TIMESTAMP WITH TIME ZONE を使用:PostgreSQL の `timestamptz` が最も堅牢です。MySQL の `TIMESTAMP` には 2038 年問題があります。代わりに `DATETIME` を使用してください。
  6. タイムスタンプ列にインデックスを作成:`WHERE created_at > ?` のようなクエリは非常に一般的です。タイムスタンプ列の BTREE インデックスにより高速になります。

一般的なスキーマ:

```sql CREATE TABLE events ( id BIGSERIAL PRIMARY KEY, event_type TEXT NOT NULL, payload JSONB NOT NULL, created_at BIGINT NOT NULL, -- Unix ms INDEX idx_events_created (created_at) ); ```

よくある落とし穴

JavaScript ミリ秒バグ

JavaScript タイムスタンプはミリ秒単位です。他のすべての主要言語はを使用します。それらを混在させると、1000 倍ずれたタイムスタンプが生成されます。

```js // Python バックエンドから const python_ts = 1700000000; // 秒 const js_date = new Date(python_ts); // 1970-01-20T08:13:20.000Z ← 間違い!

// 正解:1000 を掛ける const js_date = new Date(python_ts * 1000); ```

タイムゾーン表示バグ

タイムスタンプは時間の中の瞬間を指し、日付ではありません。サーバーが「2023-11-14 22:13:20」を UTC なのかローカルなのかを示さずにログに記録すると、ユーザーはどちらかを知る方法がありません。常に:

  • Unix タイムスタンプとして保存(本質的に UTC)
  • 明示的なタイムゾーンでクライアントに送信
  • ユーザーの実際のタイムゾーンで表示時にローカルに変換

```js // サーバーが "2023-11-14T22:13:20Z"(Z = UTC)を送信 // クライアントがユーザーのローカルタイムゾーンでレンダリング const d = new Date("2023-11-14T22:13:20Z"); console.log(d.toLocaleString()); // "11/14/2023, 5:13:20 PM"(EST で) ```

夏時間のエッジケース

春の 24 時間は実際には 23 時間(夏時間が 1 時間をスキップする);秋の 24 時間は 25 時間です。「86,400 を足して明日を取得する」と仮定するコードは問題なく動作します。一方、「24 × 60 × 60 を足す」または「明日の午前 2:30」とするコードは、夏時間の遷移をまたぐと失敗する可能性があります。

```python # 夏時間タイムゾーンでは、これは 23 時間または 25 時間の日 d = datetime.datetime(2024, 3, 10, 1, 0, 0, tzinfo=ZoneInfo("America/New_York")) # 夏時間開始 print((d + datetime.timedelta(days=1)).isoformat()) # 2024-03-11T01:00:00-05:00 ← 正しい print((d + datetime.timedelta(hours=24)).isoformat()) # 2024-03-11T02:00:00-04:00 ← 夏時間がジャンプ、これは 25 時間後 ```

スケジューリングや時刻の計算には、これらのエッジケースを正しく処理するライブラリ(Luxon、Arrow、date-fns)を優先してください。

方法 1:UtilBoxx タイムスタンプ換算ツール(推奨)

開発中の迅速な換算には、UtilBoxx タイムスタンプ換算ツール は、Unix タイムスタンプと日付を任意のタイムゾーンで相互に換算し、秒とミリ秒の両方をサポートし、完全にクライアントサイドで実行されるプライベートなブラウザ内ツールです。アップロードなし、登録なし、ログなし。API レスポンスの未知のタイムスタンプを次にデコードする必要があるときに、ブックマークしてください。

まとめ

Unix タイムスタンプはシンプルで、高速で、普遍的ですが —— 周囲のエコシステム(タイムゾーン、閏秒、2038 年問題、ミリ秒 vs 秒の混乱)は落とし穴だらけです。すべての開発者は最終的にこれらすべてに遭遇します。唯一の防御は、それらが存在することを知ることです。

ベストプラクティス:

  • UTC で 64 ビット整数として保存
  • 表示時にユーザーの実際のタイムゾーンでローカルに変換
  • 非自明な時間計算には本格的なタイムゾーンライブラリ(Luxon、date-fns-tz、zoneinfo)を使用
  • レガシーコードを保守している場合は今すぐ 32 ビットタイムスタンプを監査

Unix タイムスタンプは現在も使用されている最も古いデータ形式の一つであり、私たち全員より長生きするでしょう —— しかし、正しく使用する場合に限ります。