UNIXタイムスタンプとは?秒とミリ秒の見分け方と変換方法を解説
公開日:
ログやAPIのレスポンス、データベースの中で「1704067200」のような数字の日時を見かけることがあります。これがUNIXタイムスタンプです。タイムゾーンに左右されない便利な表現ですが、秒とミリ秒の取り違えや、表示時のタイムゾーンのずれ、2038年問題など、知っておかないとハマるポイントもあります。この記事では仕組みから各言語での変換方法までをまとめます。
UNIXタイムスタンプとは
UNIXタイムスタンプ(UNIX時間、エポック秒とも呼ばれます)は、協定世界時(UTC)の1970年1月1日0時0分0秒からの経過秒数で日時を表す方法です。この基準の時刻を「UNIXエポック」と呼びます。
- 0 → 1970-01-01 00:00:00 UTC(日本時間では同日の9:00:00)
- 1000000000 → 2001-09-09 01:46:40 UTC
- 1704067200 → 2024-01-01 00:00:00 UTC(日本時間では2024-01-01 09:00:00)
UNIXタイムスタンプには「タイムゾーン」という概念がありません。東京でもニューヨークでも、同じ瞬間なら同じ値になります。そのため、システム間で時刻をやり取りしたり、2つの時刻の差を計算したりするのに向いています。1日は常に86400秒として数えられ、うるう秒は数に含めません。うるう秒が挿入された瞬間の扱いはOSや実装によって異なりますが、日常的なアプリケーションで意識する場面はほとんどありません。
1970年より前の日時は負の値で表します。例えば -86400 は1969年12月31日0時0分0秒(UTC)です。ただし、負の値を正しく扱えないシステムもあるため、古い日付を扱うときは注意が必要です。
秒とミリ秒の見分け方
UNIXタイムスタンプには、秒単位のものとミリ秒単位のものがあり、言語やシステムによってどちらを使うかが異なります。現在の日時であれば、桁数で簡単に見分けられます。
- 10桁(例: 1704067200)→ 秒。シェルの date +%s、PHPの time()、多くのAPIなど
- 13桁(例: 1704067200000)→ ミリ秒。JavaScriptの Date.now()、Javaの System.currentTimeMillis() など
- 16桁 → マイクロ秒、19桁 → ナノ秒。Goの UnixMicro()・UnixNano() や、一部のログ・データベースなど
秒の値が10桁なのは2001年9月9日から2286年11月20日までの期間です。この範囲の日時を扱う限り、「10桁なら秒、13桁ならミリ秒」と判断して問題ありません。Pythonの time.time() のように、秒単位でも小数点以下を含む値を返すものもあります。
取り違えたときの症状も覚えておくと便利です。秒の値をミリ秒として解釈すると1970年1月の日付(1704067200ミリ秒は1970年1月20日ごろ)になり、ミリ秒の値を秒として解釈すると西暦5万年台のような遠い未来の日付になります。変換結果が明らかにおかしいときは、まず単位を疑いましょう。
このサイトのUNIXタイムスタンプ変換ツールでは、入力値が1000億(10^11)より大きければミリ秒、それ以下なら秒とみなして自動で変換し、ISO 8601形式・ブラウザのタイムゾーンでの日時・UTCの3通りで表示します。この判定のため、1973年3月3日より前の時刻をミリ秒で入力すると、秒として解釈される点に注意してください。日時からタイムスタンプへの逆変換や、現在時刻の秒・ミリ秒の表示もできます。
タイムゾーンとの関係と日時文字列の落とし穴
タイムスタンプ自体はタイムゾーンを持ちませんが、人が読める日時に変換するときには必ずタイムゾーンが必要になります。同じ1704067200でも、UTCなら1月1日0時、日本標準時(JST、UTC+9)なら1月1日9時、ニューヨーク(冬はUTC-5)なら12月31日19時です。「サーバーで変換した日時が9時間ずれる」という問題の多くは、サーバーのタイムゾーンがUTCに設定されていることが原因です。
JavaScriptでは、日時の文字列からDateを作るときの解釈にも注意が必要です。
new Date("2024-01-01"); // 日付のみ → UTC の 0 時として解釈される
new Date("2024-01-01T00:00"); // 時刻あり・オフセットなし → 実行環境のローカル時刻
new Date("2024-01-01T00:00:00+09:00"); // オフセット付き → 曖昧さがない日付だけの文字列と時刻を含む文字列で解釈が変わるため、同じ「1月1日」でも環境によって9時間ずれた値になることがあります。システム間で日時を文字列でやり取りする場合は、「+09:00」や「Z」のようにオフセットを明示したISO 8601(RFC 3339)形式を使うのが安全です。
データベースでは、内部的にはUTCまたはタイムスタンプで保存し、画面に表示するときに利用者のタイムゾーンに変換する、という設計が基本です。夏時間(DST)のある地域では、同じ現地時刻が1年に2回現れたり、存在しない時刻があったりするため、現地時刻のまま保存すると計算を誤る原因になります。複数の地域の時刻を見比べたいときは、タイムゾーン変換ツールで主要都市の時刻とUTCからのオフセットを一覧できます。
各言語での取得・変換方法
よく使う言語・環境で、現在のタイムスタンプを取得する方法と、タイムスタンプを日時に変換する方法をまとめます。
// JavaScript(Date はミリ秒で扱う)
const sec = Math.floor(Date.now() / 1000);
new Date(1704067200 * 1000).toISOString(); // "2024-01-01T00:00:00.000Z"
new Date(1704067200 * 1000).toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" });
// "2024/1/1 9:00:00"import time
from datetime import datetime, timezone
int(time.time()) # 現在の秒
datetime.fromtimestamp(1704067200, tz=timezone.utc)
# datetime.datetime(2024, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
datetime(2024, 1, 1, tzinfo=timezone.utc).timestamp() # 1704067200.0Pythonの datetime.fromtimestamp() は、tz を省略すると実行環境のローカル時刻で、タイムゾーン情報を持たない値を返します。Python 3.12で非推奨になった utcfromtimestamp() の代わりに、tz=timezone.utc を明示する書き方を使いましょう。
date +%s # 現在の秒
date -u -d @1704067200 # GNU date(Linux)
date -u -r 1704067200 # macOS・BSD の date
TZ=Asia/Tokyo date -d @1704067200 # 日本時間で表示(GNU)-- MySQL(結果はセッションのタイムゾーンで解釈される)
SELECT UNIX_TIMESTAMP(), FROM_UNIXTIME(1704067200);
-- PostgreSQL
SELECT EXTRACT(EPOCH FROM now()), to_timestamp(1704067200);このほか、PHPでは time() と date()、Goでは time.Now().Unix() と time.Unix(sec, 0) を使います。どの言語でも「秒かミリ秒か」と「どのタイムゾーンで表示するか」の2点を意識すれば、変換で迷うことはほとんどなくなります。
2038年問題とは
UNIXタイムスタンプを符号付き32ビット整数で保存しているシステムでは、表現できる最大値が2147483647になります。これは2038年1月19日3時14分7秒(UTC)、日本時間では同日の12時14分7秒です。その1秒後には値があふれて-2147483648になり、1901年12月13日20時45分52秒(UTC)として扱われてしまいます。これが「2038年問題」です。
- 32ビット版のOSや古い組み込み機器で、time_t が32ビットのままのもの
- データベースのカラムやファイル形式で、タイムスタンプを32ビット整数として保存しているもの
- MySQLのTIMESTAMP型(扱える上限が2038年1月19日)
- プログラム中で32ビット整数に変換している箇所
現在の64ビット版のLinuxやmacOSでは time_t が64ビットになっており、OS自体が影響を受けることは基本的にありません。JavaScriptのDateも、内部ではミリ秒を倍精度浮動小数点数で持ち、およそ西暦27万5千年まで表せるため問題ありません。ただし、アプリケーションのコードに思わぬ落とし穴が残っていることがあります。
// ビット演算は 32 ビット整数に変換されるため、2038年以降は値が壊れる
const sec = (Date.now() / 1000) | 0; // NG
const sec2 = Math.floor(Date.now() / 1000); // OK有効期限が数十年先になるデータ(長期の契約、証明書、ローンの返済日など)を扱うシステムでは、2038年を過ぎる日付がすでに登場しています。データベースのカラムは64ビット整数(BIGINT)や日時型を選び、30年先の日付でテストしておくと安心です。