0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PT1M は1分で P1M は1か月。ISO 8601 の期間は T の前後で M の意味が変わり、月を含むと秒数が決まらない

0
Posted at

背景

YouTube の API や動画の構造化データで、PT4M13S のような文字列をよく見かけます。
私はこれを読むたびに頭の中で分解していて、正直ちょっと面倒でした…
なので、期間の文字列を秒数や「何時間何分」に直すツールを置いています。
https://hashitosystem.com/tools/duration/

この表記、P1M と PT1M を取り違えると結果が4万倍以上ずれます。
ツールの実装を読み直しながら確かめたことのメモです。

期間表記の読み方

ISO 8601 は日付や時刻の書き方を決めた国際規格だ。
その中に「期間(duration)」の書き方があり、P で始めて、数字と単位の文字を並べる。
単位は Y(年)、M(月)、W(週)、D(日)で、時刻の部分は T で区切ってから H(時)、M(分)、S(秒)を書く。

M が2回出てくるのがポイントです。
T より前の M は月、T より後ろの M は分である。
P1M は1か月、PT1M は1分。
PT1H30M は1時間30分で、P1DT12H は1日と12時間になります。

ツールの正規表現

ツールでは、下記の正規表現で各単位の数字を取り出しています。
入力は大文字にそろえた文字列で、m[2] から m[8] に年・月・週・日・時・分・秒が入ります。

const RE = /^([+-])?P(?!$)(?:(\d+(?:[.,]\d+)?)Y)?(?:(\d+(?:[.,]\d+)?)M)?(?:(\d+(?:[.,]\d+)?)W)?(?:(\d+(?:[.,]\d+)?)D)?(?:T(?!$)(?:(\d+(?:[.,]\d+)?)H)?(?:(\d+(?:[.,]\d+)?)M)?(?:(\d+(?:[.,]\d+)?)S)?)?$/;

T の前と後ろで M を別のグループにしているので、位置だけで月か分かが決まります。
P(?!$) と T(?!$) は、P や PT だけで中身が無い文字列をはじくための先読みです。
小数の区切りに , も許しているのは、ISO 8601 がカンマも小数点として認めているからです。

年と月は秒数にできない

ここが本題です。
1日は 86,400 秒、1時間は 3,600 秒と決まっていますが、1か月の長さは月によって違います。
下記は、各月の1日から「1か月後」までの日数を数えたコードです。

const days = (y, m) => (Date.UTC(y, m, 1) - Date.UTC(y, m - 1, 1)) / 86400000;
for (const [y, m] of [[2026, 1], [2026, 2], [2026, 4], [2028, 2]]) {
  console.log(`${y}-${m}-01 + 1か月 =`, days(y, m), '日');
}

結果は下記です。

2026-1-01 + 1か月 = 31 日
2026-2-01 + 1か月 = 28 日
2026-4-01 + 1か月 = 30 日
2028-2-01 + 1か月 = 29 日

同じ P1M でも、28日から31日まで4通りの長さになります。
つまり、どの日から数えるかという起点が無いと、P1M は秒数に確定できない。
年も、うるう年があるので同じことが言えます。

そこでツールは、年を365日・月を30日として概算し、その場合は結果に「概算です」と添えるようにしました。
下記は、ツールのパース関数を同じ正規表現でいくつかの入力に通した結果です。
exact は年と月が入っていないとき true になる値で、秒数を確定値として出してよいかの印です。

PT1M     60 exact=true
P1M      2592000 exact=false
P1Y      31536000 exact=false
PT1H30M  5400 exact=true
P1DT12H  129600 exact=true
PT36H    129600 exact=true
P        NG(format)
PT       NG(format)
P1H      NG(format)
PT0,5H   1800 exact=true

PT1M は 60 秒、P1M は 2,592,000 秒で、ちょうど 43,200 倍です。
1文字の位置違いでこれだけ変わるのは、ちょっと怖いですね…
P1H は T を書き忘れた形で、H は時刻側にしか書けないのでエラーにしています。

なぜ「概算」を結果と一緒に返すのか

月を30日にする計算自体は、よくある近似です。
ただ、それを黙って秒数だけ返すと、受け取った側は確定値だと思って使ってしまう。
exact を一緒に返しておけば、呼び出し側で「月を含むなら日付ライブラリで起点から足す」と分岐できます。

ちなみに日(D)も、夏時間のある地域では23時間や25時間の日があるので厳密には揺れます。
ツールは日本で使う前提で、1日を 86,400 秒に固定しています。

期間の文字列を秒に直すコードを書くときは、M がどちら側にあるかと、年・月を含むかどうかの2点だけは必ず見てください。


本記事はAI補助で執筆した、個人開発の紹介記事です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?