How to calculate next, previous business day in Rails?

前端 未结 12 2352
粉色の甜心
粉色の甜心 2021-02-08 08:43

How to calculate next and previous business days in Rails?

相关标签:
12条回答
  • 2021-02-08 09:23

    maybe this gem can be useful for you question

    https://github.com/bokmann/business_time

    This let you calculate hours and days of business from a date given

    0 讨论(0)
  • 2021-02-08 09:32

    With the holidays-gem you can also check, if there is a public holiday. If you do so, you must define the region you need. The holidays-gem allows also to use subregions (e.g. us-va...)

    An example code with German (de) and US-american (us) holidays.

    require 'holidays'
    require 'holidays/us'
    require 'holidays/de'
    require 'holidays/core_extensions/date'
    class Date
      include Holidays::CoreExtensions::Date #provide Date#holiday?
    
      def next_business_day(region=:any)
        skip_weekends_and_holidays(1,region)
      end    
    
      def previous_business_day(region=:any)
        skip_weekends_and_holidays(-1,region)
      end
    
      def skip_weekends_and_holidays(inc, region = :any)
        date = self + inc
        while (date.wday == 6 or date.holiday?(region) ) do
          date += inc
        end   
        date
      end
    end
    

    Get attention: skip_weekends_and_holidays does not increment business days. If you increment 5 days from a Monday, you end on a Monday (unless this Monday is no holiday). If there was a holiday during the 5 days, there is additional increment.

    Some test code:

    [
      Date.new(2012,6,8), #Friday
      Date.new(2012,6,10), #Monday
      Date.new(2012,6,9), #Sunday
      Date.new(2012,12,24), #Christmas eve
      Date.new(2012,12,26), #After Christmas 
    ].each{|t|
      %w{us de}.each{|region|
        puts "====#{region}======"
        puts "Today: #{Date::DAYNAMES[t.wday]} #{Date::MONTHNAMES[t.mon]} #{t.day}"
        nextday = t.next_business_day(region)
        puts "Next B-day: #{Date::MONTHNAMES[nextday.mon]} #{nextday.day} - #{Date::DAYNAMES[nextday.wday]}"
        previousday = t.previous_business_day(region)
        puts "Previous B-day: #{Date::MONTHNAMES[previousday.mon]} #{previousday.day} - #{Date::DAYNAMES[previousday.wday]}"
      }
    

    An extract from result (christmas eve):

    ====us======
    Today: Monday December 24
    Next B-day: December 26 - Wednesday
    Previous B-day: December 23 - Sunday
    

    Germany has two free days (25+26.12):

    ====de======
    Today: Monday December 24
    Next B-day: December 27 - Thursday
    Previous B-day: December 23 - Sunday
    

    Update: I made another version to determine multiple business days:

    require 'holidays'
    require 'holidays/us'
    require 'holidays/core_extensions/date'
    #~ require 'holidays/de'
    class Date
      include Holidays::CoreExtensions::Date #provide Date#holiday?
      def next_business_day(region=:any)
        next_business_days(1,region)
      end    
    
      def next_business_days(inc, region=:any)
        date = self
        inc.times{
          date = date.next
          while (date.wday == 6 or date.holiday?(region) ) do
            date = date.next
          end
        }
        date
      end    
    
      def previous_business_day(region=:any)
        previous_business_days(1,region)
      end
    
      def previous_business_days(inc, region=:any)
        date = self
        inc.times{
          date = date.prev_day
          while (date.wday == 6 or date.holiday?(region) ) do
            date = date.prev_day
          end
        }
        date
      end    
    
    
    end
    

    My test code:

    require 'test/unit'
    class BDay_Test < Test::Unit::TestCase
      def test_2012_06_08_us()
        date = Date.new(2012, 6, 8)
        assert_equal( Date.new(2012, 06, 10), date.next_business_day('us'))
        assert_equal( Date.new(2012, 06,  7), date.previous_business_day('us'))
    
        assert_equal( Date.new(2012, 06, 17), date.next_business_days(7, 'us'))
        assert_equal( Date.new(2012, 05, 31), date.previous_business_day(7, 'us'))
      end
      def test_2012_06_08_de()
        date = Date.new(2012, 6, 8)
        assert_equal( Date.new(2012, 06, 10), date.next_business_day('de'))
        assert_equal( Date.new(2012, 06,  7), date.previous_business_day('de'))
    
        assert_equal( Date.new(2012, 06, 17), date.next_business_days(7, 'de'))
        assert_equal( Date.new(2012, 05, 31), date.previous_business_day(7, 'de'))
      end
      def test_2012_06_10_us()
        date = Date.new(2012, 6, 10)
        assert_equal( Date.new(2012, 06, 11), date.next_business_day('us'))
        assert_equal( Date.new(2012, 06,  8), date.previous_business_day('us'))
    
        assert_equal( Date.new(2012, 06, 18), date.next_business_days(7, 'us'))
        assert_equal( Date.new(2012, 06,  1), date.previous_business_day(7, 'us'))
      end
      def test_2012_06_10_de()
        date = Date.new(2012, 6, 10)
        assert_equal( Date.new(2012, 06, 11), date.next_business_day('de'))
        assert_equal( Date.new(2012, 06,  8), date.previous_business_day('de'))
    
        assert_equal( Date.new(2012, 06, 18), date.next_business_days(7, 'de'))
        assert_equal( Date.new(2012, 06,  1), date.previous_business_day(7, 'de'))
      end
      def test_2012_06_09_us()
        date = Date.new(2012, 6, 9)
        assert_equal( Date.new(2012, 06, 10), date.next_business_day('us'))
        assert_equal( Date.new(2012, 06,  8), date.previous_business_day('us'))
    
        assert_equal( Date.new(2012, 06, 17), date.next_business_days(7, 'us'))
        assert_equal( Date.new(2012, 06,  1), date.previous_business_day(7, 'us'))
      end
      def test_2012_06_09_de()
        date = Date.new(2012, 6, 9)
        assert_equal( Date.new(2012, 06, 10), date.next_business_day('de'))
        assert_equal( Date.new(2012, 06,  8), date.previous_business_day('de'))
    
        assert_equal( Date.new(2012, 06, 17), date.next_business_days(7, 'de'))
        assert_equal( Date.new(2012, 06,  1), date.previous_business_day(7, 'de'))
      end
      def test_2012_12_24_us()
        date = Date.new(2012, 12, 24)
        assert_equal( Date.new(2012, 12, 26), date.next_business_day('us'))
        assert_equal( Date.new(2012, 12, 23), date.previous_business_day('us'))
    
        assert_equal( Date.new(2013, 01,  3), date.next_business_days(7, 'us'))
        assert_equal( Date.new(2012, 12, 16), date.previous_business_day(7, 'us'))
      end
      def test_2012_12_24_de()
        date = Date.new(2012, 12, 24)
        assert_equal( Date.new(2012, 12, 27), date.next_business_day('de'))
        assert_equal( Date.new(2012, 12, 23), date.previous_business_day('de'))
    
        assert_equal( Date.new(2013, 01,  4), date.next_business_days(7, 'de'))
        assert_equal( Date.new(2012, 12, 16), date.previous_business_day(7, 'de'))
      end
      def test_2012_12_26_us()
        date = Date.new(2012, 12, 26)
        assert_equal( Date.new(2012, 12, 27), date.next_business_day('us'))
        assert_equal( Date.new(2012, 12, 24), date.previous_business_day('us'))
    
        assert_equal( Date.new(2013, 01,  4), date.next_business_days(7, 'us'))
        assert_equal( Date.new(2012, 12, 17), date.previous_business_day(7, 'us'))
      end
      def test_2012_12_26_de()
        date = Date.new(2012, 12, 26)
        assert_equal( Date.new(2012, 12, 27), date.next_business_day('de'))
        assert_equal( Date.new(2012, 12, 24), date.previous_business_day('de'))
    
        assert_equal( Date.new(2013, 01,  4), date.next_business_days(7, 'de'))
        assert_equal( Date.new(2012, 12, 17), date.previous_business_day(7, 'de'))
      end
    
    end    
    

    See test_2012_12_24_us() and date.next_business_days(7,... You end in 2013, each holiday in the period is respected.

    0 讨论(0)
  • 2021-02-08 09:33

    Here is a faster method that uses a simple calculation instead of iterating over the days.

    class Time
    
      def shift_weekdays(num_weekdays)
        base = self
    
        # corner case: self falls on a Sat or Sun then treat like its the next Monday
        case self.wday
          when 0
            base = self + 1.day
          when 6
            base = self + 2.day
        end
        day_of_week = base.wday - 1 # Monday is 0
    
        weekends = (day_of_week + num_weekdays) / 5
    
        base + (weekends*2).days + num_weekdays.days
      end
    
    end
    

    The method is on class Time but can be used on Date class as well.

    0 讨论(0)
  • 2021-02-08 09:34

    As far as I understand, this is what you are looking for? (tested it)

    require 'date'
    def next_business_day(date)
      skip_weekends(date, 1)
    end    
    
    def previous_business_day(date)
      skip_weekends(date, -1)
    end
    
    def skip_weekends(date, inc = 1)
      date += inc
      while date.wday == 0 || date.wday == 6
        date += inc
      end   
      date
    end
    

    You can test it as follows:

    begin
      t = Date.new(2009,9,11) #Friday, today
      puts "Today: #{Date::DAYNAMES[t.wday]} #{Date::MONTHNAMES[t.mon]} #{t.day}"
      nextday = next_business_day(t)
      puts "Next B-day: #{Date::MONTHNAMES[nextday.mon]} #{nextday.day}"
      previousday = previous_business_day(nextday)
      puts "back to previous: #{Date::MONTHNAMES[previousday.mon]} #{previousday.day}"
      yesterday = previous_business_day(previousday)
      puts "yesterday: #{Date::MONTHNAMES[yesterday.mon]} #{yesterday.day}"  
    end  
    
    0 讨论(0)
  • 2021-02-08 09:34

    You may need to calculate business days in the future starting from a Saturday or Sunday. 1 business day after a Monday is the Tuesday, 1 business day from a Sunday should also be Tuesday - the starting weekend day should be ignored. The following achieves this:

    class Date
    
      def business_days_future(inc)
        date = skip_weekend
        inc.times do
          date = date + 1
          date = date.skip_weekend
        end
        date
      end
    
      # If date is a saturday or sunday, advance to the following monday
      def skip_weekend
        if wday == 0
          self + 1
        elsif wday == 6
          self + 2
        else
          self
        end
      end
    
    end
    
    0 讨论(0)
  • 2021-02-08 09:36

    this is implementation:

    require 'business_time'
    
    date = Time.now
    
    next_workday(date)
    
    private
    
    def next_workday(date:)
      return date = date.next_weekday while date.workday?
    end
    
    0 讨论(0)
提交回复
热议问题