Ruby on Railsで曜日を基準にした日付計算のコツ
こんにちは、okumudです。
今回は曜日を基準に計算するときの tips を投稿します。スケジュールの繰り返しで翌月の特定の日を求める際に役立ちます。
本文中では、 ActiveSupport を使用したコードを書いています。
執筆時の Ruby on Rails のバージョンは 7.1.3.4
です。
よく使う便利なメソッド
はじめに、日付計算を行うときによく使う便利なメソッドを紹介します。
月の初めや月末
月の初めや月末は、beginning_of_month
や end_of_month
を使うことで簡単に取得できます。
datetime = Time.current # 現在時刻
# => Wed, 24 Jul 2024 06:21:14.682120334 UTC +00:00
datetime.beginning_of_month
# => Mon, 01 Jul 2024 00:00:00.000000000 UTC +00:00
datetime.end_of_month
# => Wed, 31 Jul 2024 23:59:59.999999999 UTC +00:00
月末の日付部分が必要な場合は、次のように取得できます。
datetime.end_of_month.beginning_of_day
# => Wed, 31 Jul 2024 00:00:00.000000000 UTC +00:00
週の始まりや週の終わり
今回の記事では使用しませんが、週の始まりや週の終わりは、beginning_of_week
や end_of_week
を使うことで取得できます。
datetime = Time.current
# => Wed, 24 Jul 2024 06:21:14.682120334 UTC +00:00
datetime.beginning_of_week
# => Mon, 22 Jul 2024 00:00:00.000000000 UTC +00:00
datetime.end_of_week
# => Sun, 28 Jul 2024 23:59:59.999999999 UTC +00:00
📝 Date#wday(日曜日基準) と組み合わせて使うときは、beginning_of_week
等(月曜日基準)、基準とする曜日が異なるので、計算を行うときは注意が必要です
第n週の特定の曜日 (第2日曜日など)
第n週の特定の曜日(n=1〜4)を取得するには、 beginning_of_month
と ActiveSupport::Duration
を組み合わせて求めることが可能です。
なお、第5週には存在しない曜日があるため、次のコードで処理を行うと翌月になることがあります。
nth_week = 1 # 0:第1, 1:第2, 2:第3, 3:第4
# => 0
wday = 0 # 0:日, 1:月, 2:火, 3:水, 4:木, 5:金, 6:土
datetime = Time.current
# => Wed, 24 Jul 2024 06:21:14.682120334 UTC +00:00
datetime_week = datetime.beginning_of_month + nth_week.week
# => Mon, 08 Jul 2024 00:00:00.000000000 UTC +00:00
datetime_week + ((wday - datetime_week.wday) % 7).days
# => Sun, 14 Jul 2024 00:00:00.000000000 UTC +00:00
datetime.beginning_of_month + nth_week.week
では、月初からn週目を計算しています。
((wday - datetime_week.wday) % 7).days
では、基準日から「何日分加算すれば目的の曜日になるのか」を計算しています。
この例だと、 ((0 - 1) % 7).days
→ ((-1) % 7).days
→ 6.days
となり、月曜日(datetime_week) から 6日 加算すると日曜日となります。
剰余を求めるときに、マイナス値を取りうるときは ((7 + wday - datetime_week.wday) % 7).days
のように商を加算してから計算していたのですが、ruby ではやらなくても良かったことに驚きました。
月の最終週の特定の曜日 (最終週の水曜日など)
月の最終週の特定の曜日を取得するには、前項と同じようにbeginning_of_month
と ActiveSupport::Duration
を組み合わせて求めることが可能です。
wday = 3 # 0:日, 1:月, 2:火, 3:水, 4:木, 5:金, 6:土
datetime = Time.current
# => Wed, 24 Jul 2024 06:21:14.682120334 UTC +00:00
datetime_week = datetime.beginning_of_month + 1.month - 1.week
# => Thu, 25 Jul 2024 00:00:00.000000000 UTC +00:00
datetime_week + ((wday - datetime_week.wday) % 7).days
# => Wed, 31 Jul 2024 00:00:00.000000000 UTC +00:00
翌月の1週間前が最終週の始まりなので、 datetime.beginning_of_month + 1.month - 1.week
で求めています。
あとは、前項と同じように「何日分加算すれば目的の曜日になるのか」を計算しています。
余談: タイムゾーンの考慮
異なるタイムゾーンでの日付計算が必要な場合は、in_time_zone
を使うと、ローカル時間での日付計算ができます。
wday = 3 # 0:日, 1:月, 2:火, 3:水, 4:木, 5:金, 6:土
datetime = Time.current.in_time_zone('Asia/Tokyo')
# => Wed, 24 Jul 2024 09:21:14.682120334 JST +09:00
datetime_week = datetime.beginning_of_month + 1.month - 1.week
# => Thu, 25 Jul 2024 00:00:00.000000000 JST +09:00
datetime_week + ((wday - datetime_week.wday) % 7).days
# => Wed, 31 Jul 2024 00:00:00.000000000 JST +09:00
最後に
ActiveSupport や Ruby の日付計算は非常に強力で使いやすく、直感的に行えます。計算が難しそうに見えても、強力なサポートがあるので、ぜひ使ってみてください。
Discussion