Rubyでholidaysを使った営業日の計算

2024/12/26に公開

はじめに

営業日計算は、納期管理やスケジュール調整で頻繁に必要になる機能です。ただ、土日や祝日を正確に考慮しようとすると、ちょっと面倒くさいところもあります。

この記事では、Rubyでholidaysという便利なGemを活用し、土日や祝日を考慮した営業日計算を簡単に実現する方法をご紹介します。

holidaysとは?

holidaysは、世界中の祝日データを扱うことができるライブラリです。国や地域ごとに定義された祝日データを取得し、日付操作に活用することができます。

https://github.com/holidays/holidays/tree/master

特徴

  • 多くの国と地域の祝日データをサポート
  • シンプルなAPIで祝日チェックが可能
  • カスタマイズした祝日データを定義可能

インストール

Gemfile
gem 'holidays'

Railsの場合

config/initializers/holidays.rb
require 'holidays/core_extensions/date'

class Date
  include Holidays::CoreExtensions::Date
end

holidaysが提供する拡張機能を利用することで、Dateクラスに営業日計算の機能を簡単に追加できます。

# 勤労感謝の日
d = Date.new(2024,11,23)

d.holiday?(:jp)
=> true

営業日計算の実装

営業日がどんな日を指すか、少し整理してみましょう。

  1. 平日
    基本的には月曜から金曜が営業日です。土曜と日曜はお休みの日として除きます。
  2. 祝日
    日本では、成人の日や建国記念の日などの祝日はお休みです。こういった日も非営業日になります。
  3. 特別なお休み
    年末年始やゴールデンウィークなど、会社ごとに設定されるお休みも非営業日として扱います。

このルールをまとめると、営業日として扱うのは以下のような日です:

  • 営業日: 月曜日から金曜日(祝日や特別なお休みを除く)
  • 非営業日:
    • 土曜日、日曜日
    • 祝日(例: 成人の日、建国記念の日など)
    • 年末年始(12月29日~1月3日)

これを踏まえた、営業日に関連する具体的な実装例です。

require 'holidays'

class Date
  # 年末年始の範囲を設定
  NEW_YEAR_HOLIDAYS = { start_day: 29, end_day: 3 } # 12月29日 ~ 1月3日

  # 営業日かどうかの判定
  def business_day?(region = :jp)
    # 土日を除外
    return false if saturday? || sunday?

    # 祝日を除外
    return false if holiday?(reregion)

    # 年末年始を除外
    return false if in_new_year_holiday_period?

    # 上記以外の日は営業日
    true
  end

  # N営業日後の日付
  def business_day_after(days, region = :jp)
    current_date = self
    while days > 0
      current_date += 1
      next unless current_date.business_day?(region)

      days -= 1
    end
    current_date
  end

  # 指定日からN営業日以内かどうかの判定
  def within_business_days_from?(start_date, n, region = :jp)
    current_date = start_date
    business_days_count = 0

    while current_date <= self
      business_days_count += 1 if current_date.business_day?(region)
      return true if business_days_count > 0 && business_days_count <= n
      current_date += 1
    end

    false
  end

  private

  # 年末年始の非営業日を判定
  def in_new_year_holiday_period?
    (month == 12 && day >= NEW_YEAR_HOLIDAYS[:start_day]) ||
    (month == 1 && day <= NEW_YEAR_HOLIDAYS[:end_day])
  end
end

任意の日付が営業日かどうか

(2024年11月)
1:金 2:土 3:日 4:月(振替休日) 5:火 6:水 7:木 ・・・

date = Date.new(2024, 11, 4)
date.business_day? # false(振替休日にも対応している)

date = Date.new(2024, 11, 5)
date.business_day? # true

任意の日付から3営業日後の日付を取得

date = Date.new(2024, 11, 1)
date.business_day_after(3) # 11月7日

任意の日付から5営業日以内かどうか

date = Date.new(2024, 11, 8)
start_date = Date.new(2024, 11, 1)
date.within_business_days_from?(start_date, 5) # true

祝日が変わった時の対応

祝日は、新しい法律や特別なイベントに伴って、追加や変更されることがあります。例えば、新しい祝日が制定されたり、特定のイベントに合わせて祝日が移動したりするケースがあります(例: 山の日の追加やオリンピック開催時の祝日移動など)。
基本的にはholidaysのメンテナが対応してくれるため、Gemを最新バージョンにしておくだけで問題ありません

holidaysの日本の祝日データは以下のファイルで管理されているため、最新の祝日データが反映されているかどうかを直接確認することも可能です。
https://github.com/holidays/holidays/blob/master/lib/generated_definitions/jp.rb

最新の祝日データは内閣府のサイトから確認することができます。
https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html

まとめ

この記事では、holidays使って営業日計算を実現する方法を解説しました。土日や祝日、年末年始を考慮したスケジュール管理を簡単に実装できることがお分かりいただけたと思います。

今回は紹介できませんでしたが、このGemでは独自の祝日データを定義することもできます。特定の休業日や会社独自のカレンダーを反映したい場合に有用です。ぜひ試してみてください!

chot Inc. tech blog

Discussion