Last active
February 7, 2022 18:32
-
-
Save molawson/3af26eedd0106d950ac1e39100368077 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "bundler/inline" | |
gemfile do | |
source "https://rubygems.org" | |
gem "repeatable" | |
end | |
Schedule = Repeatable::Schedule | |
module ScheduleHelpers | |
def weekdays | |
{ | |
union: [ | |
{ weekday: { weekday: 1 } }, | |
{ weekday: { weekday: 2 } }, | |
{ weekday: { weekday: 3 } }, | |
{ weekday: { weekday: 4 } }, | |
{ weekday: { weekday: 5 } }, | |
] | |
} | |
end | |
end | |
class PCOPTOSchedule | |
include ScheduleHelpers | |
def initialize(year) | |
@year = year | |
end | |
def beginning_of_year | |
Date.new(@year, 1, 1) | |
end | |
def end_of_year | |
Date.new(@year, 12, 31) | |
end | |
def all | |
{ | |
union: [ | |
summer_fridays, | |
non_summer_fridays_off, | |
christmas_break, | |
holidays, | |
] | |
} | |
end | |
def summer_fridays | |
{ | |
intersection: [ | |
{ range_in_year: { start_month: 6, end_month: 8 } }, | |
{ weekday: { weekday: 5 } }, | |
] | |
} | |
end | |
def non_summer_fridays_off | |
{ | |
difference: { | |
included: all_non_summer_fridays, | |
excluded: { union: [holidays, christmas_break, non_summer_friday_exceptions].compact }, | |
} | |
} | |
end | |
def all_non_summer_fridays | |
{ | |
intersection: [ | |
every_other_friday, | |
union: [ | |
{ range_in_year: { start_month: 1, end_month: 5 } }, | |
{ range_in_year: { start_month: 9, end_month: 12 } }, | |
] | |
] | |
} | |
end | |
def non_summer_friday_exceptions | |
exceptions = Schedule.new(all_non_summer_fridays).occurrences(beginning_of_year, end_of_year).select do |d| | |
# is this friday the week before a friday holiday? | |
Schedule.new(union: [holidays, christmas_break]).include?(d + 7) | |
end | |
{ union: exceptions.map { |d| { exact_date: { date: d } } } } if exceptions.any? | |
end | |
def every_other_friday | |
{ biweekly: { weekday: 5, start_after: Date.new(@year, 1, 7) } } | |
end | |
def christmas_break | |
{ | |
intersection: [ | |
weekdays, | |
{ range_in_year: { start_month: 12, start_day: 24, end_day: 31 } }, | |
] | |
} | |
end | |
def holidays | |
{ | |
union: [ | |
new_years_day, | |
mlk_day, | |
good_friday, | |
memorial_day, | |
july_4, | |
labor_day, | |
thanksgiving, | |
day_after_thanksgiving, | |
] | |
} | |
end | |
private | |
def new_years_day | |
{ | |
intersection: [ | |
weekdays, | |
{ exact_date: { date: Date.new(@year, 1, 1) } }, | |
] | |
} | |
end | |
def mlk_day | |
{ | |
intersection: [ | |
{ weekday_in_month: { weekday: 1, count: 3 } }, | |
{ range_in_year: { start_month: 1 } } | |
] | |
} | |
end | |
def good_friday | |
{ exact_date: { date: YearSpecificDates.new(@year).good_friday } } | |
end | |
def memorial_day | |
{ | |
intersection: [ | |
{ weekday_in_month: { weekday: 1, count: -1 } }, | |
{ range_in_year: { start_month: 5 } } | |
] | |
} | |
end | |
def july_4 | |
{ exact_date: { date: YearSpecificDates.new(@year).independence_day_observed } } | |
end | |
def labor_day | |
{ | |
intersection: [ | |
{ weekday_in_month: { weekday: 1, count: 1 } }, | |
{ range_in_year: { start_month: 9 } } | |
] | |
} | |
end | |
def thanksgiving | |
{ | |
intersection: [ | |
{ weekday_in_month: { weekday: 4, count: 4 } }, | |
{ range_in_year: { start_month: 11 } } | |
] | |
} | |
end | |
def day_after_thanksgiving | |
{ | |
intersection: [ | |
{ weekday_in_month: { weekday: 5, count: 4 } }, | |
{ range_in_year: { start_month: 11 } } | |
] | |
} | |
end | |
class YearSpecificDates | |
include ScheduleHelpers | |
def initialize(year) | |
@year = year | |
end | |
def independence_day_observed | |
july_4 = Date.new(@year, 7, 4) | |
candidates = Schedule.new(weekdays).occurrences(Date.new(@year, 7, 3), Date.new(@year, 7, 5)) | |
candidates.sort_by { |d| (july_4 - d).abs }.first | |
end | |
def good_friday | |
easter - 2 | |
end | |
# this is wacky, but it works! and apparently it's been around for quite a while ¯\_(ツ)_/¯ | |
# https://en.wikipedia.org/wiki/date_of_easter#anonymous_gregorian_algorithm | |
def easter | |
a = @year % 19 | |
b = @year / 100 | |
c = @year % 100 | |
d = b / 4 | |
e = b % 4 | |
g = ((8 * b) + 13) / 25 | |
h = ((19 * a) + b - d - g + 15) % 30 | |
i = c / 4 | |
k = c % 4 | |
l = (32 + (2 * e) + (2 * i) - h - k) % 7 | |
m = (a + (11 * h) + (19 * l)) / 433 | |
month = (h + l - (7 * m) + 90) / 25 | |
day = (h + l - (7 * m) + (33 * month) + 19) % 32 | |
Date.new(@year, month, day) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
year = 2022 | |
start = Date.new(year, 1, 1) | |
finish = Date.new(year, 12, 31) | |
schedule = PCOPTOSchedule.new(year) | |
all = Schedule.new(schedule.all).occurrences(start, finish) | |
summer_fridays = Schedule.new(schedule.summer_fridays).occurrences(start, finish) | |
non_summer_fridays = Schedule.new(schedule.non_summer_fridays_off).occurrences(start, finish) | |
christmas_break = Schedule.new(schedule.christmas_break).occurrences(start, finish) | |
holidays = Schedule.new(schedule.holidays).occurrences(start, finish) | |
puts "summer fri | non-summer fri | xmas break | holidays | total" | |
puts "-----------------------------------------------------------" | |
puts "#{"%10s" % summer_fridays.count} | #{"%14s" % non_summer_fridays.count} | #{"%10s" % christmas_break.count} | #{"%8s" % holidays.count} | #{"%5s" % all.count}" | |
# output => | |
# summer fri | non-summer fri | xmas break | holidays | total | |
# ----------------------------------------------------------- | |
# 13 | 16 | 5 | 7 | 41 | |
puts "Days Off" | |
all.each { |d| puts d.strftime("%a %b %e") } | |
# OUTPUT => | |
# Fri Jan 14 | |
# Mon Jan 17 | |
# Fri Jan 28 | |
# Fri Feb 11 | |
# Fri Feb 25 | |
# Fri Mar 11 | |
# Fri Mar 25 | |
# Fri Apr 15 | |
# Fri Apr 22 | |
# Fri May 6 | |
# Fri May 20 | |
# Mon May 30 | |
# Fri Jun 3 | |
# Fri Jun 10 | |
# Fri Jun 17 | |
# Fri Jun 24 | |
# Fri Jul 1 | |
# Mon Jul 4 | |
# Fri Jul 8 | |
# Fri Jul 15 | |
# Fri Jul 22 | |
# Fri Jul 29 | |
# Fri Aug 5 | |
# Fri Aug 12 | |
# Fri Aug 19 | |
# Fri Aug 26 | |
# Mon Sep 5 | |
# Fri Sep 9 | |
# Fri Sep 23 | |
# Fri Oct 7 | |
# Fri Oct 21 | |
# Fri Nov 4 | |
# Thu Nov 24 | |
# Fri Nov 25 | |
# Fri Dec 2 | |
# Fri Dec 16 | |
# Mon Dec 26 | |
# Tue Dec 27 | |
# Wed Dec 28 | |
# Thu Dec 29 | |
# Fri Dec 30 | |
puts "YEAR | Summer Fri | Non-Summer Fri | Xmas Break | Holidays | Total" | |
puts "------------------------------------------------------------------" | |
(2022..2026).each do |year| | |
start = Date.new(year, 1, 1) | |
finish = Date.new(year, 12, 31) | |
schedule = PCOPTOSchedule.new(year) | |
all = Schedule.new(schedule.all).occurrences(start, finish) | |
summer_fridays = Schedule.new(schedule.summer_fridays).occurrences(start, finish) | |
non_summer_fridays = Schedule.new(schedule.non_summer_fridays_off).occurrences(start, finish) | |
christmas_break = Schedule.new(schedule.christmas_break).occurrences(start, finish) | |
holidays = Schedule.new(schedule.holidays).occurrences(start, finish) | |
puts "#{year} | #{"%10s" % summer_fridays.count} | #{"%14s" % non_summer_fridays.count} | #{"%10s" % christmas_break.count} | #{"%8s" % holidays.count} | #{"%5s" % all.count}" | |
end | |
# OUTPUT => | |
# YEAR | Summer Fri | Non-Summer Fri | Xmas Break | Holidays | Total | |
# ------------------------------------------------------------------ | |
# 2022 | 13 | 16 | 5 | 7 | 41 | |
# 2023 | 13 | 16 | 5 | 7 | 41 | |
# 2024 | 13 | 17 | 6 | 8 | 44 | |
# 2025 | 13 | 17 | 6 | 8 | 43 | |
# 2026 | 13 | 17 | 6 | 8 | 43 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment