📅

Ruby on Railsで曜日を基準にした日付計算のコツ

2024/07/26に公開

こんにちは、okumudです。
今回は曜日を基準に計算するときの tips を投稿します。スケジュールの繰り返しで翌月の特定の日を求める際に役立ちます。
本文中では、 ActiveSupport を使用したコードを書いています。
執筆時の Ruby on Rails のバージョンは 7.1.3.4 です。

よく使う便利なメソッド

はじめに、日付計算を行うときによく使う便利なメソッドを紹介します。

月の初めや月末

月の初めや月末は、beginning_of_monthend_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_weekend_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_monthActiveSupport::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).days6.days となり、月曜日(datetime_week) から 6日 加算すると日曜日となります。

剰余を求めるときに、マイナス値を取りうるときは ((7 + wday - datetime_week.wday) % 7).days のように商を加算してから計算していたのですが、ruby ではやらなくても良かったことに驚きました。

月の最終週の特定の曜日 (最終週の水曜日など)

月の最終週の特定の曜日を取得するには、前項と同じようにbeginning_of_monthActiveSupport::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 の日付計算は非常に強力で使いやすく、直感的に行えます。計算が難しそうに見えても、強力なサポートがあるので、ぜひ使ってみてください。

SocialPLUS Tech Blog

Discussion