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ミリ秒)を省略できるため、以下はすべて同じ時点を表現します。
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における時点と時間長がサポートしていることを確認しました。
時間の取り扱いに起因したバグが生まれないシステム作りの一助となれば幸いです。
参考
- PDF: Data elements and interchange formats — Information interchange -Representation of dates and times — Part 1: Basic rules
- ISO 8601: Date and Time API の基礎知識
- ISO 8601 - Wikipedia
※正子: 午前0時ジャストのことで、正午の真夜中バージョン。