[ACCEPTED]-How to calculate next, previous business day in Rails?-ruby

Accepted answer
Score: 22

As far as I understand, this is what you 2 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 1 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  
Score: 11

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

An 12 example code with German (de) and US-american 11 (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 10 business days. If you increment 5 days from 9 a Monday, you end on a Monday (unless this 8 Monday is no holiday). If there was a holiday 7 during the 5 days, there is additional increment.

Some 6 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 5 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 4 made another version to determine multiple 3 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 2 end in 2013, each holiday in the period 1 is respected.

Score: 11
date = Date.today
date.next_weekday
date.prev_weekday

0

Score: 3

You may need to calculate business days 6 in the future starting from a Saturday or 5 Sunday. 1 business day after a Monday is 4 the Tuesday, 1 business day from a Sunday 3 should also be Tuesday - the starting weekend 2 day should be ignored. The following achieves 1 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
Score: 3

maybe this gem can be useful for you question

https://github.com/bokmann/business_time

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

Score: 1

I realise that this is an old thread, but 6 I just had to work this one out for myself 5 and I was looking for a very short bit of 4 code that was trivial to modify if a business 3 had weird opening days (like "closed Sunday/Monday").

def next_business_day(from_day)
  workdays = [1,2,3,4,5,6]
  test_day = from_day + 1.day
  return workdays.include?(test_day.wday) ? test_day : next_business_day(test_day)
end

I 2 suppose it could be shortened again to something 1 like this, but I think it becomes less obvious

def next_business_day(from_day)
  test_day = from_day + 1.day
  [1,2,3,4,5,6].include?(test_day.wday) ? test_day : next_business_day(test_day)
end
Score: 0

Well, you can use something like yesterday = 1.days.ago to get 6 yesterday's date. Use yesterday.strftime('%w') to get the day of 5 the week as a integer (0=Sunday, 6=Saturday). If 4 yesterday is 0 (Sunday), then the previous 3 day of the week would be 3.days.ago ... you 2 get the idea.

And you can use tomorrow = 1.days.since to get tomorrow's 1 date.

Score: 0

Here is a faster method that uses a simple 3 calculation instead of iterating over the 2 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 1 be used on Date class as well.

Score: 0

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

Score: 0

This is a method I use in my production 7 scheduling script:

require 'date'
def shift_business_days(date, incr)
  date = Date.parse(date.to_s)
  incr.abs.times do
    date += (incr < 0 ? -1 : 1)
    while date.saturday? || date.sunday? do
      date += (incr < 0 ? -1 : 1)
    end
  end
  date
end

It takes a date and a 6 positive or negative integer incr as arguments, and 5 increments or decrements the date by incr days, skipping 4 weekends. This has the added benefit of 3 being able handle a Date/Time object or 2 any date string that Date.parse can handle. For 1 example:

# (today is 2019-03-08)
shift_business_days(Time.now, 2)
##=> #<Date: 2019-03-12 ((2458555j,0s,0n),+0s,2299161j)>

shift_business_days('5/20', -10)
##=> #<Date: 2019-05-06 ((2458610j,0s,0n),+0s,2299161j)>
Score: 0

If you are running on rails, You could create 1 a config/initializers/date_time.rb

class DateTime
  def next_business_day
      # if its a friday saturday or sunday
    if self.day > 4

      # calculate the remaining days to add to the current day
      remaining_days = 7 - self.day + 1

      return remaining_days.days.from_now
    else

      # return tomorrow
      return self.tomorrow
    end
  end
end

usage:

a = DateTime.now
a.next_business_day
=> Mon, 08 Jun 2020 13:46:13 UTC +00:00

More Related questions