Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般
  • Ruby / Rails関連

ISO8601を用いて時間を正確に表現しよう

9:15 これは時間を表します。といわれてどのような解釈ができるでしょうか。

朝会の開始時刻なら午前の9時15分、昨日の退社時刻なら午後9時15分、デートの予定なら今週土曜日の午前9時15分かもしれません。
いやいやこれは9分15秒のことで、9分15秒に電車が駅に到着するかもしれませんし、9分15秒後に電車が発車するかもしれません。

このように一概に「時間」といっても文脈や状況により様々な解釈が生まれます。この記事では時間の種類とその表現方法をご紹介します。

時点と期間

時間を表現するためには、それが「時点」なのか「期間」なのかを明確区別する必要があります。

時点
時間軸の中の特定の1点を表す。英語では Instant
期間
時間軸を2つの時点で切り取った区間またはその長さを表す。英語ではPeriodまたはDuration

ポイントは時点は1点で構成され、期間は2点で構成されるということです。
しかし時間表現が1つしか示されない場合、その表現には必ず時間の幅が生じているため、それが時点なのか期間なのかが曖昧になってしまいます。

例えば 2024年12月25日を例に取ると「今年のクリスマスは 2024年12月25日 です」のようにクリスマスその日を表していれば時点、「2024年12月25日 はクリスマスセール全品30%オフ」のようなクリスマスの1日間を表しているなら期間となります。

ISO8601による時点の表現

システムで時間を表現する方法としてISO8601形式がよく用いられます。Railsの場合は以下のように相互変換可能になります。

#ruby
Time.local(2024, 12, 25).iso8601() 
#=> 2024-12-25T00:00:00+09:00

Time.parse('2024-12-25T00:00:00+09:00')
#=> 2024-12-25 00:00:00 +0900

2024-12-25T00:00:00+09:00日本の2024年12月25日正子 の時点を表現します。

正子: 午前0時ジャストのことで、正午の真夜中バージョン。

0分や0秒(や0ミリ秒)を省略できるため、以下はすべて同じ時点を表現します。

  • 2024-12-25T00:00:00+09:00
  • 2024-12-25T00:00+09:00
  • 2024-12-25T00+09:00

この表現は時点を表現するもので、期間を表現するものではありません。
ISO8601では期間を表現する方法が別途定義されています。

ISO8601による期間の表現

期間の表現方法は、2つの時点で表現する方法と、1つの時点と時間長を用いる表現の2種類に分類されます。

期間を2つの時点で表現する場合、ISO8601では 始点/終点という2つの時点を / で区切ります。

例えば 2024-12-25T00:00:00+09:00/2024-12-26T00:00:00+09:00
2024年12月25日の00:00から翌26日の00:00の期間なので、2024年のクリスマス1日間を表します。

ISO8601による時間長の表現

ISO8601では時間の長さは以下のように表現します。

P[n]Y[n]M[n]DT[n]H[n]M[n]S

P
期間(period)を表す文字
[n]Y
[n]に数値を設定して、n年間を表す
[n]M
[n]に数値を設定して、n月間を表す
[n]D
[n]に数値を設定して、n日間を表す
T
年月日と時分秒の区切り文字
[n]H
[n]に数値を設定して、n時間を表す
[n]M
[n]に数値を設定して、n分間を表す
[n]S
[n]に数値を設定して、n秒間を表す

※n=0の場合はその要素を省略可。時以降がすべて省略される場合Tは省略する。

以下具体例になります。

期間長 ISO8601
太陽光が地球に届くまで 8分19秒 PT8M19S
CDの規格上収録時間 74分43秒 PT74M43S
地球の自転周期 23時間56分4秒 PT23H56M4S
がん保険免責期間 3ヶ月 P3M
地球の公転周期 365日5時間48分46秒 P365DT5H48M46S
帳簿類の保存期間 7年 P7Y

ここで注意すべきは、時間長の変換は一意に定まらない点にあります。

例えば P1Y は 一般的には P365D と言えますが、科学的には P365DT5H48M46S と定義できたり、簡単に P365.25D とすることもできます。

ActiveSupport::Duration における時間長のサポート

Railsでは時間長を実装するActiveSupport::Durationのparse(iso8601duration) 及び iso8601(precision: nil) を用いることで、ISO8601と相互変換することができます。

require 'active_support/all'

duration = ActiveSupport::Duration.parse('P1DT12H')
#=> 1 day and 12 hours
duration.iso8601()
#=> 'P1DT12H'

duration == 1.day + 12.hours
#=>true

Time.parse('2024-12-25T00:00:00+09:00') + duration
#=> 2024-12-26 12:00:00 +0900

なお、ActiveSupport::Durationでは年や月を含む期間を時点に加算した結果が存在しない月日になる場合は、その年月の末日に丸められるため注意が必要です。

require 'active_support/all'

Time.parse('2024-01-01T00:00:00+09:00') + 1.month 
#=> 2024-02-01 00:00:00 +0900
Time.parse('2024-01-31T00:00:00+09:00') + 1.month 
#=> 2024-02-29 00:00:00 +0900 # 2024-02-31 は存在しない
Time.parse('2024-01-31T00:00:00+09:00') + 2.months
#=> 2024-03-31 00:00:00 +0900

Time.parse('2024-02-29T00:00:00+09:00') + 1.year 
#=> 2025-02-28 00:00:00 +0900 # 2025年は閏年ではない

Time.parse('2024-01-31T00:00:00+09:00') + 30.days 
#=> 2024-03-01 00:00:00 +0900 # 日の加算なら繰り上がる

ISO8601による時間長を用いた期間の表現

期間を時間長を用いて表現する場合、ISO8601では 始点/時間長 または 時間長/終点 で表現します。

始点/時間長
始点から、時間長まで経過した期間
時間長/終点
終点から、時間長まで遡った期間

2024年のクリスマスの1日間は 始点: 2024-12-25T00:00:00+09:00, 終点: 2024-12-26T00:00:00+09:00, 時間長: P1D の3つの中から任意に2つを用いた以下3パターンで表現できます。

  • 始点/終点: 2024-12-25T00:00:00+09:00/2024-12-26T00:00:00+09:00
  • 始点/時間長: 2024-12-25T00:00:00+09:00/P1D
  • 時間長/終点: P1D/2024-12-26T00:00:00+09:00

ISO8601による期間の繰り返し

ISO8601では期間の繰り返しを表現することができ、以下のように定義されています。

R[n]/期間

すなわち

  • R[n]/始点/終点 または
  • R[n]/始点/時間長 または
  • R[n]/時間長/終点

nは繰り返し回数となり、n=1の場合は期間と同じ、n=0の場合は繰り返しなし、n=-1または省略されている場合は無限に繰り返しとなります。

例えば 2024年12月15日の朝トレメニューの実績(架空)は以下のように表現できます。

実施時間 メニュー 時間×セット数 ISO8601
09:00 瞑想 5分間×1セット R[1]/2024-12-15T09:00/PT5M
09:05 ラン 5分間×3セット R[3]/2024-12-15T09:05/PT5M
09:20 腹筋 1分間×10セット R[10]/2024-12-15T09:20/PT1M
09:30 スクワット 2分間×5セット R[5]/2024-12-15T09:30/PT2M
09:40 ベンチプレス 5分間×3セット R[3]/2024-12-15T09:40/PT5M
09:55 クールダウン 5分間×1セット R[1]/2024-12-15T09:55/PT5M

期間の繰り返しは、特定の時点を定めない繰り返し形式の表現も可能で以下のように定義されます。

R[n]/時間長

例えば朝トレメニューの計画表は以下のように表現できます。

経過時間 メニュー 時間×セット数 ISO8601
00' 瞑想 5分間×1セット R[1]/PT5M
05' ラン 5分間×3セット R[3]/PT5M
20' 腹筋 1分間×10セット R[10]/PT1M
30' スクワット 2分間×5セット R[5]/PT2M
40' ベンチプレス 5分間×3セット R[3]/PT5M
55' クールダウン 5分間×1セット R[1]/PT5M

まとめ

今回は時間には時点と期間の二つがあり、ISO8601では時点に限らず期間の表現や期間の繰り返しも定義されていることを見てきました。
これらは、時点及び時間長さの組み合わせで構成され、railsではISO8601における時点と時間長がサポートしていることを確認しました。

時間の取り扱いに起因したバグが生まれないシステム作りの一助となれば幸いです。

参考


BPSアドベントカレンダー2024


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。