app/services/calendar_service.rb
# frozen_string_literal: true
class CalendarService
# https://www.opm.gov/policy-data-oversight/snow-dismissal-procedures/federal-holidays
class << self
def holiday?(date)
new(date.year).holiday?(date)
end
def observed_holiday?(date)
new(date.year).observed_holiday?(date)
end
def weekend?(date)
date.saturday? || date.sunday?
end
def weekend_or_holiday?(date)
weekend?(date) || holiday?(date)
end
def fiscal_start_date(date)
date.change(year: date.month >= 10 ? date.year : date.year - 1, month: 10, day: 1)
end
def fiscal_q2_start(date)
date.change(year: date.month >= 10 ? date.year + 1 : date.year, month: 1, day: 1)
end
def fiscal_q3_start(date)
date.change(year: date.month >= 10 ? date.year + 1 : date.year, month: 4, day: 1)
end
def fiscal_q4_start(date)
date.change(year: date.month >= 10 ? date.year + 1 : date.year, month: 7, day: 1)
end
def fiscal_end_date(date)
date.change(year: date.month >= 10 ? date.year + 1 : date.year, month: 9, day: 30)
end
end
attr_reader :year
def initialize(year)
@year = year
end
def holiday?(date)
holidays.any? { |holiday| holiday == date }
end
def observed_holiday?(date)
observed_holidays.any? { |oh| oh == date }
end
def holidays
[
new_years,
mlk,
washington,
memorial,
juneteenth,
independence,
labor,
columbus,
veterans,
thanksgiving,
christmas,
]
end
def observed_holidays
holidays.
concat([next_new_years]).
map(&method(:observed)).
select { |oh| oh.year == year }
end
# January 1st
def new_years
Date.new(year, 1, 1)
end
# 3rd Monday of January
def mlk
Date.new(year, 1, 1).
step(Date.new(year, 2, 1)).
select(&:monday?)[2]
end
# 3rd Monday of February
def washington
Date.new(year, 2, 1).
step(Date.new(year, 3, 1)).
select(&:monday?)[2]
end
# Last Monday of May
def memorial
Date.new(year, 6, 1).
step(Date.new(year, 5, 1), -1).
find(&:monday?)
end
# June 19th
def juneteenth
Date.new(year, 6, 19)
end
# July 4th
def independence
Date.new(year, 7, 4)
end
# First Monday of September
def labor
Date.new(year, 9, 1).
step(Date.new(year, 10, 1)).
find(&:monday?)
end
# Second Monday of October
def columbus
Date.new(year, 10, 1).
step(Date.new(year, 11, 1)).
select(&:monday?).second
end
# November 11th
def veterans
Date.new(year, 11, 11)
end
# 4th Thursday of November
def thanksgiving
Date.new(year, 11, 1).
step(Date.new(year, 12, 1)).
select(&:thursday?)[3]
end
# December 25th
def christmas
Date.new(year, 12, 25)
end
# If NY is on a Saturday, the observed holiday would be in the prior year,
# make sure to include it in observed holidays when necessary
def next_new_years
Date.new(year + 1, 1, 1)
end
private
def observed(date)
return date - 1 if date.saturday?
return date + 1 if date.sunday?
date
end
end