class BankingCalendar::Calendar

Constants

DEFAULT_BANKING_DAYS
DEFAULT_BANKING_HOURS
VALID_BANKING_HOURS_KEYS
VALID_CALENDAR_KEYS
VALID_DAYS

Attributes

additional_load_paths[RW]

Public Class Methods

load(calendar) click to toggle source
# File lib/banking_calendar/calendar.rb, line 10
def load(calendar)
  file_name = "#{calendar}.yml"

  directory = calendars.find do |d|
    File.exist?(File.join(d, file_name))
  end
  raise "Cannot find calendar #{calendar}" unless directory

  yaml = YAML.load_file(
    File.join(directory, file_name)
  ).transform_keys(&:to_sym)

  new(yaml)
end
load_calendar(calendar) click to toggle source
# File lib/banking_calendar/calendar.rb, line 25
def load_calendar(calendar)
  @semaphore.synchronize do
    @cached_calendars ||= {}
    unless @cached_calendars.include?(calendar)
      @cached_calendars[calendar] = load(calendar)
    end
    @cached_calendars[calendar]
  end
end
new(config) click to toggle source
# File lib/banking_calendar/calendar.rb, line 55
def initialize(config)
  @config = config
  validate_config
end

Private Class Methods

calendars() click to toggle source
# File lib/banking_calendar/calendar.rb, line 37
def calendars
  (@additional_load_paths || []) +
    [File.join(File.dirname(__FILE__), 'data')]
end

Public Instance Methods

after_banking_hours?(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 150
def after_banking_hours?(date)
  time_or_datetime? date
  return true unless banking_day?(date)

  date.hour > banking_hours.max
end
banking_day?(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 122
def banking_day?(date)
  date = date.to_date
  day = date.strftime('%a').downcase

  return false if bank_holidays.include?(date)
  return false unless banking_days.include?(day)

  true
end
banking_days_after(date, interval) click to toggle source

Given a date, add interval number of banking days.

If the given date is not a banking day, counting starts from the next banking day.

If banking hours are provided, returned date and time will be normalized to the end of banking day. If given date falls after banking hours, counting starts from the next banking day.

# File lib/banking_calendar/calendar.rb, line 92
def banking_days_after(date, interval)
  date = normalize_date(date, :after) if with_banking_hours?
  date = next_banking_day(date) unless banking_day?(date)

  interval.times do
    date = next_banking_day(date)
  end

  date
end
banking_days_before(date, interval) click to toggle source

Given a date, subtract interval number of banking days.

If the given date is not a banking day, counting starts from the previous banking day.

If banking hours are provided, returned date and time will be normalized to the end of banking day. If given date falls before banking hours, counting starts from the prior banking day.

# File lib/banking_calendar/calendar.rb, line 111
def banking_days_before(date, interval)
  date = normalize_date(date, :before) if with_banking_hours?
  date = previous_banking_day(date) unless banking_day?(date)

  interval.times do
    date = previous_banking_day(date)
  end

  date
end
banking_hour?(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 132
def banking_hour?(date)
  time_or_datetime?(date)

  hour = date.hour

  return false unless banking_day?(date)
  return false unless banking_hours.include?(hour)

  true
end
before_banking_hours?(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 143
def before_banking_hours?(date)
  time_or_datetime? date
  return false unless banking_day?(date)

  date.hour < banking_hours.min
end
end_of_banking_day(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 157
def end_of_banking_day(date)
  date.class.new(
    date.year,
    date.month,
    date.day,
    banking_hours.max + 1,
    0
  )
end
next_banking_day(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 66
def next_banking_day(date)
  loop do
    date += duration_for(date)
    break if banking_day?(date)
  end

  date
end
previous_banking_day(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 75
def previous_banking_day(date)
  loop do
    date -= duration_for(date)
    break if banking_day?(date)
  end

  date
end
validate_config() click to toggle source
# File lib/banking_calendar/calendar.rb, line 60
def validate_config
  unless (@config.keys - VALID_CALENDAR_KEYS).empty?
    raise "Only the following keys are valid: #{VALID_CALENDAR_KEYS.join(', ')}"
  end
end

Private Instance Methods

bank_holidays() click to toggle source
# File lib/banking_calendar/calendar.rb, line 235
def bank_holidays
  @bank_holidays ||= parse_dates(@config[:bank_holidays])
end
banking_days() click to toggle source
# File lib/banking_calendar/calendar.rb, line 227
def banking_days
  @banking_days ||= (@config[:banking_days] || DEFAULT_BANKING_DAYS).map do |day|
    day.downcase.strip[0..2].tap do |shortened_day|
      raise "#{day} is an invalid day." unless VALID_DAYS.include?(shortened_day)
    end
  end
end
banking_hours() click to toggle source
# File lib/banking_calendar/calendar.rb, line 215
def banking_hours
  @banking_hours ||= (@config[:banking_hours] || DEFAULT_BANKING_HOURS).map do |hour|
    hour.tap do |h|
      raise "#{h} is an invalid hour." if h > 24 || h.negative?
    end
  end
end
duration_for(date, interval = 1) click to toggle source
# File lib/banking_calendar/calendar.rb, line 169
def duration_for(date, interval = 1)
  date.is_a?(Date) ? interval : 3600 * 24 * interval
end
normalize_date(date, rollover) click to toggle source
# File lib/banking_calendar/calendar.rb, line 202
def normalize_date(date, rollover)
  time_or_datetime? date

  if rollover == :after
    date = roll_forward(date)
  elsif rollover == :before
    date = roll_backward(date)
  end
  date = end_of_banking_day(date)

  date
end
parse_dates(dates) click to toggle source
# File lib/banking_calendar/calendar.rb, line 173
def parse_dates(dates)
  (dates || []).map do |date|
    date.is_a?(Date) ? date : Date.parse(date)
  end
end
roll_backward(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 194
def roll_backward(date)
  if banking_day?(date) && before_banking_hours?(date)
    date = previous_banking_day(date)
  end

  date
end
roll_forward(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 186
def roll_forward(date)
  if banking_day?(date) && after_banking_hours?(date)
    date = next_banking_day(date)
  end

  date
end
time_or_datetime?(date) click to toggle source
# File lib/banking_calendar/calendar.rb, line 179
def time_or_datetime?(date)
  unless date.is_a?(Time) || date.is_a?(DateTime)
    raise "#{date} is #{date.class}. " \
      'Must be Time or DateTime if accounting for banking hours.'
  end
end
with_banking_hours?() click to toggle source
# File lib/banking_calendar/calendar.rb, line 223
def with_banking_hours?
  @config.key?(:banking_hours)
end