1 package FS::part_event::Condition::holiday;
4 use base qw( FS::part_event::Condition );
6 use DateTime::Format::ICal;
9 # rules lifted from DateTime::Event::Holiday::US,
10 # but their list is unordered, and contains duplicates and frivolous holidays
11 # it's better for future development for us to use our own hard-coded list,
12 # and the actual code beyond the list is just trivial use of DateTime::Format::ICal
14 tie my %holidays, 'Tie::IxHash',
16 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=1' }, # January 1
17 'Birthday of Martin Luther King, Jr.'
18 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=1;BYDAY=3mo' }, # Third Monday in January
19 'Washington\'s Birthday'
20 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=2;BYDAY=3mo' }, # Third Monday in February
22 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=5;BYDAY=-1mo' }, # Last Monday in May
24 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=7;BYMONTHDAY=4' }, # July 4
26 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=1mo' }, # First Monday in September
28 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=2mo' }, # Second Monday in October
30 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=11;BYMONTHDAY=11' }, # November 11
32 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=4th' }, # Fourth Thursday in November
34 => { 'rule' => 'RRULE:FREQ=YEARLY;BYMONTH=12;BYMONTHDAY=25' }, # December 25
37 my $oneday = DateTime::Duration->new(days => 1);
40 "Do not run on holidays",
46 label => 'Do not run on',
47 type => 'checkbox-multiple',
48 options => [ keys %holidays ],
49 option_labels => { map { $_ => $_ } keys %holidays },
50 default_value => { map { $_ => 1 } keys %holidays }
56 my( $self, $object, %opt ) = @_;
57 my $today = DateTime->from_epoch(
58 epoch => $opt{'time'} || time,
60 )->truncate( to => 'day' );
62 # if fri/mon, also check sat/sun respectively
63 # federal holidays on weekends "move" to nearest weekday
64 # eg Christmas 2016 is Mon Dec 26
65 # we'll check both, so eg both Dec 25 & 26 are holidays in 2016
67 if ($today->day_of_week == 1) {
68 $offday = $today->clone->subtract_duration($oneday);
69 } elsif ($today->day_of_week == 5) {
70 $offday = $today->clone->add_duration($oneday);
73 foreach my $holiday (keys %{$self->option('holidays')}) {
74 $holidays{$holiday}{'set'} ||=
75 DateTime::Format::ICal->parse_recurrence(
76 'recurrence' => $holidays{$holiday}{'rule'}
78 my $set = $holidays{$holiday}{'set'};
80 if $set->contains($today) or $offday && $set->contains($offday);