File Coverage

blib/lib/Date/Holidays/US.pm
Criterion Covered Total %
statement 62 78 79.4
branch 82 98 83.6
condition 328 417 78.6
subroutine 8 8 100.0
pod 4 4 100.0
total 484 605 80.0


line stmt bran cond sub pod time code
1             package Date::Holidays::US;
2 17     17   1967981 use strict;
  17         35  
  17         688  
3 17     17   89 use warnings;
  17         81  
  17         1095  
4 17     17   93 use base qw{Exporter};
  17         31  
  17         3708  
5 17     17   9507 use POSIX; #strftime to calculate wday
  17         162378  
  17         131  
6              
7             our @EXPORT_OK = qw(is_holiday holidays is_us_holiday us_holidays);
8              
9             our $VERSION = '0.09';
10              
11             =head1 NAME
12              
13             Date::Holidays::US - Date::Holidays Adapter for US Federal holidays
14              
15             =head1 SYNOPSIS
16              
17             use Date::Holidays::US qw{is_holiday};
18             my $holiday_name = is_holiday($year, $month, $day);
19              
20             =head1 DESCRIPTION
21              
22             Date::Holidays Adapter for US Federal holidays back to 1880 with updates up to 2025.
23              
24             =head1 METHODS
25              
26             =head2 is_holiday
27              
28             Returns a holiday name or undef given three arguments (year, month, day).
29              
30             my ($year, $month, $day) = (2022, 6, 19);
31             use Date::Holidays::US qw{is_holiday};
32             my $holiday_name = is_holiday($year, $month, $day);
33             if (defined $holiday_name) {
34             print "Holiday: $holiday_name\n";
35             } else {
36             print "Not a US Holiday\n";
37             }
38              
39             =cut
40              
41             sub is_holiday {
42 6025     6025 1 3081127 my $year = shift;
43 6025         8602 my $month = shift;
44 6025         7677 my $day = shift;
45 6025         64002 my $wday = POSIX::strftime(qq{%w}, 0, 0, 0, $day, $month-1, $year-1900); #12:00 am (0-6 starting on Sunday)
46              
47             #Ref: https://sgp.fas.org/crs/misc/R41990.pdf - Federal Holidays: Evolution and Current Practices (July 1, 2021)
48              
49             #5 U.S. Code § 6103 - Holidays
50             #The history of federal holidays in the United States dates back to June 28, 1870
51 6025 100 100     433858 if ($year > 1870 and $month == 1 and $day == 1) {
    50 100        
    100 100        
    100 100        
    100 66        
    50 100        
    50 100        
    100 100        
    50 100        
    50 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 66        
    100 33        
    100 100        
    50 66        
    50 33        
    100 33        
    50 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 66        
    100 33        
    100 100        
    50 66        
    50 33        
    100 33        
    100 100        
    100 100        
    100 66        
    100 100        
    50 100        
    100 100        
    100 100        
    50 100        
    50 100        
    50 100        
    50 100        
    100 100        
    50 100        
    100 100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      66        
      66        
      33        
      33        
      0        
      100        
      66        
      66        
      66        
      33        
      33        
      0        
      100        
      66        
      66        
      33        
      33        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      66        
      33        
      33        
      100        
      66        
      66        
      33        
      33        
      100        
      100        
      100        
      100        
      33        
      33        
      33        
      33        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      33        
      33        
      100        
      100        
      100        
      100        
      100        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      100        
      100        
      33        
      33        
      100        
      100        
52 32         178 return q{New Year's Day}; #January 1
53             } elsif ($year > 1909 and $month == 1 and $day == 2 and $wday == 1) { #Executive Order 1076 (May 22, 1909)
54 0         0 return q{New Year's Day Observed}; #Monday after January 1
55              
56             #observed for the first time on January 20, 1986. - Pub.L. 98–399, 98 Stat. 1475, enacted November 2, 1983
57             } elsif ($year >= 1986 and $month == 1 and $day >= 15 and $day <= 21 and $wday == 1) {
58 26         118 return 'Birthday of Martin Luther King, Jr.' #the third Monday in January
59              
60             #Inauguration Day - Only DC Area
61             #Purposefully processed after MLK day. In the case of concurrency, MLK is returned.
62             } elsif ($year >= 1965 and $month == 1 and $day == 20 and $year % 4 == 1 and $wday != 0) { #5 U.S. Code 6103(c)
63 4         17 return 'Inauguration Day' #January 20 of each fourth year after 1965 unless Sunday
64             #Note 5 U.S. Code 6103(c) provides for Monday Jan 21 to be Inauguration Day but this package returns MKL day after 1985
65             } elsif ($year == 1985 and $month == 1 and $day == 21) { #5 U.S. Code 6103(c)
66 2         9 return 'Inauguration Day' #When January 20 ... falls on Sunday, the next succeeding day...
67              
68             # Washington's Birthday was celebrated on February 22 from 1879 until 1970.
69             # in 1968 the Uniform Monday Holiday Act moved it to the third Monday in February
70             # The Act was signed into law on June 1, 1968, and took effect on January 1, 1971.
71             } elsif ($year >= 1879 and $year < 1971 and $month == 2 and $day == 22) {
72 0         0 return q{Washington's Birthday}; #February 22 from 1879 until 1970
73             } elsif ($year > 1909 and $year < 1971 and $month == 2 and $day == 23 and $wday == 1) { #Executive Order 1076 (May 22, 1909)
74 0         0 return q{Washington's Birthday Observed}; #February 23 when Monday
75             } elsif ($year >= 1971 and $month == 2 and $day >= 15 and $day <= 21 and $wday == 1) { #Uniform Monday Holiday Act (June 28, 1968)
76 28         163 return q{Washington's Birthday}; #the third Monday in February
77              
78             # Memorial Day/Decoration Day
79             } elsif ($year >= 1888 and $year < 1971 and $month == 5 and $day == 30) {
80 0         0 return 'Decoration Day'; #May 30
81             } elsif ($year >= 1909 and $year < 1971 and $month == 6 and $day == 1 and $wday == 1) { #Executive Order 1076 (May 22, 1909)
82 0         0 return 'Decoration Day Observed'; #June 1st
83             } elsif ($year >= 1971 and $month == 5 and $day >= 25 and $day <= 31 and $wday == 1) { #Uniform Monday Holiday Act (June 28, 1968)
84 28         128 return 'Memorial Day'; #the last Monday in May
85              
86             #The day was first recognized as a federal holiday in June 2021, when President
87             #Joe Biden signed the Juneteenth National Independence Day Act into law.
88             } elsif ($year >= 2021 and $month == 6 and $day == 18 and $wday == 5) { #Executive Order 11582 (Feb. 11, 1971) "or any other calendar day designated as a holiday by Federal statute"
89 4         26 return 'Juneteenth National Independence Day Observed'; #Friday before June 19
90             } elsif ($year >= 2021 and $month == 6 and $day == 19) { #Juneteenth National Independence Day Act (June 17, 2021)
91 21         94 return 'Juneteenth National Independence Day'; #June 19
92             } elsif ($year >= 2021 and $month == 6 and $day == 20 and $wday == 1) { #Executive Order 11582 (Feb. 11, 1971)
93 4         16 return 'Juneteenth National Independence Day Observed'; #Monday after June 19
94              
95             #Independence Day
96              
97             } elsif ($year >= 1971 and $month == 7 and $day == 3 and $wday == 5) { #Executive Order 11582 (Feb. 11, 1971)
98 2         13 return 'Independence Day Observed'; #Friday before July 4
99             } elsif ($year >= 1870 and $month == 7 and $day == 4) {
100 27         132 return 'Independence Day'; #July 4
101             } elsif ($year >= 1909 and $month == 7 and $day == 5 and $wday == 1) { #Executive Order 1076 (May 22, 1909)
102 4         19 return 'Independence Day Observed'; #Monday after July 4
103              
104             ## Labor Day
105             # By 1894, thirty U.S. states were already officially celebrating Labor Day. In that year,
106             # Congress passed a bill recognizing the first Monday of September as Labor Day and making
107             # it an official federal holiday. President Grover Cleveland signed the bill into law on
108             # June 28.[15][4] The federal law, however, only made it a holiday for federal workers.
109              
110             } elsif ($year >= 1894 and $month == 9 and $day >= 1 and $day <= 7 and $wday == 1) {
111 28         151 return 'Labor Day'; #the first Monday in September
112              
113             ##Columbus Day
114             } elsif ($year >= 1971 and $month == 10 and $day >= 8 and $day <= 14 and $wday == 1) { #Uniform Monday Holiday Act
115 28         132 return 'Columbus Day'; #the second Monday in October
116              
117             ##Veterans Day (>1954)/Armistice Day (<1954)
118             #November 11 (1938 to 1970 and >1978)
119             #fourth Monday in October (1971 to 1977)
120              
121             } elsif ($year >= 1938 and $year < 1954 and $month == 11 and $day == 11) {
122 0         0 return 'Armistice Day'; #November 11
123             } elsif ($year >= 1945 and $year < 1954 and $month == 11 and $day == 12 and $wday == 1) { #Executive Order 9636 (October 3, 1945)
124 0         0 return 'Armistice Day Observed'; #Monday after November 11
125              
126             } elsif ($year >= 1954 and $year < 1971 and $month == 11 and $day == 11) {
127 1         5 return 'Veterans Day'; #November 11
128             } elsif ($year >= 1954 and $year < 1971 and $month == 11 and $day == 12 and $wday == 1) { #Executive Order 9636 (October 3, 1945)
129 0         0 return 'Veterans Day Observed'; #Monday after November 11
130              
131             } elsif ($year >= 1971 and $year < 1978 and $month == 10 and $day >= 22 and $day <= 28 and $wday == 1) {
132 7         44 return 'Veterans Day'; #fourth Monday in October
133              
134             } elsif ($year >= 1978 and $month == 11 and $day == 10 and $wday == 5) { #Executive Order 11582 (Feb. 11, 1971)
135 3         47 return 'Veterans Day Observed'; #Friday before November 11
136             } elsif ($year >= 1978 and $month == 11 and $day == 11) {
137 37         198 return 'Veterans Day'; #November 11
138             } elsif ($year >= 1978 and $month == 11 and $day == 12 and $wday == 1) { #Executive Order 11582 (Feb. 11, 1971)
139 4         17 return 'Veterans Day Observed'; #Monday after November 11
140              
141             ##Thanksgiving Day
142             #ref: https://library.law.unc.edu/2024/11/the-history-of-the-thanksgiving-holiday/
143             } elsif ($year >= 1870 and $year < 1939 and $month == 11 and $day >= 24 and $day <= 30 and $wday == 4) { #Law 1870 but date set by President
144 2         12 return 'Thanksgiving Day'; #the last Thursday in November precedent from Abraham Lincoln
145             } elsif ($year >= 1939 and $year <= 1941 and $month == 11 and $day >= 17 and $day <= 23 and $wday == 4) { #FDR "Franksgiving"
146 3         19 return 'Thanksgiving Day'; #the second to last Thursday by FDR 1939-1941
147             } elsif ($year > 1941 and $month == 11 and $day >= 22 and $day <= 28 and $wday == 4) { #Law signed December 26, 1941
148 30         203 return 'Thanksgiving Day'; #the fourth Thursday in November.
149              
150             ##Day before Christmas Day
151             #Ref: https://thehill.com/homenews/administration/5655984-trump-declares-christmas-holidays/
152             } elsif ($year == 2019 and $month == 12 and $day == 24) {
153 0         0 return 'Day before Christmas Day'; #Executive Order on Providing for the Closing of Executive Departments and Agencies of the Federal Government on December 24, 2019
154             } elsif ($year == 2020 and $month == 12 and $day == 24) {
155 0         0 return 'Day before Christmas Day'; #Executive Order on Providing for the Closing of Executive Departments and Agencies of the Federal Government on December 24, 2020
156             } elsif ($year == 2024 and $month == 12 and $day == 24) {
157 2         12 return 'Day before Christmas Day'; #Executive Order on Providing for the Closing of Executive Departments and Agencies of the Federal Government on December 24, 2024
158             } elsif ($year == 2025 and $month == 12 and $day == 24) {
159 2         9 return 'Day before Christmas Day'; #PROVIDING FOR THE CLOSING OF EXECUTIVE DEPARTMENTS AND AGENCIES OF THE FEDERAL GOVERNMENT ON DECEMBER 24, 2025, AND DECEMBER 26, 2025
160              
161             ##Christmas Day
162             } elsif ($year >= 1971 and $month == 12 and $day == 24 and $wday == 5) { #Executive Order 11582 (Feb. 11, 1971)
163 4         29 return 'Christmas Day Observed'; #Friday before December 25
164             } elsif ($year >= 1870 and $month == 12 and $day == 25) {
165 27         136 return 'Christmas Day'; #December 25
166             } elsif ($year >= 1909 and $month == 12 and $day == 26 and $wday == 1) { #Executive Order 1076 (May 22, 1909)
167 4         8 return 'Christmas Day Observed'; #Monday after December 25
168              
169             } elsif ($year == 2014 and $month == 12 and $day == 26) {
170 0         0 return 'Day following Christmas Day'; #Executive Order -- Closing of Executive Departments and Agencies of the Federal Government on Friday, December 26, 2014
171             } elsif ($year == 2025 and $month == 12 and $day == 26) {
172 2         11 return 'Day following Christmas Day'; #PROVIDING FOR THE CLOSING OF EXECUTIVE DEPARTMENTS AND AGENCIES OF THE FEDERAL GOVERNMENT ON DECEMBER 24, 2025, AND DECEMBER 26, 2025
173              
174             } elsif ($year >= 1971 and $month == 12 and $day == 31 and $wday == 5) { #Executive Order 11582 (Feb. 11, 1971)
175 4         26 return q{New Year's Day Observed}; #Friday before January 1
176              
177             # Beginning with the death of President Kennedy in 1963, the incumbent President has issued an Executive order closing
178             # Government offices throughout the world as a mark of respect upon the death of each President or former President.
179             } elsif ($year == 1963 and $month == 11 and $day == 25) {
180             #35 November 25, 1963, National Day of Mourning for President John F. Kennedy
181 0         0 return 'National Day of Mourning for President John F. Kennedy';
182             } elsif ($year == 1973 and $month == 1 and $day == 25) {
183             #36 January 25 1973, National Day of Mourning for President Lyndon B. Johnson
184 0         0 return 'National Day of Mourning for President Lyndon B. Johnson';
185             } elsif ($year == 1994 and $month == 4 and $day == 27) {
186             #37 April 27, 1994, National Day of Mourning for President Richard Nixon
187 0         0 return 'National Day of Mourning for President Richard Nixon';
188             } elsif ($year == 2007 and $month == 1 and $day == 2) {
189             #38 January 2, 2007 National Day of Mourning for President Gerald R. Ford
190 0         0 return 'National Day of Mourning for President Gerald R. Ford';
191             } elsif ($year == 2025 and $month == 1 and $day == 9) {
192             #39 January 9, 2025 National Day of Mourning for President Jimmy Carter
193 2         8 return 'National Day of Mourning for President James Earl Carter, Jr.';
194             } elsif ($year == 2004 and $month == 6 and $day == 11) {
195             #40 June 11, 2004, National Day of Mourning for President Ronald W. Reagan
196 0         0 return 'National Day of Mourning for President Ronald W. Reagan';
197             } elsif ($year == 2018 and $month == 12 and $day == 5) {
198             #41 December 5, 2018, National Day of Mourning for President George H. W. Bush
199 2         10 return 'National Day of Mourning for President George H. W. Bush';
200              
201             } else {
202 5651         15143 return undef;
203             }
204             }
205              
206             =head2 is_us_holiday
207              
208             Wrapper around is_holiday function per the API specification. See L
209              
210             =cut
211              
212 4     4 1 1144 sub is_us_holiday {return is_holiday(@_)};
213              
214             =head2 holidays
215              
216             Returns a hash reference containing all of the holidays in the specified year. The keys for the returned hash reference are the dates where 2-digit month and 2-digit day are concatenated.
217              
218             use Date::Holidays::US qw{holidays};
219             my $year = 2022;
220             my $holidays_href = holidays($year);
221             foreach my $key (sort keys %$holidays_href) { #e.g. "0101", "0619","0704"
222             my ($month, $day) = $key =~ m/\A([0-9]{2})([0-9]{2})\Z/;
223             my $name = $holidays_href->{$key};
224             print "Year: $year, Month: $month, Day: $day, Name: $name\n";
225             }
226              
227             =cut
228              
229             sub holidays {
230 16     16 1 49 my $year = shift;
231 16         71 my %holidays = ();
232 16         567 my $time = POSIX::mktime(0, 0, 0, 1, 0, $year-1900); #Jan 1st
233 16         50 while (1) {
234 5858         128706 my ($year_calculated, $month, $day) = split /-/, POSIX::strftime("%Y-%m-%d", POSIX::gmtime($time));
235 5858 100       139985 last if $year_calculated > $year;
236 5842         8724 my $date = $month . $day;
237 5842         10446 my $name = is_holiday($year, $month, $day);
238 5842 100       11918 $holidays{$date} = $name if defined($name);
239 5842         8415 $time += 86400; #Note: Not all US days have 24 hours but we are using UTC for the date component
240             }
241 16         199 return \%holidays;
242             }
243              
244             =head2 us_holidays
245              
246             Wrapper around holidays function per the API specification. See L
247              
248             =cut
249              
250 2     2 1 6 sub us_holidays {return holidays(@_)};
251              
252             =head1 TODO
253              
254             =head1 SEE ALSO
255              
256             L (wrapper), L (defunct), L (e.g., Valentine's Day, Mother's Day, etc.)
257              
258             =head1 AUTHOR
259              
260             Michael R. Davis, MRDVT
261              
262             =head1 COPYRIGHT AND LICENSE
263              
264             Copyright (C) 2025 by Michael R. Davis
265              
266             MIT License
267              
268             =cut
269              
270             1;