This page looks best with JavaScript enabled

【Rails】ago, sinceとmonthを組み合わせるときの月末の仕様

 ·  ☕ 2 分で読めます
✏️

まずはこれを見てほしい。

1
2
3
4
5
[1] pry(main)> Time.parse('2021-8-30').since(1.month)
=> 2021-09-30 00:00:00 +0900

[2] pry(main)> Time.parse('2021-8-31').since(1.month)
=> 2021-09-30 00:00:00 +0900

それぞれ、「8月30日の1ヶ月後」「8月31日の1ヶ月後」という意味だ。しかし、どちらも9月30日を指している
つまり、こうなる。

1
2
3
4
[1] pry(main)> day = Time.parse('2021-8-30')
=> 2021-08-30 00:00:00 +0900
[2] pry(main)> day.since(1.month) == day.tomorrow.since(1.month)
=> true

8月が31日まであるのに対して、9月が30日までしかないため、このようなことになってしまう。
もちろん、Time.parse('2021-8-28').since(6.month)Time.parse('2021-8-29').since(6.month)Time.parse('2021-8-30').since(6.month)Time.parse('2021-8-31').since(6.month)も同じになる。

これは1.monthのせい(おかげ)である。
1.monthの意味なら、8月31日の1ヶ月後は10月1日ではなく9月30日であるべきなのだから、理想の動きである(そもそも、ActiveSupportはそういうトリッキーなことをするための拡張モジュールである)。

1
2
[1] pry(main)> 1.month.class
=> ActiveSupport::Duration

なので、例えばテストでday.since(1.month) == day.tomorrow.since(1.month)の検証のようなことをしたいなら、day.since(1.week) == day.tomorrow.since(1.week)にするなどの変更をするとよい。
1.monthActiveSupport::Durationの中でも変動が激しい(1.yearもうるう年があるので変動し得る)値であるという認識を持たなければならない。

ちなみに、当然ながらこうなる。

1
2
3
4
5
[1] pry(main)> Time.parse('2020-2-29')
=> 2020-02-29 00:00:00 +0900

[2] pry(main)> Time.parse('2020-2-29').since(1.year)
=> 2021-02-28 00:00:00 +0900
Share on

END
END
@aiandrox

目次