Unixタイムスタンプとは何か
Unixタイムスタンプ(Unixエポック時間、POSIX時間とも呼ばれる)は、1970年1月1日00:00:00 UTC(Unixエポック)から経過した秒数を表す整数です。2026年6月1日12:00:00 UTCは1780315200です。この単純な表現は人間には不親切ですが、コンピュータにとっては理想的です。タイムゾーン情報を含まず、単一の整数で時刻を一意に表現でき、異なるシステム間で曖昧さなく交換できます。
なぜ1970年が起点なのか:Unixオペレーティングシステムが1960年代後半に開発され、1970年1月1日が切りの良い近い過去として選ばれました。当時は32ビット符号付き整数(最大約21億)で表現されており、2038年1月19日まで持つと計算されました。半世紀以上先のことなど気にする必要がない、と思われた時代です。
現代のシステムではUnixタイムスタンプの粒度にバリエーションがあります。秒単位(10桁:1780315200)、ミリ秒単位(13桁:1780315200000)、マイクロ秒単位(16桁:1780315200000000)、ナノ秒単位(19桁:1780315200000000000)。JavaScriptのDate.now()はミリ秒、Pythonのtime.time()は浮動小数点の秒、Goのtime.UnixNano()はナノ秒を返します。どの精度かを常に確認してください。
タイムスタンプの変換と操作
基本的な変換は単純です。日時からタイムスタンプへ:各構成要素(年月日時分秒)からエポックからの秒数を計算します。タイムスタンプから日時へ:秒数から年月日時分秒を逆算します。うるう年、月の日数の違い、うるう秒(後述)を正確に処理する必要があります。ほとんどの場合、言語標準ライブラリに任せるべきです。
よくある操作:2つのタイムスタンプの差を取れば経過秒数が分かります。タイムスタンプに86400を足せば1日後になります(うるう秒を除く)。ただし「1ヶ月後」の計算は単純な加算ではできません。月の長さが異なるためです。1月31日の1ヶ月後は2月28日か?3月3日か?ビジネスロジックによって答えが変わるため、言語のDate/Time APIを使い、ルールを明示してください。
ミリ秒と秒の混同はバグの温床です。APIが秒単位のタイムスタンプを期待しているのに13桁のミリ秒を渡すと、紀元58,000年ごろの日時として解釈されます。逆にミリ秒を期待するフィールドに10桁を渡すと、1970年1月21日になります。桁数で判断できます:10桁は秒(2001年〜2286年の範囲)、13桁はミリ秒です。当サイトのtimestamp-converterツールで両方の形式を相互変換できます。
// JavaScriptでのタイムスタンプ操作
// 現在のタイムスタンプ
const nowMs = Date.now(); // ミリ秒: 1780315200000
const nowSec = Math.floor(nowMs / 1000); // 秒: 1780315200
// 日時からタイムスタンプへ
const date = new Date('2026-06-01T12:00:00Z');
const timestamp = date.getTime(); // ミリ秒
const timestampSec = Math.floor(date.getTime() / 1000); // 秒
// タイムスタンプから日時へ
new Date(1780315200 * 1000).toISOString();
// "2026-06-01T12:00:00.000Z"
// 秒 vs ミリ秒の判別
function normalizeTimestamp(ts) {
if (ts.toString().length <= 10) {
return ts * 1000; // 秒→ミリ秒に変換
}
return ts; // 既にミリ秒
}
// 2つのタイムスタンプの差(時間単位)
function hoursBetween(ts1, ts2) {
return Math.abs(ts2 - ts1) / 3600;
}タイムゾーン処理のベストプラクティス
Unixタイムスタンプ自体にはタイムゾーンの概念がありません。常にUTCです。しかし人間に表示する際にはローカルタイムゾーンへの変換が必要になり、ここが複雑さの始まりです。日本(UTC+9)では1780315200は2026年6月1日21:00:00 JSTです。UTCで12:00のイベントが日本では21:00に見えるのは正しいですが、「6月1日の予定」と言った時に日付がずれるケースに注意が必要です。
保存はUTC、表示時に変換。これがゴールデンルールです。データベースにはUTCのタイムスタンプを保存し、ユーザーに表示する瞬間にローカルタイムゾーンに変換します。ユーザーのタイムゾーン情報はプロフィール設定またはブラウザのIntl.DateTimeFormat().resolvedOptions().timeZoneから取得できます。
夏時間(DST)は最も厄介な問題です。日本は夏時間を採用していませんが、米国やヨーロッパのユーザーを持つシステムでは、年に2回のDST切り替え時に1時間の「消失」や「重複」が発生します。例えば米国東部時間で2026年3月8日02:00は存在しません(01:59から直接03:00に飛ぶ)。このときにスケジュールされたcronジョブはスキップされるか2回実行されるか、実装次第です。
タイムゾーンオフセット(+09:00)ではなくタイムゾーン名(Asia/Tokyo)を保存してください。オフセットは固定ですが、タイムゾーンのルールは政府によって変更されることがあります。IANAタイムゾーンデータベース(tzdata)は年に数回更新されます。最近の例ではロシアの複数のタイムゾーン変更、サモアの日付変更線の移動などがあります。
// タイムゾーン処理の実践例
// UTCで保存
const eventTimeUTC = new Date('2026-06-01T12:00:00Z');
// 日本時間で表示
eventTimeUTC.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
// "2026/6/1 21:00:00"
// ニューヨーク時間で表示
eventTimeUTC.toLocaleString('en-US', { timeZone: 'America/New_York' });
// "6/1/2026, 8:00:00 AM"
// ユーザーのタイムゾーンを検出
const userTZ = Intl.DateTimeFormat().resolvedOptions().timeZone;
// "Asia/Tokyo"(日本のユーザーの場合)
// ISO 8601形式での表現(タイムゾーン情報付き)
const isoString = eventTimeUTC.toISOString(); // "2026-06-01T12:00:00.000Z"
// Temporal API(Stage 3、より正確なTZ処理)
// const zdt = Temporal.ZonedDateTime.from({
// timeZone: 'Asia/Tokyo',
// year: 2026, month: 6, day: 1, hour: 21
// });
// zdt.toInstant().epochSeconds; // UTCタイムスタンプ2038年問題の実態
2038年問題(Y2K38、Epochalypse)は、32ビット符号付き整数でUnixタイムスタンプを格納しているシステムが2038年1月19日03:14:07 UTCにオーバーフローする問題です。この瞬間に32ビットの最大正値(2,147,483,647)を超え、符号ビットが反転して1901年12月13日を指すようになります。
影響範囲:2026年現在、多くのシステムは既に64ビットタイムスタンプに移行済みです。64ビットの場合、約2920億年後までオーバーフローしません。Linuxカーネルは5.6(2020年)以降で32ビットシステム上のtime_t問題に対処。macOSやWindowsの現行バージョンも64ビットです。しかし問題は組み込みシステム、IoTデバイス、古いデータベーススキーマ、レガシーCコードに残っています。
実際に影響を受ける例:MySQLのTIMESTAMP型は内部的に32ビット整数で、2038年1月19日が上限です。2038年以降の日付を扱う必要がある場合はDATETIME型を使ってください。ファイルシステム(FAT32のタイムスタンプは2107年まで、ext4は2446年まで)、POSIX準拠の組み込みOSなども影響を受ける可能性があります。
開発者として今すべきこと:新規コードではすべてのタイムスタンプに64ビット整数を使う。データベースのスキーマでBIGINTまたはDATETIME型を使う。外部APIの仕様で秒のタイムスタンプを32ビットに制限していないか確認する。2038年まであと12年ですが、35年ローンの計算や保険の満期日計算など、今日の時点で2038年以降の日付を扱うシステムは既に存在します。
うるう秒とその扱い
うるう秒はUTC時計を地球の自転に合わせるため、不定期に追加(まれに削除)される1秒です。1972年以来27回追加されています。うるう秒が挿入される瞬間、時計は23:59:59の次に23:59:60を刻み、その後00:00:00になります。Unixタイムスタンプはうるう秒を無視する仕様で、1日は常に86,400秒として扱われます。
うるう秒がシステムに問題を起こした事例は数多くあります。2012年6月30日のうるう秒ではLinuxカーネルのバグによりReddit、Mozilla、FourSquareなどがダウンしました。2017年1月1日のうるう秒ではCloudflareのDNSに問題が発生しました。GoogleやAWSは「うるう秒スミア」(数時間かけてうるう秒を分散吸収)で対処しています。
2022年11月のITU会議で、2035年までにうるう秒を廃止する決定が行われました。2035年以降はUTCとUT1(天文時)のずれが蓄積されますが、技術システムの安定性が優先されました。したがって、新しいシステムではうるう秒をそれほど心配する必要はなくなりつつあります。
実装上のアドバイス:NTPクライアントがうるう秒スミアを適用するか、ステップ補正するかはシステム設定次第です。分散システムで因果関係の順序が重要な場合は、Googleの論文「Spanner: TrueTime」のアプローチ(時間の不確実性区間を扱う)が参考になります。通常のWebアプリケーションでは、NTPに任せてうるう秒を無視するのが現実的です。
各言語でのタイムスタンプ操作
JavaScript:Date.now()はミリ秒のタイムスタンプを返します。new Date(timestamp)はミリ秒を期待します。performance.now()は高精度(マイクロ秒レベル)ですがエポックからの時間ではなくページロードからの経過です。Temporal API(Stage 3提案、2026年時点で一部ブラウザに実装中)はタイムゾーンとカレンダーを正確に扱える新しいAPIです。
Python:time.time()は浮動小数点の秒数。datetime.datetime.now(tz=datetime.timezone.utc).timestamp()でUTCタイムスタンプを取得。datetimeモジュールのnaive datetime(タイムゾーン情報なし)は避け、常にaware datetime(タイムゾーン情報付き)を使ってください。pytzまたはPython 3.9以降のzoneinfo.ZoneInfoモジュールでタイムゾーンを扱います。
Go:time.Now().Unix()は秒、time.Now().UnixMilli()はミリ秒、time.Now().UnixNano()はナノ秒。Goのtime.Timeは内部的にナノ秒精度を持ち、monotonic clockの読み取りも含みます。タイムゾーン変換はtime.LoadLocation("Asia/Tokyo")とtime.In()で行います。
Java:System.currentTimeMillis()はミリ秒。java.time.Instant.now().getEpochSecond()は秒。java.timeパッケージ(Java 8以降)は不変でスレッドセーフな日時APIを提供します。古いjava.util.DateやCalendarは使わないでください。ZonedDateTimeでタイムゾーン付き日時を扱い、Instantでエポックベースのタイムスタンプを扱います。
// 各言語でのタイムスタンプ取得(JavaScript例)
// 現在のタイムスタンプ
Date.now(); // ミリ秒: 1780315200000
Math.floor(Date.now() / 1000); // 秒: 1780315200
// ISO 8601文字列への変換
new Date(1780315200000).toISOString();
// "2026-06-01T12:00:00.000Z"
// 特定のタイムゾーンでフォーマット
new Intl.DateTimeFormat('ja-JP', {
timeZone: 'Asia/Tokyo',
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
}).format(new Date(1780315200000));
// "2026/06/01 21:00:00"
// 相対時間表示(○分前、○時間前)
function relativeTime(timestamp) {
const diff = Date.now() - timestamp;
const minutes = Math.floor(diff / 60000);
if (minutes < 60) return `${minutes}分前`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}時間前`;
const days = Math.floor(hours / 24);
return `${days}日前`;
}ISO 8601とRFC 3339:フォーマットの標準
Unixタイムスタンプは機械には最適ですが、人間が読む場面やAPIのレスポンスでは文字列表現が必要です。ISO 8601は日時のフォーマット標準で、「2026-06-01T12:00:00Z」のような形式です。RFC 3339はISO 8601のプロファイル(サブセット)で、インターネットプロトコルでの使用に特化しています。実務上、両者はほぼ同じ形式を指します。
重要な構成要素:日付部分(2026-06-01)はYYYY-MM-DD、時刻部分(12:00:00)はHH:MM:SS、Tは日付と時刻の区切り、Zは「Zulu time」でUTCを意味します。タイムゾーンオフセットは+09:00(日本時間)のように表記します。秒の小数部(.000)はオプションです。ミリ秒精度なら.000、マイクロ秒なら.000000を付けます。
APIを設計する際の推奨:レスポンスではISO 8601形式(UTC)を返してください。「2026-06-01T12:00:00Z」は世界中のどのクライアントでも正確に解釈できます。ローカル時間(「2026-06-01T21:00:00+09:00」)を返すとクライアント側での再変換が複雑になります。Unixタイムスタンプ(整数)を返すAPIもありますが、ISO 8601の方がデバッグしやすいです。
パースの注意点:JavaScriptのDate.parse()はブラウザ間で挙動が異なる歴史があります。「2026-06-01」(時刻なし)がUTCとして解釈されるかローカル時間として解釈されるかは実装依存でした(ES2015以降はUTCに統一)。安全のため、常にタイムゾーン情報を含む完全な形式(末尾にZまたはオフセット)を使用してください。
実践的なデバッグとトラブルシューティング
タイムスタンプ関連のバグは再現が難しいことで悪名高いです。特定のタイムゾーンのユーザーだけに発生する、DSTの切り替え時だけに発生する、日付境界をまたぐ場合だけに発生する、といったパターンがあります。テストでは異なるタイムゾーンと日付境界条件を意識的にカバーしてください。
「1日ずれ」バグの一般的な原因:タイムゾーン変換の方向が逆(UTCの午後をJSTに変換すると翌日になる)、Date生成時のオフセット符号ミス、「1月1日」からの日数計算で0始まりと1始まりの混同、月末日(31日、30日、28日/29日)の処理漏れ。
デバッグの手順:1)問題のタイムスタンプ値を確認する。2)そのタイムスタンプをUTCとローカル時間の両方で表示する。3)タイムスタンプが秒かミリ秒かを桁数で確認する。4)タイムゾーン変換が正しい方向か確認する。5)DSTの影響を確認する。当サイトのtimestamp-converterツールで任意のタイムスタンプをすぐにデコードして確認できます。
分散システムでの時刻ずれ:複数のサーバーの時計が同期していない場合、タイムスタンプの順序が因果関係と一致しないことがあります。NTPで通常数ミリ秒以内に同期されますが、VMの一時停止やネットワーク分断後に大きなずれが生じる可能性があります。厳密な順序が必要なら、Lamport時計やベクタークロックのような論理時計の併用を検討してください。