「時差で1時間ズレてログが読めない」「サーバーとフロントの時間が合わない」など、時間まわりのトラブルはプログラミングの定番です。
その裏には、UNIX時間やタイムゾーン、UTCやサマータイムといった少しややこしい概念が関わっています。
本記事では、初心者でも押さえておきたいUNIX時間とタイムゾーンの基本を、実務での使い方とあわせて整理していきます。
UNIX時間とは何かを理解する
時間はなぜ「UNIX時間」で表現されるのか
プログラミングの世界では、日時を「2025-11-25 10:00」のような文字列ではなく、UNIX時間(Unix Timestamp)という1つの整数値で扱う場面が多くあります。
これは、時間を数値として扱うことで、比較や加減算、保存をシンプルにするためです。
文字列表現のままでは、並び替えや差分計算のたびにパースやタイムゾーン補正が必要になります。
一方で、UNIX時間は「基準時刻から何秒経過したか」だけに集中しているため、計算処理を効率的かつミスなく行いやすいのが大きな利点です。

人間にとっては「日付+時刻」の形が読みやすいですが、プログラムから見ると「秒数のほうがシンプルで扱いやすい」という事情があり、UNIX時間が広く利用されています。
UNIX時間(Unix Timestamp)の仕組みと秒数カウント
UNIX時間は「ある基準時刻からの経過秒数を整数で表したもの」です。
この「ある基準時刻」が、次で説明するUNIX epochです。
ここで重要なのは、UNIX時間は通常UTC(協定世界時)を基準にカウントしている、という点です。
つまり、タイムゾーンがJSTなのか、アメリカなのかに関係なく、世界中で同じ「秒数」が共有されています。

UNIX時間は一般にint64などの整数型で扱われ、ミリ秒単位などの「拡張UNIX時間」もよく使われます。
その場合、msやmillisなどの名称が付いているかを必ず確認することが大切です。
1970年1月1日(UNIX epoch)からの経過秒とは
UNIX epochとは、「UNIX時間が0になる瞬間」として決められた基準日時です。
これは1970年1月1日 00時00分00秒 (UTC)です。
例えば、次のように解釈できます。
- UNIX時間
0… 1970-01-01 00:00:00 UTC - UNIX時間
1… 1970-01-01 00:00:01 UTC - UNIX時間
86400… 1970-01-02 00:00:00 UTC (1日後)
日本時間(JST, UTC+9)で見ると、UNIX時間0は1970-01-01 09:00:00 JSTになります。
ここで、「UNIX時間そのものにはタイムゾーンがくっついていない」ことに注意が必要です。
あくまでUTCのカウンタであり、表示するときにどのタイムゾーンとして解釈するかで、見た目の時刻が変わります。
32ビット問題と2038年問題の概要
古いシステムでは、UNIX時間を32ビット符号付き整数で保存していることがあります。
この場合、表現できる範囲はおよそ-2,147,483,648 〜 2,147,483,647です。
UNIX時間で見ると、上限の2,147,483,647は2038年1月19日 03:14:07 UTCに相当し、その次の秒2,147,483,648に進もうとすると、整数がオーバーフローしてマイナス側に飛んでしまいます。
これが有名な2038年問題です。

現代の多くの環境では64ビット整数(int64)でUNIX時間を扱っており、現実的な範囲では上限を気にする必要はほぼありません。
しかし、レガシーなC言語系APIや古いOSでは今も影響が残っているため、システム連携時には型の確認が重要です。
タイムゾーンとUTCの基本
タイムゾーン(Time Zone)とは何か
タイムゾーン(Time Zone)とは、地球上の位置に応じて決められた「ローカル時間のルール」です。
単に「UTC+9」のような固定オフセットだけでなく、歴史的な変更やサマータイム(DST)なども含んだ複雑な定義を持っています。
実務では、IANAタイムゾーンデータベースの形式であるAsia/Tokyo、America/New_YorkのようなIDがよく使われます。
これらは単純なオフセット表現UTC+9よりも、歴史的な変更やサマータイムの有無を正確に表現できる利点があります。
UTCとローカル時間の違いを整理
UTC(協定世界時)は、世界共通の基準となる時間です。
一方、ローカル時間は「その地域のタイムゾーンルールを適用した時刻」です。

例えば、同じ瞬間を以下のように表現できます。
- UTC: 2025-05-01 00:00:00 (UTC)
- 日本時間(JST): 2025-05-01 09:00:00 (UTC+9)
- ニューヨーク時間(サマータイム中): 2025-04-30 20:00:00 (UTC-4)
「時間がズレている」のではなく、「同じ瞬間を違う時計で見ている」と考えるとイメージしやすくなります。
日本時間(JST)とUTCの関係(UTC+9)を理解する
日本標準時(JST)はUTC+9であり、サマータイムを採用していません。
つまり、1年中ずっと「UTCより9時間進んでいる」だけのシンプルなタイムゾーンです。
例えば、次のように変換できます。
- UTC → JST: UTC時刻に
+9時間 - JST → UTC: JST時刻から
-9時間
ただし、これは日本だけを対象にしたアプリであれば通用する簡略化です。
グローバル展開するサービスでは、サマータイムを含む他地域のタイムゾーンが登場し、単純な「+n時間」だけでは済まなくなっていきます。
サマータイム(DST)が時間をややこしくする理由
サマータイム(Daylight Saving Time, DST)は、夏季の一時期だけ時計を1時間進める制度です。
これが時間処理を極端にややこしくする主な原因です。
問題点として、次のようなものがあります。
- 存在しない時刻がある
- 例: ある日、02:00〜02:59がスキップされる
- 同じ時刻が2回現れる
- 例: 冬時間に戻るタイミングで、1:30が2回存在する
- 開始・終了の日付やルールが国や年によって異なる

このような複雑さがあるため、サマータイムを自前で計算しようとするのは非常に危険です。
後述するように、必ずタイムゾーン対応ライブラリに任せるのが鉄則です。
プログラミングでの時間の扱い方
「UNIX時間」と「タイムゾーン付き時間」の使い分け
実務では、次の2種類の時間を意識して使い分けることが重要です。
- UNIX時間(UTCベースの秒数)
- タイムゾーン付き時間(Zoned DateTimeなど)
UNIX時間は「瞬間」を一意に表すのに向いています。
データベース保存、ログの記録、システム間の通信など、「世界共通の基準で時間を表したい」場面に適しています。
一方、タイムゾーン付き時間は「人が見るカレンダー上の日時」を扱うのに使います。
ユーザーのローカルタイムで日付をまたぐイベントや、「毎日9時に通知する」といった仕様では、タイムゾーンをきちんと考慮する必要があります。

「DBにはUNIX時間で保存し、UIに出すときだけユーザーのタイムゾーンで解釈する」というパターンを基本形として覚えておくとよいでしょう。
日付と時刻のフォーマット
日時を文字列にする際には、ISO 8601形式をベースにするのが一般的です。
例えば次のような形式です。
2025-11-25T10:00:00Z… UTCの時刻(ZはUTCを意味)2025-11-25T19:00:00+09:00… JST (UTC+9) の時刻2025-11-25… 日付のみ
自前でフォーマットを独自定義しないことが重要です。
後から別のシステムや言語で扱おうとしたときに、解釈ミスやライブラリ非対応の原因になりやすいためです。
また、秒未満を扱う場合は.123や.123456のように小数点以下で表記します。
用途に応じて精度(ミリ秒、マイクロ秒など)を統一しておくと、比較やログ解析がしやすくなります。
ローカル時間からUTCへ、UTCからローカル時間への変換
時間変換では、まず「何を基準にしているか」をはっきりさせることが重要です。
典型的な流れは以下の通りです。
- ユーザー入力(ローカル時間+タイムゾーン) → UTC(UNIX時間)
- DBから取得したUTC(UNIX時間) → ユーザーのタイムゾーンでローカル時間に変換 → 画面表示
疑似コードで表すと、次のようなイメージです。
// ユーザーが「2025-11-25 10:00」を Asia/Tokyo で入力した場合
local = parseLocalDateTime("2025-11-25 10:00")
zoned = attachTimeZone(local, "Asia/Tokyo") // タイムゾーンを紐づけ
utcInstant = zoned.toUTC() // UTC の瞬間に変換
unixTime = utcInstant.toUnixTime() // 秒(またはミリ秒)に変換して保存
逆に表示時は、次のようになります。
utcInstant = fromUnixTime(unixTime) // UNIX時間 → UTC の瞬間
zonedForUser = utcInstant.toTimeZone(userTimeZone) // ユーザーのTZに変換
display = format(zonedForUser) // 「YYYY-MM-DD HH:mm」などに
ローカル時間をそのままUNIX時間とみなしたり、「+9時間」をベタ書きしてしまうと、タイムゾーンやサマータイムを考慮できず、大きなズレの原因になります。
ライブラリに任せるべき処理とやってはいけない手計算
時間処理での鉄則は「できる限りライブラリに任せる」ことです。
特に次のような処理を手計算するのは避けるべきです。
- 日付の足し算・引き算(うるう年・月末日を含む)
- サマータイム(DST)をまたぐ計算
- タイムゾーンID(Asia/○○)からオフセットを求める処理
- 「毎月第n月曜日」のようなカレンダー演算

代表的なライブラリとしては、次のようなものがあります。
- Java:
java.timeパッケージ(Instant,ZonedDateTimeなど) - JavaScript/TypeScript:
Luxon,DateTimeFormat、最近は標準のTemporal(提案中/一部環境) - Python:
datetimeモジュール +zoneinfo/pytz - Go: 標準ライブラリ
time
「+9時間」「-5時間」などの定数をコード中にベタ書きするのは最小限に抑え、できる限りタイムゾーンIDとライブラリ関数で処理することが、将来のバグを大きく減らします。
開発でよくある時間ズレの原因
時間ズレの典型的な原因を、いくつかピックアップしておきます。
- ローカル時間をUTCと誤解する
- 例: フロントエンドで
new Date()のローカル時間をそのままサーバーに送信し、サーバー側でUTCとして扱ってしまう。
- 例: フロントエンドで
- クライアントとサーバーでタイムゾーンの前提が違う
- 例: サーバーはUTC、クライアントはJSTを前提としており、変換をどちらか一方しか行っていない。
- サマータイムを考慮せずに「固定オフセット」だけで処理する
- 例: アメリカ時間を「常にUTC-5」とハードコードしてしまう。
- DBのカラム定義と実際の中身が食い違う
- 例:
timestamp without time zoneにUTCを入れているのか、ローカル時間を入れているのかがチーム内で統一されていない。
- 例:
- 環境ごとにサーバーのタイムゾーン設定がバラバラ
- 例: 本番サーバーはUTCだが、開発環境はJSTのまま、という構成で動作差が出る。
「どのレイヤーで、どのタイムゾーンとして扱っているか」を明文化し、チームで共有しておくことが、時間ズレを防ぐ大きなポイントです。
初心者が押さえるべき実践的なポイント
DBにはUNIX時間(またはUTC)を保存するのが基本
実務でのセオリーとして、データベースにはUTC基準で時間を保存することが推奨されます。
具体的には次のようなパターンがあります。
- UNIX時間(整数)で保存する
- カラム型:
BIGINT(秒 or ミリ秒)
- カラム型:
- UTCのタイムゾーン付き日時で保存する
- 例: PostgreSQL の
timestamptzに UTC を保存
- 例: PostgreSQL の

DBの中身を「何時として解釈するか」を悩まない状態にしておくことが、長期運用では大きな安心につながります。
表示はユーザーのタイムゾーンで行う考え方
一方で、ユーザーに表示する時刻は、そのユーザーのタイムゾーンで表示するのが基本です。
国際的なサービスであれば、ユーザーごとにタイムゾーン設定を持ち、次のような流れで処理します。
- DBからUTC(またはUNIX時間)を取得する
- ユーザーのタイムゾーン情報を取得する
- ライブラリでUTC → ローカル時間に変換する
- ローカル時間をフォーマットして画面に表示する
これにより、アメリカのユーザーと日本のユーザーが同じイベントを見ても、それぞれのローカル時間で自然に理解できるようになります。
「DBはUTC統一、UIはユーザーごとにローカル表示」というパターンをセットで覚えておきましょう。
ログやトレースではUTCを使うメリット
アプリケーションログやトレース、監視のメトリクスでは、UTCで時間を記録することが一般的です。
理由はシンプルで、複数のサーバーや地域にまたがっても、時系列を素直に比較できるからです。
例えば、次のようなメリットがあります。
- サーバーA(東京)とサーバーB(シンガポール)のログを、単純な時間順で並べられる
- サマータイムによってログの順序が入れ替わる心配がない
- 障害対応で海外チームと連携する際も、基準時刻を共有しやすい
ログを閲覧するUI側で「ローカル時間に変換して見せる」ことはあっても、ログファイル内部の生データはUTCとして統一するのがおすすめです。
テスト時に時間を固定する・モックする重要性
時間が関わるコードをテストする場合、「今この瞬間」の依存を避けて、時間を固定・モックすることが重要です。
そうしないと、テストを実行する日や環境によって結果が変わってしまいます。
よくある工夫としては、次のようなものがあります。
- 「現在時刻を返す関数」を1カ所に集約し、テストでは差し替える
- フレームワークやライブラリが提供する「時計オブジェクト」(Clock, TimeProviderなど)を利用する
- うるう年、月末、サマータイム切り替え前後などの「境界日時」でテストする

現在時刻now()をそこら中で直接呼び出す設計は、テストしにくくバグも見つけにくい構成につながります。
時間を扱うコードこそ、テスト戦略を意識して設計することが大切です。
まとめ
UNIX時間とタイムゾーンは、どちらも「同じ時間を違う角度から見たもの」ですが、役割ははっきり分かれています。
UNIX時間はUTC基準の秒数カウンタとして「瞬間」を一意に表すための道具であり、タイムゾーンは人間が生活するカレンダー上の「日時」を表すためのルールセットです。
実務では、次のポイントを押さえておくと、時間まわりのトラブルを大きく減らせます。
- DBやログではUTC(またはUNIX時間)を使って統一する
- 画面表示やユーザー入力では、ユーザーのタイムゾーンで扱う
- サマータイムや日付計算は、必ずライブラリに任せる
- テストでは時間を固定・モックし、「今この瞬間」への依存を避ける
時間の扱いは最初はややこしく見えますが、「保存はUTC、表示はローカル」「計算はライブラリ」という基本パターンを身につければ、かなり見通しがよくなります。
少しずつ実際のコードで試しながら、自分なりの「時間の扱い方の型」を育てていくとよいでしょう。
