システム開発における時差の取り扱いについて

posted in: etc | 0

こんにちは。阿部です。

日本に住んでいると、北海道でも沖縄でも同じ日時なので、日時というものが、それほど複雑なものとは思っていない人も多いかと思います。
ところが、複数の国で使われるシステムを開発するには、複雑怪奇な日時と向き合う必要に迫られます。

日時の表記法

まず、表記法について説明しておきます。日時の表し方は国や地域ごとに異なります。例えば、

  • 2019/09/01 15:30
  • 01-09-2019 03:30 PM
  • Sep 1, 2019 03.30 PM

日本人だけが読むのなら、「2019/09/01 15:30」のようなフォーマットでほとんど問題ないかと思いますが、誤解のないように表記するには、国際規格に従うのが無難です。

ISO 8601 拡張形式:

  • 2019-09-01T12:34:56+09:00

「年/月/日T時:分:秒+時差」というフォーマットです。
詳細はリンク先を見てください。
JavaのAPIドキュメントなどでも、日時を示すときは ISO 8601の拡張形式で書かれていることが多いようです。
以下の説明でも、この表記法を使っていきます。

日時を構成する要素

日時を扱う上で重要な、次の3つの要素について説明していきます。

  1. 年月日時分秒
  2. オフセット
  3. タイムゾーン

1. 年月日時分秒

年月日時分秒は、私たちが日常的に使っている日時のことです。
細かいことを言えば、ミリ秒、ナノ秒の存在もあるのですが、そこまでの精度は必要ないものとしましょう。

ここで注意してほしいのは、年月日時分秒の情報だけでは、現実世界の「時点」を特定することができないということです。
カレンダーと時計を写した写真があったとして、どこの国で撮影されたのか分からなければ、実際の撮影時間を特定することはできませんよね。

2. オフセット

オフセットとは、いわゆる時差のことです。
日時が世界協定時(UTC)から、何時間何分ずれているかを表しています。
地球上の経度とおおよそ連動した値になります。
オフセットを表す、次のような図を見たことがあると思います。

時差が記された世界地図
https://commons.wikimedia.org/wiki/File:World_Time_Zones_Map.png

例えば日本なら +09:00(UTCより9時間早い)、ニューヨークなら -05:00(UTCより5時間遅い)といった具合です。

年月日時分秒とオフセットを合わせると、現実世界の一点の時刻を表すことができます。

3. タイムゾーン

タイムゾーンとは、「ある地域が、いつからいつまで、どのオフセットを採用するか」を表す情報です。
端的に言えば、夏時間の実施状況を表す情報です。

2019年現在の日本では、オフセットは一年中+09:00ですが、夏時間のある地域ではオフセットが変動します。
例えば、ロサンゼルスでは、通常-08:00のオフセットが、夏時間中は-07:00となります。

前述の年月日時分秒とオフセットは、定義の問題なので、不確実性はありませんでした。
ところが、このタイムゾーンというのは、政治的な要因でたびたび変更されてしまいます。
そのため、継続的なメンテナンスが必要な項目です。
日本人になじみ深い「元号」と同じような性質のものです。

たとえば、Javaのタイムゾーン関連のAPIは、tz databaseというデータベースを元にしています。
例えば、日本だと”Asia/Tokyo”、米国太平洋時間だと”America/Los_Angeles”のように、都市名を元にしたIDが振られています。

年月日時分秒とオフセットとタイムゾーンの3つを合わせると、ある地域のすべての時点(夏でも冬でも)における日時を表現することができるようになります。

まとめ

  • 年月日時分秒(LocalDateTime):\
    どこの日時かという情報がないが、日本だけを考えるならこれでも十分。
  • オフセット(ZoneOffset):\
    いわゆる時差。
  • タイムゾーン(ZoneId):\
    いつからいつまで夏時間が適用されるのかを表す情報。

参考までに、JavaとC#で該当するドキュメントへのリンクを貼っておきます。

Java C#
(1)年月日時分秒 LocalDateTime DateTime
(2)オフセット ZoneOffset DateTimeOffset
(3)タイムゾーン ZoneId TimeZoneInfo
(1) + (2) OffsetDateTime DateTimeOffset
(1) + (2) + (3) ZonedDateTime 該当なし
LINEで送る
Pocket