このエントリは、SmartHR Advent Calendar 2024 シリーズ2の3日目の記事です。
こんにちは、SmartHR基本機能でプロダクトエンジニアをしているyonoです。今日はタイトルにある通り、プロダクト開発で遭遇したRailsの日付計算のちょっとした罠についてお伝えしたいと思います。
前提: 育児休業について
SmartHR基本機能が提供する機能の一つとして、育児休業給付金を申請する機能があります。育児休業を取り扱うにあたって、育児休業をいつまで取得できるか、すなわち育児休業終了日を計算する必要があります。育児休業は原則、子が1歳に達する日まで取得できますが、最大で2歳に達する日まで延長できます。延長する際は1歳6ヶ月に達する日までと、2歳に達する日までの二回に分けて延長されます。
育児休業終了日を計算してみよう
では、育児休業終了日を計算してみましょう。 この〜歳に達する日というのは一見すると誕生日のように見えますが、実際は誕生日の前日を指しています。例えば1歳に達する日、というのはRailsだと以下のように記述できます。
birthday = Date.new(2024, 8, 31) childcare_leave_end_on = birthday + 1.year - 1.day #=> 2025/08/30
一回延長した際の育児休業終了日を計算してみよう
では次に、育児休業を一回延長した際の育児休業終了日について考えてみます。つまり、1歳6ヶ月の誕生日の前日ということになります。先程のロジックを一部変更して以下のように記述できます。
birthday = Date.new(2024, 8, 31) childcare_leave_end_on = birthday + 18.month - 1.day # => ?
一見よさそうですが、本当にそうでしょうか?では改めて、育児休業を一回延長した場合の育児休業終了日の計算について深堀りしてみます。
「1歳6ヶ月に達する日」の罠
1歳6ヶ月に達する日は、誕生日の1年6ヶ月後の月の、誕生日と同じ日(これを誕生日応当日といいます)の前日です。例えば誕生日が2024/06/10の場合は、誕生日応当日が2025/12/10で、1歳6ヶ月に達する日は2025/12/09ということになります。しかし、誕生日応当日が存在しない場合があります。例えば誕生日が2024/08/31の場合は、誕生日応当日は2026/02/31ということになりますが、このような日付は存在しません。このように誕生日応当日が存在しない場合は、育児休業終了日は誕生日の1年6ヶ月後の月の末日となります。つまり、2026/02/28が育児休業終了日ということです。
さて、先程のロジックで計算した日付を見てみましょう。
birthday = Date.new(2024, 8, 31) childcare_leave_end_on = birthday + 18.month - 1.day # => 2024/02/27
1日ずれています。なぜこのようなことになったのでしょうか?実は、Railsが提供する [Integer#month
] (https://api.rubyonrails.org/classes/Integer.html#method-i-months)などによる日付計算は、先程の2026/02/31のような存在しない日付が計算された場合は、自動的にその月の末日へ変換して返すようになっています。つまり、以下のようになります。
birthday = Date.new(2024, 8, 31) childcare_leave_end_on = birthday # 2024/08/31 + 18.month # 2026/02/28 - 1.day # 2026/02/27 月末のさらに前日になってしまった
どうすればよかったのか
振り返って考えてみましょう。
つまり、1歳6ヶ月の誕生日の前日ということになります。
さらっと流してしまったのですが、そもそも1歳6ヶ月の誕生日、という日常ではそれほど考えないであろう概念がでてきた時点で、立ち止まって深堀りする必要がありました。また、Railsの日付計算は非常に直感的で便利ですがそれゆえに、細かな仕様を見過ごしてしまいました。
では、改めて育児休業を一回延長した際の育児休業終了日を計算してみましょう。誕生日応当日の有無でロジックを変える必要があります。誕生日応当日がある場合は、その日の前日が育児休業終了日、ない場合は月末が育児休業終了日となります。誕生日応当日があるかどうかは、日付の計算結果と誕生日が同じ日であるかどうかで判定できそうです。例えば、2024/01/31 → 2025/07/31の場合は、日が同じため誕生日応当日があるということになります。ですが2024/08/31 → 2026/02/28の場合は、日が異なるため誕生日応当日がない、ということになります。誕生日応当日がない場合は代わりにその月の末日を計算する必要がありますが、先述した通りRailsの日付計算は存在しない日付が計算された場合はその月の末日へ変換して返すようになっています。ですので、計算結果をそのまま利用すればよいでしょう。これらをまとめると、以下のように記述できます。
birthday = Date.new(2024, 8, 31) birthday_corresponding_date = birthday + 18.month childcare_leave_end_on = if birthday.day == birthday_corresponding_date.day birthday_corresponding_date - 1.day else birthday_corresponding_date end #=> 2024/02/28
今度は適切に育児休業終了日を計算できました。
まとめ
育児休業終了日の計算を通して遭遇した、Railsの日付計算の罠について紹介しました。皆様の日々のRails lifeに少しでもお役に立てば幸いです。
We Are Hiring!
SmartHR では一緒に SmartHR を作りあげていく仲間を募集中です! 複雑なドメインをプロダクトに落とし込むことに興味がある方をお待ちしています! 少しでも興味を持っていただけたら、カジュアル面談でざっくばらんにお話ししましょう!