Class: Roxbury::BusinessCalendar

Inherits:
Object
  • Object
show all
Defined in:
lib/roxbury/business_calendar.rb

Constant Summary collapse

DAYS_OF_THE_WEEK =
%w[Mon Tue Wed Thu Fri Sat Sun]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(working_hours: {}, holidays: []) ⇒ BusinessCalendar

Returns a new instance of BusinessCalendar.



7
8
9
10
11
12
13
14
15
# File 'lib/roxbury/business_calendar.rb', line 7

def initialize working_hours: {}, holidays: []
  @working_hours = DAYS_OF_THE_WEEK.inject({}) do |wh, dow|
    wh.merge dow => WorkingHours.parse(working_hours[dow])
  end
  if @working_hours.values.all?(&:non_working?)
    raise ArgumentError, 'You must specify at least one working day in working_hours.'
  end
  @holidays = Set.new(holidays)
end

Instance Attribute Details

#working_hoursObject (readonly)

Returns the value of attribute working_hours.



5
6
7
# File 'lib/roxbury/business_calendar.rb', line 5

def working_hours
  @working_hours
end

Instance Method Details

#add_working_days(to, number_of_days) ⇒ Date, Time

Returns The result of adding the number_of_days to the given date. If a Date is given returns a Date, otherwise if a Time is given returns a Time.

Parameters:

  • to (Date, Time)
  • number_of_days (Integer, Float)

Returns:

  • (Date, Time)

    The result of adding the number_of_days to the given date. If a Date is given returns a Date, otherwise if a Time is given returns a Time.



57
58
59
60
# File 'lib/roxbury/business_calendar.rb', line 57

def add_working_days to, number_of_days
  result = add_working_hours(to, number_of_days * max_working_hours_in_a_day)
  to.is_a?(Date) ? result.to_date : result
end

#add_working_hours(to, number_of_hours) ⇒ Object

Raises:

  • (ArgumentError)


33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/roxbury/business_calendar.rb', line 33

def add_working_hours to, number_of_hours
  raise ArgumentError, 'number_of_hours must not be negative' if number_of_hours < 0
  to = cast_time(to, :start)
  rolling_timestamp = roll_forward(to)
  remaining_hours = number_of_hours

  until (bday = business_day(rolling_timestamp)).include?(rolling_timestamp + remaining_hours.hours)
    remaining_hours -= bday.number_of_working_hours(from: rolling_timestamp)
    rolling_timestamp = at_beginning_of_next_business_day(rolling_timestamp)
  end

  rolling_timestamp + remaining_hours.hours
end

#at_beginning_of_next_business_day(date) ⇒ Object

Snaps the date to the beginning of the next business day.



77
78
79
# File 'lib/roxbury/business_calendar.rb', line 77

def at_beginning_of_next_business_day date
  roll_forward date.tomorrow.beginning_of_day
end

#holiday?(date_or_time) ⇒ Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/roxbury/business_calendar.rb', line 81

def holiday? date_or_time
  @holidays.include?(date_or_time.to_date)
end

#roll_forward(date) ⇒ Object

Snaps the date to the beginning of the next business day, unless it is already within the working hours.

Parameters:

  • date (Date, Time)


65
66
67
68
69
70
71
72
73
74
# File 'lib/roxbury/business_calendar.rb', line 65

def roll_forward date
  bday = business_day(date)
  if bday.include?(date)
    date
  elsif bday.starts_after?(date)
    bday.at_beginning
  else
    roll_forward date.tomorrow.beginning_of_day
  end
end

#working_days_between(from, to) ⇒ Float

Returns the number of working days between the given dates.

Parameters:

  • from (Date, Time)

    if it’s a date, it’s handled as the beginning of the day

  • to (Date, Time)

    if it’s a date, it’s handled as the end of the day

Returns:

  • (Float)

    the number of working days between the given dates.



50
51
52
# File 'lib/roxbury/business_calendar.rb', line 50

def working_days_between from, to
  working_hours_between(from, to) / max_working_hours_in_a_day.to_f
end

#working_hours_between(from, to) ⇒ Float

Returns the number of working hours between the given dates.

Parameters:

  • from (Date, Time)

    if it’s a date, it’s handled as the beginning of the day

  • to (Date, Time)

    if it’s a date, it’s handled as the end of the day

Returns:

  • (Float)

    the number of working hours between the given dates



20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/roxbury/business_calendar.rb', line 20

def working_hours_between from, to
  from, to, sign = invert_if_needed cast_time(from, :start), cast_time(to, :end)

  working_hours_per_day = (from.to_date..to.to_date).map do |date|
    filters = {}
    filters[:from] = from if date == from.to_date
    filters[:to] = to if date == to.to_date
    business_day(date).number_of_working_hours filters
  end

  working_hours_per_day.sum.round(2) * sign
end