【地球の中心でUnixTimestampを叫ぶ！】日付時刻とタイムゾーンの理解を深める

# 【地球の中心でUnixTimestampを叫ぶ！】日付時刻とタイムゾーンの理解を深める
## 序章

### クイズ

いきなりですが、クイズです。以下のエンジニアとのやり取りを見て「誤っている」「あいまいだ」という箇所を3つ探してください。

- 若手設計者Aと若手開発者Bのやり取り
  - 設計者A：Xシステムの画面仕様の件なんだけど、ここのデータベースのこのテーブルの項目に入っている日時型の値を画面にJSTで表示してほしいんだ！
  - 開発者B：オッケー、画面実装しておくよ。ていうか、このデータベースって、日時型をUTCで保持しているからちょっとめんどくさいんだよなぁ。
  - 設計者A：へー、そういうもんなんだね。
  - 開発者B：まあJSTで良いんだったら時差が＋9時間だから、取得した値に9時間足して表示するようにしておくね！
  - 設計者A：ありがとう！

解答は本記事の末尾に・・・。

### はじめに

こんにちは、京セラコミュニケーションシステム　立脇（@kccs_hiroshi-tatsuwaki）です。

さて、いきなりクイズからはじまった本記事ですが、実際に身の回りであったやり取りをオマージュして出題しています。

「若手エンジニアだからしょうがないね」というご意見をお持ちの方いらっしゃると思いますが、意外に中堅エンジニアでもきちんと理解できていない方、いらっしゃると思います。

タイトルがキャッチー（昔、こんなタイトルの映画ありましたよね（笑））ですが、私は日時型・タイムゾーンで迷ったときは、頭の中でこれを叫んで整理しています。何を言っているんだ、とお思いの方もこちらの記事を拝読いただき「なるほどな」と思っていただけたら幸いです。

またこれから書く考え方は、私の中で物事をシンプルにして理解していることですので、もしかすると正確性には少々欠けてしまうかも知れませんがその点ご了承ください。（なるべく正確に書くように心がけます！）

### 本記事の対象者

- わかった振りしているけど実はよくわかってないんだ、という方
- わかってはいるけど念のためおさえておこう、という方
- 冒頭のクイズを見て、3つも探せなかった方
- 地球の中心とUnixTimestampの関連性を知りたい方

## 本題

### まずUnixTimestampとは？

ざっくり説明になりますが、Unix Timestampとは、1970年1月1日　00:00:00（協定世界時）からの経過秒数を表す整数値です。つまり、この数字を見ることで、「いつ」という情報を瞬時に理解できるものです。

たとえば、2024年1月1日　00:00:00（協定世界時）のUnixTimestampは、「1704067200」となります。

参考までに、[こういうサイト](https://www.unixtimestamp.com/)もあったりします。

要はUnixTimestampは**整数値**なんです。これをよく覚えておいてください。

### 日時型とは？

UnixTimestampを理解したところで、たとえば『電車の改札に入った時刻、出た時刻を記録する』というシステムをUnixTimestampという概念しかない世界で実現することを考えてみましょう。

- 記録する
  - 電車の改札に入った時刻を記録する　→　今が「日本標準時の2024年1月23日　12:34:56」だからUnixTimestampで表現すると「1705980896」なのでこの整数を記録。
  - 電車の改札から出た時刻を記録する　→　今が「日本標準時の2024年1月23日　13:24:55」だからUnixTimestampで表現すると「1705983895」なのでこの整数を記録。

- 記録を閲覧する
  - 電車の改札に入った時刻を閲覧する　→　UnixTimestampでいうと「1705980896」だから、「日本標準時の2024年1月23日　12:34:56」ってことか。
  - 電車の改札から出た時刻を閲覧する　→　UnixTimestampでいうと「1705983895」だから、「日本標準時の2024年1月23日　13:24:55」ってことか。

整数値だけで管理するのは大変ですね。毎回この変換をしなければならないのはつらいです・・。

ですので、便利にするために日時型があるのです。以下に各言語・データベースでの日時型を表す型の例です。

| 言語/データベース | 日時型 | 詳細 |
|---|---|---|
|**Java**| `java.util.Date` | ミリ秒単位のタイムスタンプ |
| | `java.sql.Timestamp` | ミリ秒単位のタイムスタンプ (SQLとの互換性) |
| | `java.time.LocalDate` | 年月日 |
| | `java.time.LocalTime` | 時分秒 |
| | `java.time.LocalDateTime` | 年月日時分秒 |
| | `java.time.Instant` | UTCタイムスタンプ |
|**Python**| `datetime.datetime` | 年月日時分秒 |
| | `datetime.date` | 年月日 |
| | `datetime.time` | 時分秒 |
| | `datetime.timedelta` | 日時差 |
|**JavaScript**| `Date` | ミリ秒単位のタイムスタンプ |
| | `Date.now()` | 現在時刻のミリ秒単位のタイムスタンプ |
|**MySQL**| `DATETIME` | 年月日時分秒 (マイクロ秒まで) |
| | `TIMESTAMP` | 年月日時分秒 (マイクロ秒まで) |
| | `DATE` | 年月日 |
| | `TIME` | 時分秒 |
|**PostgreSQL**| `timestamp` | 年月日時分秒 (マイクロ秒まで) |
| | `timestamp without time zone` | 年月日時分秒 (マイクロ秒まで) |
| | `timestamp with time zone` | 年月日時分秒 (マイクロ秒まで、タイムゾーン付き) |
| | `date` | 年月日 |
| | `time` | 時分秒 |
|**BigQuery**| `TIMESTAMP` | 年月日時分秒 (ナノ秒まで) |
| | `DATE` | 年月日 |
| | `TIME` | 時分秒 |

各言語・データベースにおいて、これらの型を利用することで、先ほどの『UnixTimestampでいうと「1705980896」だから、「日本標準時の2024年1月23日　12:34:56」ってことか』のような面倒な変換を簡単にできるようにしているのです。

でも、**日時の実体は「整数値であるUnixTimestampである」ということ**を忘れないでください。

### では、タイムゾーンって？

タイムゾーンは、地球上の異なる地域で同じ時刻を保つために設定された時間帯です。世界は24のタイムゾーンに分けられており、それぞれのタイムゾーンは協定世界時 (UTC) から東または西に15度ずつずらしています。

参考までにタイムゾーンの例をいくつかご紹介します。

| タイムゾーン| 略称 | 協定世界時時差 | 詳細 |
|---|---|---|---|
|America/Los_Angeles|PST|-8時間|アメリカ合衆国太平洋標準時を表します。|
|America/New_York|EST|-5時間|アメリカ合衆国東部標準時を表します。|
|Europe/London|GMT（UTC）|なし|英国グリニッジ標準時（=協定世界時）を表します。|
|Africa/Johannesburg|SAST|+2時間|南アフリカ標準時を表します。|
|Asia/Tokyo|JST|+9時間|日本標準時を表します。|
|Australia/Sydney|AEST|+10時間|オーストラリア東部標準時を表します。|

大きい国だと地域によって時差があるわけですね。日本は標準時は1個だけなのでシステム実装側としてはシンプルで助かりますね！

ここでようやく**「地球」**というワードが出てきました！

### 地球の中心でUnixTimeを叫ぶ！

ここでおさえておきたいのは、タイムゾーンが異なっている2つのタイムゾーンの日時を見たときに

- 日本標準時の2024年1月1日　09:00:00
- 世界標準時の2024年1月1日　00:00:00

が指しているUnixTimestampは、同じ「1704067200」であることです。

当たり前のことですが、**地球に暮らす私たちは場所が離れていても同じ日同じ時（=UnixTimestamp）を共有してる**わけですね。この感覚がすごく大事です。

これを思い出すときに、私はいつも以下の絵を思い浮かべます。

![日時の表現](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140479/e224e352-5cc5-a7ba-582c-08d466e114b3.png)

まさに地球の中心にあるUnixTimestampは不変のものである！ということがイメージしやすくなると思います。

### ところでタイムゾーン文字「JST」って非推奨であることを知ってましたか？

そもそも「UTC」や「Asia/Tokyo」や「JST」といったタイムゾーン文字はどこかで定義されているのでしょうか。

実は[Time Zone Database](https://www.iana.org/time-zones)にて定義されています。2024年7月時点で「Released 2024-02-01」と記載がありますので、延々と更新されているものなんですね。

この中の記載を読んでいくと

- JSTはJapan Standard Timeの略

と記載があると同時に

- JSTはJerusalem Standard Time（エルサレム標準時）の略

とも記載があります。（このあたりの細かい話は、私のタイムゾーン理解のバイブル「[タイムゾーン呪いの書](https://zenn.dev/dmikurube/articles/curse-of-timezones-common-ja)」をご確認ください。すごくおもしろいです。）

何が言いたいかというと、**JSTのような「略語」はなるべく使わない方が良いということ**です。一意ではない可能性がある時点で使わない方が好ましいです。

面倒でも「Asia/Tokyo」もしくは「+0900」のような時差での表記を心がけましょう。

## まとめ

### クイズの解答

ここまで本記事を読んでいただいた皆さんは、もう答えはわかりますよね。以下に解答例を記載します。

- 設計者Aさんの『日時型の値を画面にJSTで表示してほしい』はあいまいです。上でも述べているように「日本標準時」「Asia/Tokyo」「+0900」がより正確ですね。
- 開発者Bさんの『このデータベースって、日時型をUTCで保持している』が誤りです。日時型の実体はUnixTimestampですから、日時型をUTCで保持している、という表現はおかしいです。
- 開発者Bさんの『時差が＋9時間だから、取得した値に9時間足して表示する』が誤りです。

### 具体的な例題

こんなお題を考えてみましょう。

> CSVファイルから日時項目を読み取って日本時間で標準出力するPythonプログラム

```python
# 以下のようなCSVが存在する前提です。なお、OSのローカルタイムゾーンは協定世界時です。
# --data.csv------------
# ヘッダ
# 2024-01-01 00:00:00
# 2024-01-02 00:00:00
# ----------------------

import csv
import datetime

def main():
    with open('data.csv', 'r', encoding='utf-8') as f:
        reader = csv.reader(f)
        # ヘッダー行をスキップ
        next(reader)
        for row in reader:
            # 日時項目を日付型に変換 (ここではタイムゾーンを指定していないので、システムのローカルタイムゾーンが使用されます。仕様上問題無いか確認しましょう。)
            date_str = row[0] # 日時項目はCSVファイルの1列目にあると仮定
            dt = datetime.datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
            # 日本標準時 (Asia/Tokyo) に変換
            dt_jst = dt.astimezone(datetime.timezone(datetime.timedelta(hours=9)))
            print("日本時間："+dt_jst.strftime('%Y-%m-%d %H:%M:%S'))

main()
```

出力結果は以下となります。
```
日本時間：2024-01-01 09:00:00
日本時間：2024-01-02 09:00:00
```

決して9時間加算しているわけではなく、タイムゾーンの概念で変換していることがわかると思います。（9時間加算してしまうと、UnixTimestampが別のものになってしまいます。）

このように、明示的にタイムゾーンを利用するときもそうですが、暗黙的にシステムのタイムゾーンを利用する際も、しっかりと理解してその仕様で問題ないかを熟考しましょう。

### まとめ

日付文字列から日時型（=UnixTimestamp）に変換する際、または日時型（=UnixTimestamp）から日付文字列に変換して表示する際、それぞれどちらのパターンでもタイムゾーンを意識するようにしましょう。

- **日付文字列から日時型（=UnixTimestamp）に変換する** 
  - この日付文字はUnixTimestampに変換する際のタイムゾーンは何か、をキッチリ考える。（暗黙的な変換をしない。）
  - 地表で表示されている文字をどういう解釈（=タイムゾーン）でUnixTimestampに変換したいのか。

- **日時型（=UnixTimestamp）から日付文字列に変換する** 
  - このUnixTimestampを見せたい相手はどこのタイムゾーンで見たいのか、をキッチリ考える。（暗黙的な変換をしない。）
  - 地球の中心からどこの地表（=タイムゾーン）に表示させたいのか。

### 余談と所感

ちなみに、冒頭のクイズのAさんとBさんのやり取りでも、画面表示について実は問題になりません。（「日本時間で表示する」という仕様は満たせます。）

ただ、この画面表示のプログラムを異なるOSのシステムに移植したり、データベースのタイムゾーンの設定変更を実施したときに挙動が変わったりします。サイレントでこういう問題が仕込まれてしまいます。だからこそ厄介な問題であると私は思っています。

私はソフトウェアの最大のメリットは「再利用性」だと思っています。その再利用性を阻害するような実装はなるべく避けたいものですね。


## おしらせ

弊社X（旧：Twitter）では、Qiita投稿に関する情報や各種セミナー情報をお届けしております。情報収集や学びの場を求める皆さん！ぜひフォローしていただき、最新情報を手に入れてください:grinning:

https://x.com/kccs_digital