diff --git a/holidays_us.go b/holidays_us.go new file mode 100644 index 0000000..bb27e58 --- /dev/null +++ b/holidays_us.go @@ -0,0 +1,45 @@ +package holidays + +import "time" + +func init() { + registerHolidayDataSource("us", holidaysUSNational{}) +} + +type holidaysUSNational struct{} + +func (h holidaysUSNational) GetIncludes() []string { return []string{} } +func (h holidaysUSNational) GetHolidays(year int) []Holiday { + return []Holiday{ + newHoliday("New Year's Day", map[string]string{}, h.fixWeekend(dateFromNumbers(year, 1, 1))), // 1st January + newHoliday("Birthday of Martin Luther King, Jr.", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 1, 15), time.Monday)), // Third Monday in January + newHoliday("Washington's Birthday", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 2, 15), time.Monday)), // Third Monday in February + newHoliday("Memorial Day", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 5, 25), time.Monday)), // Last Monday in May + newHoliday("Independence Day", map[string]string{}, h.fixWeekend(dateFromNumbers(year, 7, 4))), // 4 July + newHoliday("Labor Day", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 9, 1), time.Monday)), // First Monday in September + newHoliday("Columbus Day", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 10, 8), time.Monday)), // Second Monday in October + newHoliday("Veterans Day", map[string]string{}, h.fixWeekend(dateFromNumbers(year, 11, 11))), // 11 November + newHoliday("Thanksgiving Day", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 11, 23), time.Thursday)), // Fourth Thursday in November + newHoliday("Christmas Day", map[string]string{}, h.fixWeekend(dateFromNumbers(year, 12, 25))), // 25 December + } +} + +func (h holidaysUSNational) findNextWeekday(in time.Time, wd time.Weekday) time.Time { + for in.Weekday() != wd { + in = in.Add(1 * 24 * time.Hour) + } + return in +} + +// If a holiday falls on a Saturday it is celebrated the preceding Friday; +// if a holiday falls on a Sunday it is celebrated the following Monday. +func (h holidaysUSNational) fixWeekend(in time.Time) time.Time { + switch in.Weekday() { + case time.Saturday: + return in.Add(-1 * 24 * time.Hour) + case time.Sunday: + return in.Add(1 * 24 * time.Hour) + default: + return in + } +} diff --git a/holidays_us_test.go b/holidays_us_test.go new file mode 100644 index 0000000..86acc6d --- /dev/null +++ b/holidays_us_test.go @@ -0,0 +1,71 @@ +package holidays + +import ( + "testing" + "time" +) + +func TestHolidaysUS(t *testing.T) { + // List of known dates according to US Office of Personnel Management + // https://www.opm.gov/policy-data-oversight/snow-dismissal-procedures/federal-holidays/ + + knownDates := map[int]map[string]time.Time{ + 2011: map[string]time.Time{ + "New Year's Day": dateFromNumbers(2010, 12, 31), // 1 = Saturday... What the hell are you doing?!? + "Birthday of Martin Luther King, Jr.": dateFromNumbers(2011, 1, 17), + "Washington's Birthday": dateFromNumbers(2011, 2, 21), + "Memorial Day": dateFromNumbers(2011, 5, 30), + "Independence Day": dateFromNumbers(2011, 7, 4), + "Labor Day": dateFromNumbers(2011, 9, 5), + "Columbus Day": dateFromNumbers(2011, 10, 10), + "Veterans Day": dateFromNumbers(2011, 11, 11), + "Thanksgiving Day": dateFromNumbers(2011, 11, 24), + "Christmas Day": dateFromNumbers(2011, 12, 26), // 25 = Sunday + }, + 2014: map[string]time.Time{ + "New Year's Day": dateFromNumbers(2014, 1, 1), + "Birthday of Martin Luther King, Jr.": dateFromNumbers(2014, 1, 20), + "Washington's Birthday": dateFromNumbers(2014, 2, 17), + "Memorial Day": dateFromNumbers(2014, 5, 26), + "Independence Day": dateFromNumbers(2014, 7, 4), + "Labor Day": dateFromNumbers(2014, 9, 1), + "Columbus Day": dateFromNumbers(2014, 10, 13), + "Veterans Day": dateFromNumbers(2014, 11, 11), + "Thanksgiving Day": dateFromNumbers(2014, 11, 27), + "Christmas Day": dateFromNumbers(2014, 12, 25), + }, + 2017: map[string]time.Time{ + "New Year's Day": dateFromNumbers(2017, 1, 2), // 1 = Sunday + "Birthday of Martin Luther King, Jr.": dateFromNumbers(2017, 1, 16), + "Washington's Birthday": dateFromNumbers(2017, 2, 20), + "Memorial Day": dateFromNumbers(2017, 5, 29), + "Independence Day": dateFromNumbers(2017, 7, 4), + "Labor Day": dateFromNumbers(2017, 9, 4), + "Columbus Day": dateFromNumbers(2017, 10, 9), + "Veterans Day": dateFromNumbers(2017, 11, 10), // 11 = Saturday + "Thanksgiving Day": dateFromNumbers(2017, 11, 23), + "Christmas Day": dateFromNumbers(2017, 12, 25), + }, + } + + for year, knownHolidays := range knownDates { + generatedHolidays, err := GetHolidays("us", year) + if err != nil { + t.Fatalf("[US] Could not load holidays for %d: %s", year, err) + } + + for name, date := range knownHolidays { + + found := false + for _, hd := range generatedHolidays { + if hd.Name == name && hd.ParsedDate == date { + found = true + } + } + if !found { + t.Errorf("[US] Did not find %q on %s.", name, date.Format("2006-01-02")) + } + + } + } +}