forked from 18F/us-federal-holidays
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
204 lines (170 loc) · 5.46 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
const dayjs = require("dayjs");
const utcPlugin = require("dayjs/plugin/utc");
dayjs.extend(utcPlugin);
const getDateFor = ({ day = 1, month, year }) =>{
let year_str = year.toString()
let day_str = day.toString().padStart(2,'0')
let month_str = month.toString().padStart(2,'0')
return dayjs(`${year_str}-${month_str}-${day_str}`, "YYYY-MM-DD");
}
const getNthDayOf = (n, day, month, year) => {
let result = dayjs(getDateFor({ month, year })).day(day);
// dayjs.day(x) can return a time in the past (relative to the date being
// operated on), because it returns a time from within the operand date's
// current week. E.g.:
//
// date = July 1, 2021 # Thursday
// dayjs(date).day(0) # Get Sunday
// # returns June 27, 2021
if (result.month() !== month - 1) {
result = result.add(1, "week");
}
result = result.add(n - 1, "week");
return result;
};
const getLastDayOf = (day, month, year) => {
let year_str = year.toString()
let day_str = day.toString().padStart(2,'0')
let month_str = month.toString().padStart(2,'0')
const daysInMonth = dayjs(getDateFor({ month, year })).daysInMonth();
const lastDayOfMonth = dayjs(`${year_str}-${month_str}-${daysInMonth}`, "YYYY-MM-DD");
let result = lastDayOfMonth.day(day_str);
// See above comment for more details. TL;DR is dayjs.day(x) is not
// constrained to the same month as the operand object.
if (result.month() !== month - 1) {
result = result.subtract(1, "week");
}
return result;
};
const allFederalHolidaysForYear = (
year = new Date().getFullYear(),
{ shiftSaturdayHolidays = true, shiftSundayHolidays = true } = {}
) => {
const holidays = [];
// New Year's Day
holidays.push({
name: `New Year's Day`,
date: getDateFor({ day: 1, month: 1, year })
});
// Birthday of Martin Luther King, Jr.
// Third Monday of January; fun fact: actual birthday is January 15
holidays.push({
name: `Birthday of Martin Luther King, Jr.`,
date: getNthDayOf(3, 1, 1, year)
});
// Washington's Birthday
// Third Monday of February; fun fact: actual birthday is February 22
// Fun fact 2: officially "Washington's Birthday," not "President's Day"
holidays.push({
name: `Washington's Birthday`,
alsoObservedAs: "Presidents' Day",
date: getNthDayOf(3, 1, 2, year)
});
// Memorial Day
// Last Monday of May
holidays.push({
name: `Memorial Day`,
date: getLastDayOf(1, 5, year)
});
if (year > 2020) {
// Juneteenth
holidays.push({
name: `Juneteenth National Independence Day`,
date: getDateFor({ day: 19, month: 6, year })
});
}
// Independence Day
holidays.push({
name: `Independence Day`,
date: getDateFor({ day: 4, month: 7, year })
});
// Labor Day
// First Monday in September
holidays.push({
name: `Labor Day`,
date: getNthDayOf(1, 1, 9, year)
});
// Columbus Day
// Second Monday in October
holidays.push({
name: `Columbus Day`,
alsoObservedAs: "Indigenous Peoples' Day",
date: getNthDayOf(2, 1, 10, year)
});
// Veterans Day
holidays.push({
name: `Veterans Day`,
date: getDateFor({ day: 11, month: 11, year })
});
// Thanksgiving Day
// Fourth Thursday of November
holidays.push({
name: `Thanksgiving Day`,
date: getNthDayOf(4, 4, 11, year)
});
// Christmas Day
holidays.push({
name: `Christmas Day`,
date: getDateFor({ day: 25, month: 12, year })
});
return holidays.map(holiday => {
let date = dayjs(holiday.date);
if (date.day() === 0 && shiftSundayHolidays) {
// Actual holiday falls on Sunday. Shift the observed date forward to
// Monday.
date = date.add(1, "day");
}
if (date.day() === 6 && shiftSaturdayHolidays) {
// Actual holiday falls on Saturday. Shift the observed date backward
// to Friday.
date = date.subtract(1, "day");
}
return {
name: holiday.name,
alsoObservedAs: holiday.alsoObservedAs,
date: date.toDate(),
dateString: date.format("YYYY-MM-DD")
};
});
};
const isAHoliday = (
date = new Date(),
{ shiftSaturdayHolidays = true, shiftSundayHolidays = true, utc = false } = {}
) => {
const newDate = utc ? dayjs.utc(date) : dayjs(date);
const year = newDate.year();
const shift = { shiftSaturdayHolidays, shiftSundayHolidays };
// Get the holidays this year, plus check if New Year's Day of next year is
// observed on December 31 and if so, add it to this year's list.
const allForYear = allFederalHolidaysForYear(year, shift);
const nextYear = allFederalHolidaysForYear(year + 1, shift);
allForYear.push(nextYear[0]);
// If any dates in this year's holiday list match the one passed in, then
// the passed-in date is a holiday. Otherwise, it is not.
return allForYear.some(
holiday => holiday.dateString === newDate.format("YYYY-MM-DD")
);
};
const getOneYearFromNow = () => {
const future = new Date();
future.setUTCFullYear(future.getUTCFullYear() + 1);
return future;
};
const federalHolidaysInRange = (
startDate = new Date(),
endDate = getOneYearFromNow(),
options
) => {
const startYear = startDate.getFullYear();
const endYear = endDate.getFullYear();
const candidates = [];
for (let year = startYear; year <= endYear; year += 1) {
candidates.push(...allFederalHolidaysForYear(year, options));
}
return candidates.filter(h => h.date >= startDate && h.date <= endDate);
};
module.exports = {
isAHoliday,
allForYear: allFederalHolidaysForYear,
inRange: federalHolidaysInRange
};