Time::SecondsのONE_MONTHとONE_YEARについて
Time::SecondsのONE_MONTH
とONE_YEAR
を使う場合,本当にその方法で良いのかよく考えたほうが良いと思います.バグが出る可能性が高い気がします.
例えば以下の様な場合
use Time::Piece; use Time::Seconds; my $tp = localtime->strptime("2014-04", "%Y-%m"); say $tp->mon; # => 4 say $tp->ymd; # => 2014-04-01 $tp -= ONE_MONTH; say $tp->mon; # => 3 say $tp->ymd; # => 2014-03-01 $tp -= ONE_MONTH; say $tp->mon; # => 1 (!) say $tp->ymd; # => 2014-01-30 (!!)
このコードは,Time::PieceのオブジェクトからONE_MONTH
を引いてやる事によって1ヶ月前の月を表現したいという意図を表していますが,上手く動きません.
それもそのはず,Time::Seconds
の実装は以下のようになっており,
https://github.com/rjbs/Time-Piece/blob/master/Seconds.pm#L29-L31
ONE_MONTH
は2629744秒 (つまりONE_YEAR / 12
) のようにして定義されています.
30日を秒に表すと60 * 60 * 24 * 30 = 2592000
秒だからONE_MONTH
よりも小さいわけですね.
加えて2014年2月は28日しか無く,つまり60 * 60 * 24 * 28 = 2419200
秒なので,ここで決定的に差が生まれてしまったという訳ですね.これは具合が良くない! もちろん「1ヶ月前の今日」みたいなものも上手くは取れないですね.
そしてONE_YEAR
はONE_YEAR
で31556930秒,つまり365.24225日として表現されている為,こちらも普通に使うとおかしな事になってしまいます.
use Time::Piece; use Time::Seconds; my $tp = localtime->strptime("2014", "%Y"); $tp -= ONE_YEAR; say $tp->year; # 2012 (!) say $tp->ymd; # 2012-12-31 (!!)
のっけからやってくれる!!!! まあそれはそうですよね,という感じ.
こちらも「1年前の今日」みたいなものは上手く取れない.
で,どうするかというと,Time::Piece::Monthを使うと言った方法が考えられますが,このモジュールは内部でDate::Simpleを使っていて,Date::Simpleは呪われている(呪われていた)という問題があるので少し渋い.この方法でも問題なさそうだったらこれで良いとは思います.
問題が複雑では無かったら,LEAP_YEAR
やNON_LEAP_YEAR
,ONE_FINANCIAL_MONTH
や,あるいはONE_REAL_MONTH
やONE_REAL_YEAR
を駆使して自分で組み立てるというのも1つの手かもわかりません.
とにかく,ONE_MONTH
とONE_YEAR
を使って何らかの日付け操作をしているコードを見つけた時は疑ってみたほうが良いと思います.
手が空いたらここらへんをケアするシンプルなモジュールを書くのもやぶさかではありませんが,果たして手が空くのか!
[追記]
Time::Piece::Plus使えばええんや!!!!! という僕の中の僕が囁きましたのでシェアします.
[追記]
id:karupanerura氏「add_months/add_years ってメソッド使えや!!!」
とのこと.なんで僕はこのメソッドの存在を知らなかったのだろうか!?
ともかくONE_MONTHとかONE_YEARとかつかってたら大体おかしいことには違いないので,そういう場合は疑うってことでひとつ.