File Coverage

blib/lib/Date/Holidays/CA.pm
Criterion Covered Total %
statement 146 164 89.0
branch 27 38 71.0
condition 4 6 66.6
subroutine 36 43 83.7
pod 9 9 100.0
total 222 260 85.3


line stmt bran cond sub pod time code
1             # Date::Holidays::CA
2             #
3             # This module is free software! You can copy, modify, share and
4             # distribute it under the same license as Perl itself.
5             #
6             # Rick Scott
7             # rick@shadowspar.dyndns.org
8             #
9             # Sun Oct 25 14:32:20 EDT 2009
10              
11              
12 8     8   2059092 use strict;
  8         20  
  8         387  
13 8     8   87 use warnings;
  8         21  
  8         900  
14             package Date::Holidays::CA;
15             our $VERSION = '0.07'; # VERSION
16              
17             # ABSTRACT: Date::Holidays::CA determines public holidays for Canadian jurisdictions
18              
19 8     8   192 use 5.006;
  8         39  
20 8     8   78 use Carp;
  8         41  
  8         740  
21 8     8   7472 use DateTime;
  8         4602123  
  8         506  
22 8     8   5827 use DateTime::Event::Easter;
  8         575718  
  8         30908  
23              
24              
25             require Exporter;
26              
27             our @ISA = qw(Exporter);
28              
29             our %EXPORT_TAGS = ( 'all' => [ qw(
30             is_holiday
31             is_ca_holiday
32             is_holiday_dt
33             holidays
34             ca_holidays
35             holidays_dt
36             ) ] );
37              
38             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
39             our @EXPORT = qw();
40              
41              
42              
43             sub new {
44 36     36 1 1664304 my $class = shift;
45 36         78 my $args_ref = shift;
46              
47 36         218 my $self = {
48             province => 'CA',
49             language => 'EN/FR',
50             };
51              
52 36         98 bless $self, $class;
53 36         182 $self->set($args_ref);
54 33         88 return $self;
55             }
56              
57              
58              
59              
60             sub get {
61 4 100   4 1 139 croak 'Wrong number of arguments to get()' if scalar @_ != 2;
62 3         42 my $self = shift;
63 3         9 my $field = shift;
64              
65 3 100       19 if (exists $self->{$field}) {
66 2         11 return $self->{$field};
67             }
68              
69 1         13 croak "No such field $field";
70             }
71              
72              
73              
74             sub set {
75 38 50   38 1 249 croak 'Wrong number of arguments to set()' if scalar @_ != 2;
76 38         77 my $self = shift;
77 38         82 my $args_ref = shift;
78              
79 38         75 while (my ($field, $value) = each %{$args_ref}) {
  88         344  
80 55         78 my $new_value;
81              
82 55 50       181 if ($new_value = _validate($field, $value)) {
83 50         135 $self->{$field} = $new_value;
84             }
85             }
86              
87 33         77 return 1;
88             }
89              
90              
91              
92             sub is_holiday {
93 5 100   5 1 39 return ( is_ca_holiday(@_) ? 1 : 0 );
94             }
95              
96              
97              
98             sub is_ca_holiday {
99 5     5 1 7 my $self;
100 5 50       18 $self = shift if (ref $_[0]); # invoked in OO style
101              
102 5         7 my $year = shift;
103 5         7 my $month = shift;
104 5         9 my $day = shift;
105 5         9 my $options = shift;
106              
107 5 100 66     19 if (defined $options and defined $options->{province} ) {
108             $self->{province} = $options->{province}
109 1         3 }
110 5         20 _assert_valid_date($year, $month, $day);
111              
112 5 50       1887 unless (defined $self) {
113 0         0 $self = Date::Holidays::CA->new($options);
114             }
115              
116 5         23 my $calendar = $self->_generate_calendar($year);
117              
118             # assumption: there is only one holiday for any given day.
119 5         61 while (my ($holiday_name, $holiday_dt) = each %$calendar) {
120 56 100 66     265 if ($month == $holiday_dt->month and $day == $holiday_dt->day) {
121 3         147 return $holiday_name;
122             }
123             }
124              
125 2         78 return;
126             }
127              
128              
129              
130             sub is_holiday_dt {
131 0     0 1 0 my ($self, $dt, $options);
132              
133             my @args = map {
134 0 0       0 ref $_ eq 'DateTime' ? ($_->year, $_->month, $_->day) : $_
  0         0  
135             } @_;
136              
137 0         0 return is_holiday(@args);
138             }
139              
140              
141              
142             sub holidays {
143 22     22 1 187 my $calendar = holidays_dt(@_);
144              
145             my %holidays = map {
146 22         117 $calendar->{$_}->strftime('%m%d') => $_
  266         10581  
147             } keys %$calendar;
148              
149 22         1375 return \%holidays;
150             }
151              
152              
153              
154             sub ca_holidays {
155 0     0 1 0 return holidays(@_);
156             }
157              
158              
159              
160             sub holidays_dt {
161 22     22 1 36 my $self;
162 22 100       65 $self = shift if (ref $_[0]); # invoked in OO style
163              
164 22         50 my $year = shift;
165 22         39 my $args_ref = shift;
166              
167 22 100       59 unless (defined $self) {
168 3         19 $self = Date::Holidays::CA->new($args_ref);
169             }
170              
171 22         91 return $self->_generate_calendar($year);
172             }
173              
174              
175              
176             ### internal functions
177              
178             my @VALID_PROVINCES = qw{ CA AB BC MB NB NL NS NT NU ON PE QC SK YT };
179             my @VALID_LANGUAGES = qw{ EN/FR FR/EN EN FR };
180             my %VALUES_FOR = (
181             'PROVINCE' => \@VALID_PROVINCES,
182             'LANGUAGE' => \@VALID_LANGUAGES,
183             );
184              
185              
186             # _validate($field, $value)
187             #
188             # accepts: field name ( 'province' | 'language' )
189             # possible value for that field
190             # returns: if $value is a valid value for $field, canonicalize and return
191             # it (eg, upcase it).
192             # if $value isn't valid, throw an exception.
193              
194              
195             sub _validate {
196 55     55   108 my $field = shift;
197 55         102 my $value = shift;
198              
199 55         78 my @valid_values = @{ $VALUES_FOR{uc($field)} };
  55         404  
200 53 50       125 croak "No such field $field" unless @valid_values;
201              
202 53         94 foreach my $valid_value (@valid_values) {
203 167 100       527 return uc($value) if uc($value) eq $valid_value;
204             }
205              
206 3         44 croak "$value is not a recognized setting for $field";
207             }
208              
209              
210             # _assert_valid_date
211             #
212             # accepts: numeric year, month, day
213             # returns: nothing
214             #
215             # throw an exception on invalid dates; otherwise, do nothing.
216              
217             sub _assert_valid_date {
218 5     5   15 my ($year, $month, $day) = @_;
219              
220             # DateTime does date validation when a DT object is created.
221 5         33 my $dt = DateTime->new(
222             year => $year, month => $month, day => $day,
223             );
224             }
225              
226              
227             # format: each holiday is listed as a triplet:
228             # * function that returns a DateTime object for that holiday
229             # * english name
230             # * french name
231             # listing the names each time makes for a verbose list with a lot of
232             # repetition; unfortunately different provinces sometimes call different
233             # holidays different things.
234              
235             my %HOLIDAYS_FOR = (
236             CA => [
237             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
238             \&_good_friday, 'Good Friday', 'Vendredi Saint',
239             \&_easter_monday, 'Easter Monday', 'Lundi de Pâques',
240             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
241             \&_canada_day, 'Canada Day', 'Fête du Canada',
242             \&_civic_holiday, 'Civic Holiday', 'Fête Civique',
243             \&_labour_day, 'Labour Day', 'Fête du Travail',
244             \&_truth_reconciliation_day, 'National Day for Truth and Reconciliation', 'Journée nationale de la vérité et de la réconciliation',
245             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
246             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
247             \&_christmas_day, 'Christmas Day', 'Noël',
248             \&_boxing_day, 'Boxing Day', 'Lendemain de Noël',
249             ],
250              
251             AB => [
252             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
253             \&_family_day, 'Family Day', 'Jour de la Famille',
254             \&_good_friday, 'Good Friday', 'Vendredi Saint',
255             \&_easter_monday, 'Easter Monday', 'Lundi de Pâques',
256             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
257             \&_canada_day, 'Canada Day', 'Fête du Canada',
258             \&_civic_holiday, 'Alberta Heritage Day', 'Jour d\'Héritage d\'Alberta',
259             \&_labour_day, 'Labour Day', 'Fête du Travail',
260             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
261             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
262             \&_christmas_day, 'Christmas Day', 'Noël',
263             \&_boxing_day, 'Boxing Day', 'Lendemain de Noël',
264             ],
265              
266             BC => [
267             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
268             \&_family_day, 'Family Day', 'Jour de la Famille',
269             \&_good_friday, 'Good Friday', 'Vendredi Saint',
270             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
271             \&_canada_day, 'Canada Day', 'Fête du Canada',
272             \&_civic_holiday, 'BC Day', 'Fête de la Colombie-Britannique',
273             \&_labour_day, 'Labour Day', 'Fête du Travail',
274             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
275             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
276             \&_christmas_day, 'Christmas Day', 'Noël',
277             ],
278              
279             MB => [
280             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
281             \&_family_day, 'Louis Riel Day', 'Jour de Louis Riel',
282             \&_good_friday, 'Good Friday', 'Vendredi Saint',
283             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
284             \&_canada_day, 'Canada Day', 'Fête du Canada',
285             \&_labour_day, 'Labour Day', 'Fête du Travail',
286             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
287             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
288             \&_christmas_day, 'Christmas Day', 'Noël',
289             ],
290              
291             NB => [
292             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
293             \&_family_day, 'Family Day', 'Le jour de la Famille',
294             \&_good_friday, 'Good Friday', 'Vendredi Saint',
295             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
296             \&_canada_day, 'Canada Day', 'Fête du Canada',
297             \&_civic_holiday, 'New Brunswick Day', 'Fête du Nouveau-Brunswick',
298             \&_labour_day, 'Labour Day', 'Fête du Travail',
299             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
300             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
301             \&_christmas_day, 'Christmas Day', 'Noël',
302             \&_boxing_day, 'Boxing Day', 'Lendemain de Noël',
303             ],
304              
305             NL => [
306             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
307             \&_st_patricks_day, 'St Patrick\'s Day', 'La Saint-Patrick',
308             \&_good_friday, 'Good Friday', 'Vendredi Saint',
309             \&_st_georges_day, 'St George\'s Day', 'La Saint-Georges',
310             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
311             \&_nl_discovery_day, 'June Day', 'Jour de juin',
312             \&_canada_day, 'Memorial Day', 'Fête du Canada',
313             \&_orangemens_day, 'Orangemen\'s Day', 'Fête des Orangistes',
314             \&_labour_day, 'Labour Day', 'Fête du Travail',
315             \&_truth_reconciliation_day, 'National Day for Truth and Reconciliation', 'Journée nationale de la vérité et de la réconciliation',
316             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
317             \&_remembrance_day, 'Armistice Day (Remembrance Day)', 'Jour du Souvenir',
318             \&_christmas_day, 'Christmas Day', 'Noël',
319             \&_boxing_day, 'Boxing Day', 'Lendemain de Noël',
320             ],
321              
322             NS => [
323             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
324             \&_family_day, 'Nova Scotia Heritage Day', 'Le jour de la Famille',
325             \&_good_friday, 'Good Friday', 'Vendredi Saint',
326             \&_canada_day, 'Canada Day', 'Fête du Canada',
327             \&_labour_day, 'Labour Day', 'Fête du Travail',
328             \&_christmas_day, 'Christmas Day', 'Noël',
329             ],
330              
331             NT => [
332             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
333             \&_good_friday, 'Good Friday', 'Vendredi Saint',
334             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
335             \&_national_aboriginal_day, 'National Aboriginal Day', 'Journée Nationale des Autochtones',
336             \&_canada_day, 'Canada Day', 'Fête du Canada',
337             \&_civic_holiday, 'Civic Holiday', 'Fête Civique',
338             \&_labour_day, 'Labour Day', 'Fête du Travail',
339             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
340             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
341             \&_christmas_day, 'Christmas Day', 'Noël',
342             \&_boxing_day, 'Boxing Day', 'Lendemain de Noël',
343             ],
344              
345             NU => [
346             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
347             \&_good_friday, 'Good Friday', 'Vendredi Saint',
348             \&_easter_monday, 'Easter Monday', 'Lundi de Pâques',
349             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
350             \&_canada_day, 'Canada Day', 'Fête du Canada',
351             \&_nunavut_day, 'Nunavut Day', 'Jour du Nunavut',
352             \&_civic_holiday, 'Civic Holiday', 'Congé Statutaire',
353             \&_labour_day, 'Labour Day', 'Fête du Travail',
354             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
355             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
356             \&_christmas_day, 'Christmas Day', 'Noël',
357             \&_boxing_day, 'Boxing Day', 'Lendemain de Noël',
358             ],
359              
360             ON => [
361             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
362             \&_family_day, 'Family Day', 'Jour de la Famille',
363             \&_good_friday, 'Good Friday', 'Vendredi Saint',
364             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
365             \&_canada_day, 'Canada Day', 'Fête du Canada',
366             \&_civic_holiday, 'Civic Holiday', 'Congé Statutaire',
367             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
368             \&_christmas_day, 'Christmas Day', 'Noël',
369             \&_boxing_day, 'Boxing Day', 'Lendemain de Noël',
370             ],
371              
372             PE => [
373             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
374             \&_family_day, 'Islander Day', 'Fête des Insulaires',
375             \&_good_friday, 'Good Friday', 'Vendredi Saint',
376             \&_canada_day, 'Canada Day', 'Fête du Canada',
377             \&_labour_day, 'Labour Day', 'Fête du Travail',
378             \&_truth_reconciliation_day, 'National Day for Truth and Reconciliation', 'Journée nationale de la vérité et de la réconciliation',
379             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
380             \&_christmas_day, 'Christmas Day', 'Noël',
381             ],
382              
383             QC => [
384             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
385             \&_good_friday, 'Good Friday', 'Vendredi Saint',
386             \&_victoria_day, 'National Patriot\'s Day', 'Journée Nationale des Patriotes / Fête de la Reine',
387             \&_st_john_baptiste_day, 'Saint-Jean-Baptiste Day', 'La Saint-Jean',
388             \&_canada_day, 'Canada Day', 'Fête du Canada',
389             \&_labour_day, 'Labour Day', 'Fête du Travail',
390             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
391             \&_christmas_day, 'Christmas Day', 'Noël',
392             ],
393              
394             SK => [
395             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
396             \&_family_day, 'Family Day', 'Jour de la Famille',
397             \&_good_friday, 'Good Friday', 'Vendredi Saint',
398             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
399             \&_canada_day, 'Canada Day', 'Fête du Canada',
400             \&_civic_holiday, 'Saskatchewan Day', 'Fête de la Saskatchewan',
401             \&_labour_day, 'Labour Day', 'Fête du Travail',
402             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
403             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
404             \&_christmas_day, 'Christmas Day', 'Noël',
405             ],
406              
407             YT => [
408             \&_new_years_day, 'New Year\'s Day', 'Jour de l\'An',
409             \&_good_friday, 'Good Friday', 'Vendredi Saint',
410             \&_victoria_day, 'Victoria Day', 'Fête de la Reine',
411             \&_national_aboriginal_day, 'National Aboriginal Day', 'Journée Nationale des Autochtones',
412             \&_canada_day, 'Canada Day', 'Fête du Canada',
413             \&_yt_discovery_day, 'Discovery Day', 'Jour du découverte',
414             \&_labour_day, 'Labour Day', 'Fête du Travail',
415             \&_thanksgiving_day, 'Thanksgiving Day', 'Action de Grâce',
416             \&_remembrance_day, 'Remembrance Day', 'Jour du Souvenir',
417             \&_christmas_day, 'Christmas Day', 'Noël',
418             ],
419             );
420              
421              
422             # _generate_calendar
423             #
424             # accepts: numeric year
425             # returns: hashref (string $holiday_name => DateTime $holiday_dt)
426             #
427             # generate a holiday calendar for the specified year -- a hash mapping
428             # holiday names to datetime objects.
429             sub _generate_calendar {
430 27     27   52 my $self = shift;
431 27         58 my $year = shift;
432 27         42 my $calendar = {};
433              
434 27         57 my @holiday_list = @{ $HOLIDAYS_FOR{$self->{'province'}} };
  27         309  
435              
436 27         101 while(@holiday_list) {
437 332         967 my $holiday_dt = (shift @holiday_list)->($year); # fn invokation
438 332         193713 my $name_en = shift @holiday_list;
439 332         644 my $name_fr = shift @holiday_list;
440              
441             my $holiday_name =
442             $self->{'language'} eq 'EN' ? $name_en
443             : $self->{'language'} eq 'FR' ? $name_fr
444             : $self->{'language'} eq 'EN/FR' ? "$name_en/$name_fr"
445 332 0       1022 : $self->{'language'} eq 'FR/EN' ? "$name_fr/$name_en"
    50          
    50          
    100          
446             : "$name_en/$name_fr"; # sane default, should never get here
447              
448 332         1999 $calendar->{$holiday_name} = $holiday_dt;
449             }
450              
451 27         159 return $calendar;
452             }
453              
454             ### toolkit functions
455              
456             # _nth_monday
457             #
458             # accepts: year, month, ordinal of which monday to find
459             # returns: numeric date of the requested monday
460             #
461             # find the day of week for the first day of the month,
462             # calculate the number of day to skip forward to hit the first monday,
463             # then skip forward the requisite number of weeks.
464             #
465             # in general, the number of days we need to skip forward from the
466             # first of the month is (target_dow - first_of_month_dow) % 7
467              
468             sub _nth_monday {
469 84     84   327150 my $year = shift;
470 84         123 my $month = shift;
471 84         116 my $n = shift;
472              
473 84         249 my $first_of_month = DateTime->new(
474             year => $year,
475             month => $month,
476             day => 1,
477             );
478              
479 84         21694 my $date_of_first_monday = 1 + ( (1 - $first_of_month->dow()) % 7);
480              
481 84         717 return $date_of_first_monday + 7 * ($n - 1);
482             }
483              
484             # _nearest_monday
485             #
486             # accepts: year, month, day for a given date
487             # returns: day of the nearest monday to that date
488              
489             sub _nearest_monday {
490 31     31   5550 my $year = shift;
491 31         38 my $month = shift;
492 31         38 my $day = shift;
493              
494 31         89 my $dt = DateTime->new(year => $year, month => $month, day => $day);
495              
496 31         7609 my $delta_days = ((4 - $dt->dow) % 7) - 3;
497              
498 31         287 return $day + $delta_days;
499             }
500              
501             # _round_to_monday
502             #
503             # accepts: year, month, day for a given date
504             # returns: day unless day is a Sat/Sun, then the next Mon
505             sub _round_to_monday {
506 104     104   295 my $year = shift;
507 104         137 my $month = shift;
508 104         142 my $day = shift;
509              
510 104         342 my $dt = DateTime->new(year => $year, month => $month, day => $day);
511              
512 104         26464 my $delta_days = (8 - $dt->dow());
513 104 100       505 $delta_days = 0 if($delta_days > 2);
514              
515 104         584 return $day + $delta_days;
516             }
517              
518              
519             ### holiday date calculating functions
520             #
521             # these all take one parameter ($year) and return a DateTime object
522             # specifying the day of the holiday for that year.
523              
524             sub _new_years_day {
525 27     27   54 my $year = shift;
526              
527 27         165 return DateTime->new(
528             year => $year,
529             month => 1,
530             day => 1,
531             );
532             }
533              
534             sub _family_day {
535 2     2   4 my $year = shift;
536              
537 2         5 return DateTime->new(
538             year => $year,
539             month => 2,
540             day => _nth_monday($year, 2, 3),
541             );
542             }
543              
544             sub _st_patricks_day {
545 6     6   9 my $year = shift;
546              
547 6         20 return DateTime->new(
548             year => $year,
549             month => 3,
550             day => _nearest_monday($year, 3, 17),
551             );
552             }
553              
554             sub _good_friday {
555 27     27   48 my $year = shift;
556              
557 27         105 my $dt = DateTime->new( year => $year, month => 1, day => 1 );
558 27         7088 my $event = DateTime::Event::Easter->new(day => 'Good Friday');
559 27         6197 return $event->following($dt);
560             }
561              
562             sub _easter_sunday {
563 0     0   0 my $year = shift;
564              
565 0         0 my $dt = DateTime->new( year => $year, month => 1, day => 1 );
566 0         0 my $event = DateTime::Event::Easter->new(day => 'Easter Sunday');
567 0         0 return $event->following($dt);
568             }
569              
570             sub _easter_monday {
571 19     19   40 my $year = shift;
572              
573 19         112 my $dt = DateTime->new( year => $year, month => 1, day => 1 );
574 19         9516 my $event = DateTime::Event::Easter->new(day => +1);
575 19         3610 return $event->following($dt);
576             }
577              
578             sub _st_georges_day {
579 6     6   12 my $year = shift;
580              
581 6         42 return DateTime->new(
582             year => $year,
583             month => 4,
584             day => _nearest_monday($year, 4, 23),
585             );
586             }
587              
588             sub _victoria_day {
589 27     27   47 my $year = shift;
590              
591 27         133 my $may_24 = DateTime->new(
592             year => $year,
593             month => 5,
594             day => 24,
595             );
596              
597 27         7132 return DateTime->new(
598             year => $year,
599             month => 5,
600             day => 25 - $may_24->dow()
601             );
602             }
603              
604             sub _national_aboriginal_day {
605 0     0   0 my $year = shift;
606              
607 0         0 return DateTime->new(
608             year => $year,
609             month => 6,
610             day => 21,
611             );
612             }
613              
614             sub _st_john_baptiste_day {
615 0     0   0 my $year = shift;
616              
617 0         0 return DateTime->new(
618             year => $year,
619             month => 6,
620             day => 24,
621             );
622             }
623              
624             sub _nl_discovery_day {
625 6     6   8 my $year = shift;
626              
627 6         19 return DateTime->new(
628             year => $year,
629             month => 6,
630             day => _nearest_monday($year, 6, 24),
631             );
632             }
633              
634             sub _canada_day {
635 27     27   59 my $year = shift;
636              
637             # Canada Day is July 1st unless it is a Sunday. If it is a
638             # Sunday then Monday is the Holiday. Further complicated by
639             # the fact that July 1st on a Saturday is usually a holiday
640             # for people who work week days. So we round to Monday if
641             # July 1st is a Saturday or Sunday.
642 27         147 return DateTime->new(
643             year => $year,
644             month => 7,
645             day => _round_to_monday($year, 7, 1)
646             );
647             }
648              
649             sub _nunavut_day {
650 0     0   0 my $year = shift;
651              
652 0         0 return DateTime->new(
653             year => $year,
654             month => 7,
655             day => 9,
656             );
657             }
658              
659             sub _orangemens_day {
660 6     6   17 my $year = shift;
661              
662 6         20 return DateTime->new(
663             year => $year,
664             month => 7,
665             day => _nearest_monday($year, 7, 12),
666             );
667             }
668              
669             sub _civic_holiday {
670 21     21   41 my $year = shift;
671              
672 21         100 return DateTime->new(
673             year => $year,
674             month => 8,
675             day => _nth_monday($year, 8, 1),
676             );
677             }
678              
679             sub _yt_discovery_day {
680 0     0   0 my $year = shift;
681              
682 0         0 return DateTime->new(
683             year => $year,
684             month => 8,
685             day => _nth_monday($year, 8, 3),
686             );
687             }
688              
689             sub _labour_day {
690 27     27   41 my $year = shift;
691              
692 27         95 return DateTime->new(
693             year => $year,
694             month => 9,
695             day => _nth_monday($year, 9, 1),
696             );
697             }
698              
699             sub _truth_reconciliation_day {
700 25     25   41 my $year = shift;
701              
702 25         81 return DateTime->new(
703             year => $year,
704             month => 9,
705             day => 30,
706             );
707             }
708              
709             sub _thanksgiving_day {
710 27     27   47 my $year = shift;
711              
712 27         83 return DateTime->new(
713             year => $year,
714             month => 10,
715             day => _nth_monday($year, 10, 2),
716             );
717             }
718              
719             sub _remembrance_day {
720 27     27   55 my $year = shift;
721              
722 27         115 return DateTime->new(
723             year => $year,
724             month => 11,
725             day => 11,
726             );
727             }
728              
729             sub _christmas_day {
730 52     52   87 my $year = shift;
731              
732             # Christmas Day is December 25th but the Holiday is normally
733             # observed on the Monday if it occurs on a week end day
734 52         153 return DateTime->new(
735             year => $year,
736             month => 12,
737             day => _round_to_monday($year, 12, 25)
738             );
739             }
740              
741             sub _boxing_day {
742 25     25   46 my $year = shift;
743              
744             # Normally the day after the Christmas Holiday, except if
745             # Christmas is on Friday, then our holiday is on Monday
746 25         84 my $result = _christmas_day($year)->add(days=>1);
747 25         32090 return DateTime->new(
748             year => $year,
749             month => 12,
750             day => _round_to_monday($year, 12, $result->day())
751             );
752             }
753              
754              
755             1; # all's well
756              
757             __END__
758              
759             =pod
760              
761             =encoding UTF-8
762              
763             =head1 NAME
764              
765             Date::Holidays::CA - Date::Holidays::CA determines public holidays for Canadian jurisdictions
766              
767             =head1 VERSION
768              
769             version 0.07
770              
771             =head1 SYNOPSIS
772              
773             # procedural approach
774              
775             use Date::Holidays::CA qw(:all);
776              
777             my ($year, $month, $day) = (localtime)[ 5, 4, 3 ];
778             $year += 1900;
779             $month += 1;
780              
781             print 'Woot!' if is_holiday($year, $month, $day, {province => 'BC'});
782              
783             my $calendar = holidays($year, {province => 'BC'});
784             #returns a hash reference
785             print $calendar->{'0701'}; # "Canada Day/Fête du Canada"
786              
787              
788             # object-oriented approach
789              
790             use DateTime;
791             use Date::Holidays::CA;
792              
793             my $dhc = Date::Holidays::CA->new({ province => 'QC' });
794              
795             print 'Woot!' if $dhc->is_holiday(DateTime->today);
796              
797             my $calendar = $dhc->holidays_dt(DateTime->today->year);
798             print join keys %$calendar, "\n"; # lists holiday names for QC
799              
800             =head1 DESCRIPTION
801              
802             Date::Holidays::CA determines public holidays for Canadian jurisdictions.
803             Its interface is a superset of that provided by Date::Holidays -- read
804             on for details.
805              
806             =head1 NAME
807              
808             Date::Holidays::CA - Holidays for Canadian locales
809              
810             =head1 FUNCTIONS / METHODS
811              
812             =head2 Class Methods
813              
814             =head3 new()
815              
816             Create a new Date::Holidays::CA object. Parameters should be given as
817             a hashref of key-value pairs.
818              
819             my $dhc = Date::Holidays::CA->new(); # defaults
820              
821             my $dhc = Date::Holidays::CA->new({
822             province => 'ON', language => 'EN'
823             });
824              
825             Two parameters can be specified: B<province> and B<language>.
826              
827             =head4 Province
828              
829             =over
830              
831             =item * CA
832              
833             Canadian Federal holidays (the default).
834              
835             =item * AB
836              
837             Alberta
838              
839             =item * BC
840              
841             British Columbia
842              
843             =item * MB
844              
845             Manitoba
846              
847             =item * NB
848              
849             New Brunswick
850              
851             =item * NL
852              
853             Newfoundland & Labrador
854              
855             =item * NS
856              
857             Nova Scotia
858              
859             =item * NT
860              
861             Northwest Territories
862              
863             =item * NU
864              
865             Nunavut
866              
867             =item * ON
868              
869             Ontario
870              
871             =item * PE
872              
873             Prince Edward Island
874              
875             =item * QC
876              
877             Quebec
878              
879             =item * SK
880              
881             Saskatchewan
882              
883             =item * YT
884              
885             Yukon Territory
886              
887             =back
888              
889             =head4 Language
890              
891             =over
892              
893             =item * EN/FR
894              
895             English text followed by French text.
896              
897             =item * FR/EN
898              
899             French text followed by English text.
900              
901             =item * EN
902              
903             English text only.
904              
905             =item * FR
906              
907             French text only.
908              
909             =back
910              
911             =head2 Object Methods
912              
913             =head3 get()
914              
915             Retrieve fields of a Date::Holidays::CA object.
916              
917             $prov = $dhc->('province');
918              
919             =head3 set()
920              
921             Alter fields of a Date::Holidays::CA object. Specify parameters just
922             as with new().
923              
924             $dhc->set({province => 'QC', language => 'FR/EN'});
925              
926             =head2 Combination Methods
927              
928             These methods are callable in either object-oriented or procedural style.
929              
930             =head3 is_holiday()
931              
932             For a given year, month (1-12) and day (1-31), return 1 if the given
933             day is a holiday; 0 if not. When using procedural calling style, an
934             additional hashref of options can be specified.
935              
936             $holiday_p = is_holiday($year, $month, $day);
937              
938             $holiday_p = is_holiday($year, $month, $day, {
939             province => 'BC', language => 'EN'
940             });
941              
942             $holiday_p = $dhc->is_holiday($year, $month, $day);
943              
944             =head3 is_ca_holiday()
945              
946             Similar to C<is_holiday>. Return the name of the holiday occurring on
947             the specified date if there is one; C<undef> if there isn't.
948              
949             print $dhc->is_ca_holiday(2001, 1, 1); # "New Year's Day"
950              
951             =head3 is_holiday_dt()
952              
953             As is_holiday, but accepts a DateTime object in place of a numeric year,
954             month, and day.
955              
956             $holiday_p = is_holiday($dt, {province => 'SK', language => 'EN'});
957              
958             $holiday_p = $dhc->is_holiday($dt);
959              
960             =head3 holidays()
961              
962             For the given year, return a hashref containing all the holidays for
963             that year. The keys are the date of the holiday in C<mmdd> format
964             (eg '1225' for December 25); the values are the holiday names.
965              
966             my $calendar = holidays($year, {province => 'MB', language => 'EN'});
967             #returns a hash reference
968             print $calendar->{'0701'}; # "Canada Day"
969              
970             my $calendar = $dhc->holidays($year);
971             #returns a hash reference
972             print $calendar->{'1111'}; # "Remembrance Day"
973              
974             =head3 ca_holidays()
975              
976             Same as C<holidays()>.
977              
978             =head3 holidays_dt()
979              
980             Similar to C<holidays()>, after a fashion: returns a hashref with the
981             holiday names as the keys and DateTime objects as the values.
982              
983             my $calendar = $dhc->holidays_dt($year);
984              
985             =head1 SPECIFICATIONS
986              
987             The following holidays are recognized:
988              
989             =over
990              
991             =item I<New Year's Day>
992              
993             January 1.
994              
995             =item I<Islander Day>
996              
997             PE. Originally added in 2009 as the second Monday in February, this
998             holiday will be revised to the third Monday in February starting in
999             2010. I<This module shows Islander Day as falling on the third Monday>
1000             -- see the I<KNOWN BUGS> section.
1001              
1002             =item I<Family Day / Louis Riel Day>
1003              
1004             The Third Monday of February is Family Day in AB, SK, and ON, and
1005             Louis Riel Day in MB.
1006              
1007             =item I<St. Patrick's Day>
1008              
1009             NL. Nearest Monday to March 17.
1010              
1011             =item I<Good Friday>
1012              
1013             The Friday falling before Easter Sunday.
1014              
1015             =item I<Easter Monday>
1016              
1017             CA, QC. The Monday following Easter Sunday.
1018              
1019             =item I<St. Patrick's Day>
1020              
1021             NL. Nearest Monday to April 23.
1022              
1023             =item I<Victoria Day>
1024              
1025             Monday falling on or before May 24.
1026              
1027             =item I<National Aboriginal Day>
1028              
1029             NT. June 21.
1030              
1031             =item I<Saint-Jean-Baptiste Day>
1032              
1033             QC. June 24.
1034              
1035             =item I<Discovery Day>
1036              
1037             There are actually two holidays named "Discovery Day". Newfoundland observes
1038             Discovery Day on the Monday nearest June 24, and the Yukon observes Discovery
1039             Day on the third Monday of August.
1040              
1041             =item I<Canada Day>
1042              
1043             July 1.
1044              
1045             =item I<Nunavut Day>
1046              
1047             NU. July 9.
1048              
1049             =item I<Orangemen's Day>
1050              
1051             NL. Monday nearest July 12.
1052              
1053             =item I<Civic Holiday>
1054              
1055             AB, BC, MB, NB, NS, NT, NU, ON, PE, SK (that is to say, not CA, NL, QC, or YT).
1056             First Monday of August.
1057              
1058             Different provinces call this holiday different things -- eg "BC Day" in
1059             British Columbia, "Alberta Heritage Day" in Alberta, "Natal Day" in
1060             Nova Scotia and PEI, and so forth.
1061              
1062             =item I<Labour Day>
1063              
1064             First Monday of September.
1065              
1066             =item I<Thanksgiving Day>
1067              
1068             Second Monday of October.
1069              
1070             =item I<Remembrance Day>
1071              
1072             All but ON and QC. November 11.
1073              
1074             =item I<Christmas Day>
1075              
1076             December 25.
1077              
1078             =item I<Boxing Day>
1079              
1080             CA, NL, NT, NU, ON, PE. December 26.
1081              
1082             =item I<National Day for Truth and Reconciliation>
1083              
1084             CA and PE. September 30.
1085              
1086             =back
1087              
1088             =head1 REFERENCES
1089              
1090             L<http://en.wikipedia.org/wiki/Public_holidays_in_Canada>
1091              
1092             L<http://www.craigmarlatt.com/canada/symbols_facts&lists/holidays.html>
1093              
1094             L<http://www.craigmarlatt.com/canada/symbols_facts&lists/august_holiday.html>
1095              
1096             L<http://geonames.nrcan.gc.ca/info/prov_abr_e.php> (Provincial abbreviations)
1097              
1098             A grillion government web pages listing official statutory holidays, all of
1099             which seem to have gone offline or moved.
1100              
1101             L<http://www.gov.mb.ca/labour/standards/doc,louis-riel_day,factsheet.html> (MB's Louis Riel Day)
1102              
1103             L<http://www.theguardian.pe.ca/index.cfm?sid=244766&sc=98> (PEI's Islander Day)
1104              
1105             =head1 KNOWN BUGS
1106              
1107             B<Historical holidays are not supported>; the current set of holidays
1108             will be projected into the past or future. For instance, Louis Riel Day
1109             was added as a Manitoba holiday in 2008, but if you use this module to
1110             generate a holiday list for 2007, Louis Riel Day will be present.
1111             Also, PEI's Islander Day was first observed on the second Monday of 2009,
1112             but will subsequently be observed on the third Monday of the month; this
1113             module will always show it as occurring on the third Monday.
1114             This will be addressed if there is demand to do so.
1115              
1116             B<Several lesser holidays are not yet implemented>:
1117              
1118             =over
1119              
1120             =item I<Calgary Stampede>
1121              
1122             I am told that the morning of the Stampede Parade is commonly given as
1123             a half-day holiday by employers within the city of Calgary, but I
1124             haven't been able to verify this, nor does there seem to be a way to
1125             mathematically calculate when parade day will be scheduled.
1126              
1127             =item I<St Johns Regatta Day>
1128              
1129             Regatta Day is a municipal holiday in St Johns, NL, and it is scheduled for
1130             the first Wednesday in August. However, if the weather on Quidi Vidi Lake
1131             does not look promising on Regatta morning, the event I<(and the attendant
1132             holiday)> are postponed until the next suitable day.
1133              
1134             How to programatically determine the day of this holiday has not yet been
1135             satisfactorily ascertained. L<Acme::Test::Weather|Acme::Test::Weather>
1136             has been considered.
1137              
1138             =item I<Gold Cup and Saucer Day (PEI)>
1139              
1140             Some few employees apparently get the day of the Gold Cup and Saucer
1141             harness race as a holiday, but I haven't been able to independently
1142             verify this.
1143              
1144             =item I<Construction Holiday (Quebec)>
1145              
1146             In Quebec, the vast majority of the construction industry gets the last
1147             full two weeks of July off, and it's also a popular time for other folks
1148             to book vacation. Since this technically only applies to a single
1149             industry, I haven't added it to this module, but I will if there is
1150             sufficient demand.
1151              
1152             =back
1153              
1154             =head1 HELP WANTED
1155              
1156             As you can see from the I<KNOWN BUGS> section above, our holiday structure
1157             can be fairly baroque. Different provinces and cities get different
1158             holidays; sometimes these are paid statutory holidays that are
1159             included in Employment Standards legislation; other times they are
1160             unofficial holidays that are given by convention and codified only in
1161             collective agreements and municipal by-laws. Thus, it's hard to know
1162             what's commonly considered "a holiday" in the various regions of the
1163             country without actually having lived and worked there.
1164              
1165             I only have direct experience with British Columbia and Ontario; my
1166             impression of what folks in other provinces consider to be a holiday
1167             is based on research on the WWW. I've tried to define a holiday as
1168             any day when "the majority of the workforce either get the day off
1169             (paid or unpaid) or receive pay in lieu." If the holidays list in this
1170             module doesn't accurately reflect the application of that definition
1171             to your region of Canada, I'd like to hear about it.
1172              
1173             Assistance with French translations of the holiday names and this
1174             documentation is most welcome. My French isn't all that great,
1175             but I'm happy to learn. =)
1176              
1177             Finally, I'd appreciate an email from any users of this module.
1178             I'm curious to know who has picked it up, and any feedback you might
1179             have will shape its future development.
1180              
1181             =head1 CAVEATS
1182              
1183             For reasons outlined in the two sections above, please be forewarned
1184             that what days are considered holidays may change with versions of
1185             the module.
1186              
1187             Note that the holiday is intended to reflect the actual observed
1188             holiday not the date of the actual day. Christmas Day, for example,
1189             is December 25th but the actual day of the holiday may be two days
1190             later, the Monday, if Christmas Day is on a Saturday.
1191              
1192             =head1 SEE ALSO
1193              
1194             =over
1195              
1196             =item Date::Holidays
1197              
1198             =item DateTime
1199              
1200             =item DateTime::Event::Easter
1201              
1202             =back
1203              
1204             =head1 AUTHOR
1205              
1206             Rick Scott <rick@cpan.org>
1207              
1208             =head1 COPYRIGHT AND LICENSE
1209              
1210             This software is copyright (c) 2006-2022 by Rick Scott.
1211              
1212             This is free software; you can redistribute it and/or modify it under
1213             the same terms as the Perl 5 programming language system itself.
1214              
1215             =cut