時間計算における丸め
本稿では時間計算についての丸め操作を考える。 最初に丸めの定義は以下とする。
丸め(まるめ)とは、与えられた数値を、ある一定の丸め幅の整数倍の数値に置き換えることである。 https://ja.wikipedia.org/wiki/%E7%AB%AF%E6%95%B0%E5%87%A6%E7%90%86
丸めの対象
時間計算における丸めの対象は瞬間(Instant)あるいは日時(LocalDateTime)とする。
瞬間 とはタイムゾーン(場所)によらず一意な時間である。瞬間は一般にUTC時間で表される。 一方、日時 は特定の瞬間を表すものではなく、タイムゾーンによってその日時を表す瞬間は異なる。 瞬間 と 日時 はタイムゾーンが与えられれば相互に変換することができる。
丸めの単位
時間計算における丸めの単位は継続時間(Duration)とする。 継続時間とは、長さを表す時間の概念で秒で表されるものとする(実際の実装ではナノ秒)。
ここでまず単純な例を挙げると:
- 2001/02/03 01:02:03 を 10分(600秒) を単位に切り捨てる → 2001/02/03 01:00:00
- 2001/02/03 01:02:03 を 10分(600秒) を単位に切り上げる → 2001/02/03 01:10:00
まず丸め処理をするためには、丸めの対象となる日時を数値に変換する必要がある。
この例だと丸めの単位が一日の長さの約数なので、日を無視して時間だけに注力すれば良い。
SI単位系を用いて計算すると 01:02:03
は (1 * 60 * 60) + (2 * 60) + 3 = 3723(秒)
となるので、ここから 3723 % 600 = 123(秒)
のように余りを求めてそれを足し引きすればいい。
これは自明は結果だが、以下のケースだとどうなるか:
- 2001/02/03 01:02:03 を 7分 を単位に切り捨てる → ?
この丸めは一般に定義できない。
なぜなら7分(420秒)というのは一日の長さの約数ではないからだ。
一日の長さははSI単位系で 24 * 60 * 60 = 86400(秒)
なので 86400 / 420 = 205.714286...
のように割り切ることはできない。
すると日を含めた日時を数値に変換して余りを求めなければならないが、それはどの瞬間を起点とするか、1年の長さが何秒なのかによって結果が異なってしまう。
これは 紀年法と暦法を定義しないと数値に変換することはできない ことを意味する。
実装
golang の time package https://golang.org/src/time/time.go
この実装では紀年法を西暦、暦をグレゴリオ暦と規定して1日の長さの約数でない丸めもサポートしている。 ナノ秒での丸めもするので、非常に難解なコードになっている。
java の time api https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/time/Instant.java#L748
この実装では1日の長さの約数でない丸めはサポートされず、もし与えられた場合は例外を投げている。
php の brick/date-time https://github.com/emonkak/date-time/blob/master/src/Instant.php#L315