File Coverage

blib/lib/Date/Holidays/NZ.pm
Criterion Covered Total %
statement 121 126 96.0
branch 53 66 80.3
condition 21 36 58.3
subroutine 17 17 100.0
pod 4 10 40.0
total 216 255 84.7


line stmt bran cond sub pod time code
1             package Date::Holidays::NZ;
2 1     1   26657 use strict;
  1         5  
  1         48  
3 1     1   10 use base qw(Exporter);
  1         4  
  1         245  
4              
5             our $SET;
6             BEGIN {
7 1     1   6 eval { require Set::Scalar };
  1         318  
8 1 50       11 if ($@) {
9 1         778 require Set::Object;
10 1 50       9069 die "Need at least Set::Object 1.09"
11             if ($Set::Object::VERSION < 1.09);
12 1         35 $SET = "Set::Object";
13             } else {
14 0         0 $SET = "Set::Scalar";
15             }
16             }
17              
18 1     1   9 use vars qw($VERSION @EXPORT @EXPORT_OK);
  1         2  
  1         316  
19             $VERSION = '1.03';
20             @EXPORT = qw(is_nz_holiday nz_holidays nz_regional_day nz_holiday_date);
21             @EXPORT_OK = qw(%HOLIDAYS $NATIONAL_HOLIDAYS %regions
22             nz_region_code nz_region_name %holiday_cache);
23              
24             my $AD = "Anniversary Day";
25              
26             our %HOLIDAYS
27             = (
28             # holidays which are fixed, but unless you would "normally have
29             # worked on that day", then they are moved to the next working
30             # day
31             "New Years Day" => "0101+",
32             "Day after New Years Day" => '0102+',
33             "Christmas Day" => "1225+",
34             "Boxing Day" => '1226+',
35             "Waitangi Day" => "0206+",
36             "ANZAC Day" => "0425+",
37              
38             # other nationwide holidays
39             "Easter Monday" => "Easter + 1day",
40             "Good Friday" => "Easter - 2days",
41             "Queens Birthday" => '1st Monday in June',
42             "Labour Day" => "4th Monday in October",
43              
44             # Anniversary days - these are the official dates, but regional
45             # authorities and sometimes district councils pick the actual
46             # dates.
47             "Auckland $AD" => "Closest Monday to 0129",
48              
49             #"Taranaki $AD" => "Closest Monday to 0331",
50             # Moves to 2nd Monday in March to avoid Easter.
51             "Taranaki $AD" => "2nd Monday in March",
52              
53             #"Hawkes' Bay $AD" => "Closest Monday to 1101",
54             # Moved to Friday before Labour Day.
55             "Hawkes' Bay $AD" => "4th Monday in October - 3days",
56              
57             "Wellington $AD" => "Closest Monday to 0122",
58              
59             # "Marlborough $AD" => "Closest Monday to 1101",
60             # Observed 1st Monday after Labour Day.
61             "Marlborough $AD" => "4th Monday in October + 7days",
62              
63             "Nelson $AD" => "Closest Monday to 0201",
64              
65             #"Westland $AD" => "Closest Monday to 1201",
66             "Westland $AD" => "1st monday in december",
67              
68             # ``Varies throughout Westland, but Greymouth observes the
69             # official day.'' - that's the West Coast for you
70              
71             "Otago $AD" => "Closest Monday to 0323",
72             "Southland $AD" => "Closest Monday to 0117",
73             "Chatham Islands $AD" => "Closest Monday to 1130",
74              
75             # oddballs
76             #"Canterbury $AD" => "Closest Monday to 1216",
77             "Dominion Day" => "4th Monday in September",
78             "Christchurch Show Day" => "1st Tuesday in November + 10d",
79             );
80              
81             our %CHANGESET_2014 = (
82             # Pre-mondayisation dates for Waitangi and ANZAC days
83             "Waitangi Day" => "0206",
84             "ANZAC Day" => "0425",
85             );
86              
87             our $NATIONAL_HOLIDAYS =
88             $SET->new( "Waitangi Day",
89             "ANZAC Day",
90             "New Years Day",
91             "Day after New Years Day",
92             "Christmas Day",
93             "Boxing Day",
94             "Easter Monday",
95             "Good Friday",
96             "Queens Birthday",
97             "Labour Day",
98             );
99              
100             our %holiday_aliases =
101             ( "Birthday of the Reigning Sovereign" => "Queens Birthday",
102             );
103              
104             # These are Census 2001 region codes.
105             our %regions =
106             (
107             1 => "Northland",
108             2 => "Auckland",
109             3 => "Waikato",
110             4 => "Bay of Plenty",
111             5 => "Gisbourne",
112             6 => "Hawkes' Bay",
113             7 => "Taranaki",
114             8 => "Manuwatu-Wanganui",
115             9 => "Wellington",
116             12 => "West Coast",
117             13 => "Canterbury",
118             -13 => "Canterbury (South)", # tsk! naughty use of sign bit
119             14 => "Otago",
120             15 => "Southland",
121             16 => "Tasman",
122             17 => "Nelson",
123             18 => "Marlborough",
124             99 => "Outside Regional Authority (incl. Chatham Is)",
125             );
126             our %rev_regions;
127              
128             # which anniversary days are followed by each region is largely
129             # educated guesses, please e-mail samv@cpan.org if this list is
130             # incorrect.
131             our %FOLLOW = ( 1 => 2, 3 => 2, 4 => 5, 5 => 6,
132             8 => 9, 12 => "Westland",
133             13 => "Christchurch Show Day",
134             -13 => "Dominion Day",
135             16 => 17,
136             99 => "Chatham Islands",
137             );
138              
139 1     1   8 use Scalar::Util qw(looks_like_number);
  1         2  
  1         148  
140 1     1   8 use Carp qw(croak);
  1         2  
  1         566  
141              
142             sub rev_regions {
143 36     36 0 91 my $region_name = shift;
144 36 50       109 unless ( $rev_regions{Auckland} ) {
145 36         222 %rev_regions = map { lc($_) } reverse %regions;
  1296         2920  
146             }
147 36   66     657 return $rev_regions{lc($region_name)}
148             || croak "`$region_name' is not a valid NZ region name";
149             }
150              
151             sub nz_region_name {
152 10     10 0 903 my $region = shift;
153 10 50       29 return undef unless defined $region;
154 10 100       48 if ( looks_like_number($region) ) {
155 6   66     121 return $regions{$region}
156             || croak "no such NZ region code `$region'";
157             } else {
158 4         11 return $regions{rev_regions($region)};
159             }
160             }
161              
162             sub nz_region_code {
163 34     34 0 1080 my $region = shift;
164 34 50       99 return undef unless defined $region;
165 34 100       122 if ( looks_like_number($region) ) {
166 5 100       111 exists $regions{$region}
167             or croak "no such NZ region code `$region'";
168 4         17 return $region;
169             } else {
170 29         90 return rev_regions($region);
171             }
172             }
173              
174             # try to guess the regional day observed from the region
175             sub nz_regional_day {
176 93 50   93 1 4660 my $label = shift or return undef;
177              
178 93 100 100     986 if ( !looks_like_number($label) and
179             exists $HOLIDAYS{$label." $AD"} ) {
180 65         242 return "$label $AD";
181             }
182 28         84 my $region = nz_region_code($label);
183 28         64 my $followed;
184 28 100       87 if ( $followed = $FOLLOW{$region} ) {
185 27 100       122 if ( looks_like_number($followed) ) {
186 3         13 $followed = nz_region_name($followed);
187             }
188             }
189             else {
190 1         4 $followed = nz_region_name($region);
191             }
192 28 100       116 if ( $followed !~ /Day$/ ) {
193 12         44 $followed .= " $AD";
194             }
195 28         91 return $followed;
196             }
197              
198             sub check_falling_on {
199 52     52 0 158 my ($h, $year, $date) = @_;
200              
201 52         200 my $falls_on = UnixDate($year.$date, "%w");
202 52 100       39240 if ( $falls_on >= 6 ) {
203 12         52 my $name = delete $h->{$date};
204             #print STDERR "Blast, $name falls on a ".UnixDate($year.$date, "%A"). " ($falls_on)\n";
205 12         44 my $add = ($falls_on + 2) % 7;
206             #print STDERR "Adding $add days to it.\n";
207 12         80 my $to_fall_on = UnixDate(DateCalc($year.$date, "+${add}d"), "%m%d");
208             #print STDERR "Trying $to_fall_on instead.\n";
209 12   100     39490 while ( exists $h->{$to_fall_on} or
210             UnixDate($year.$to_fall_on, "%w") >= 6
211             ) {
212 7         4265 $to_fall_on = UnixDate(DateCalc($year.$to_fall_on, "+1d"), "%m%d");
213             #print STDERR "That's no bloody good, trying $to_fall_on instead.\n";
214             }
215             #print STDERR "Settling on $to_fall_on ($name Holiday)\n";
216 12         29926 $h->{$to_fall_on} = "$name Holiday";
217             }
218             }
219              
220 1     1   463 use Date::Manip qw(DateCalc ParseDate UnixDate ParseRecur);
  1         126927  
  1         1034  
221              
222             sub interpret_date {
223 208     208 0 15437 my $year = shift;
224 208         393 my $value = shift;
225              
226 208         442 my ($date, $add);
227 208 100       1728 if ( $value =~ m/^(\d\d)(\d\d)\+?$/ ) {
    100          
    100          
    50          
228 67         226 return $value;
229             }
230             elsif ( $value =~ m/^(\d+(?:st|nd|rd|th) \w+day in \w+)(.*)$/i) {
231 66         279 (my $spec, $add) = ($1, $2);
232 66         347 $date = ParseDate($spec. " $year");
233             }
234             elsif ( $value =~ m/^Closest (\w+day) to (\d\d)(\d\d)(.*)$/i) {
235             #print STDERR "**** $value($year):\n";
236 49         260 (my ($day, $month, $dom), $add) = ($1, $2, $3, $4);
237 49         286 $date = ParseDate("$year-$month-$dom");
238 49         38687 my $wanted = UnixDate($day, "%w");
239 49         74006 my $got = UnixDate($date, "%w");
240             #print STDERR " $year-$month-$dom is a ".UnixDate($date, "%A")
241             #." ($got)\n";
242 49 100       29733 if ( my $diff = ($wanted - $got + 7) % 7 ) {
243             #print STDERR " difference is $diff days\n";
244 43 100       174 if ( $diff < 4 ) {
245             #print STDERR "Adding $diff days\n";
246 20         135 $date = DateCalc($date, "+${diff}d");
247             } else {
248 23         64 $diff = 7 - $diff;
249             #print STDERR "Subtracting $diff days\n";
250 23         157 $date = DateCalc($date, "-${diff}d");
251             }
252             } else {
253             #print STDERR "Which is good\n";
254             }
255             }
256             elsif ( $value =~ m/^Easter(.*)$/i) {
257 26         186 ($date) = ParseRecur("*$year:0:0:0:0:0:0*EASTER");
258 26         53065 $add = $1;
259             }
260 141 100       190328 $date = DateCalc($date, "$add") if $add;
261 141         170864 return UnixDate($date, "%m%d");
262             }
263              
264             sub nz_stat_holidays {
265 11     11 0 32 my $year = shift;
266              
267             # merge the old definitions of Waitangi day and ANZAC day if year is pre-mondayisation
268 11         239 my %holidays = %HOLIDAYS;
269 11 100       59 if ( $year < 2014 ){
270 7         105 %holidays = ( %holidays, %CHANGESET_2014 );
271             }
272              
273             # build the relative dates
274 11         43 my (%h, @tentative);
275 11         135 foreach my $holiday ($NATIONAL_HOLIDAYS->members) {
276              
277 110 50 50     402 my $when = interpret_date($year, $holidays{$holiday}||die)
278             or die "couldn't interpret $year, $holidays{$holiday}";
279              
280 110 100       26218 if ( $when =~ s/\+// ) {
281 52         133 push @tentative, $when;
282             }
283 110         350 $h{$when} = $holiday;
284             }
285 11         115 for my $date ( sort { $a <=> $b } @tentative ) {
  79         196  
286 52         194 check_falling_on(\%h, $year, $date);
287             }
288              
289 11         194 return \%h;
290             }
291              
292             our %regional_holiday_cache
293             = # exceptions
294             ( "2004/Westland $AD" => "1129",
295             "2008/Hawkes' Bay $AD" => "1017",
296             "2008/Otago $AD" => "0325",
297             );
298              
299             our %holiday_cache;
300              
301             sub nz_holidays {
302 85     85 1 25452 my ($year, $region) = @_;
303              
304 85   66     413 my $hols = $holiday_cache{$year} ||= nz_stat_holidays($year);
305              
306 85 50       217 if ( $region ) {
307 0         0 my $rd = nz_regional_day($region);
308              
309             my $when = $regional_holiday_cache{"$year/$rd"} ||=
310 0   0     0 (interpret_date($year, $HOLIDAYS{$rd}||die)
      0        
311             or die "couldn't interpret $year, $HOLIDAYS{$rd}");
312              
313 0         0 $hols = { %$hols, $when => "$rd" };
314             }
315              
316 85         195 return $hols;
317             }
318              
319             sub is_nz_holiday {
320 168     168 1 157114 my ($year, $month, $day, $region) = @_;
321 168         935 my $mmdd = sprintf("%.2d%.2d", $month, $day);
322              
323 168   33     670 my $hols = $holiday_cache{$year} ||= nz_stat_holidays($year);
324              
325 168 50       525 if ( exists $hols->{$mmdd} ) {
326 0         0 return $hols->{$mmdd};
327             }
328              
329 168 100       457 if ( $region ) {
330 84         293 my $rd = nz_regional_day($region);
331              
332             my $when = $regional_holiday_cache{"$year/$rd"} ||=
333 84   50     698 (interpret_date($year, $HOLIDAYS{$rd}||die)
      66        
334             or die "couldn't interpret $year, $HOLIDAYS{$rd}");
335              
336 84 50       48258 if ( $when eq $mmdd ) {
337 84         367 return $rd;
338             }
339             }
340              
341 84         252 return undef;
342             }
343              
344             sub nz_holiday_date {
345 6     6 1 1555 my ($year, $holname) = @_;
346 6 100       30 $holname = $holiday_aliases{$holname} if $holiday_aliases{$holname};
347 6 50       30 exists $HOLIDAYS{$holname} or croak "no such holiday $holname";
348              
349 6 100       31 if ( $NATIONAL_HOLIDAYS->has($holname) ) {
350 3         40 my $date = interpret_date($year, $HOLIDAYS{$holname});
351 3         1141 my $hols = nz_holidays($year);
352              
353 3 100       20 if ( $date =~ s/\+$// ) {
354 1   66     10 while ( not exists $hols->{$date} or
355             $hols->{$date} !~ m/^\Q$holname\E/i
356             ) {
357 2 50       3036 die "couldn't find it!" if $date eq "1231";
358 2         12 $date = UnixDate(DateCalc($year.$date, "+1d"), "%m%d");
359             }
360             }
361              
362 3         3475 return $date;
363             } else {
364             return $regional_holiday_cache{"$year/$holname"}
365 3   66     52 || interpret_date($year, $HOLIDAYS{$holname});
366             }
367             }
368              
369             1;
370              
371             __END__
372              
373             =encoding utf-8
374              
375             =head1 NAME
376              
377             Date::Holidays::NZ - Determine New Zealand public holidays
378              
379             =head1 SYNOPSIS
380              
381             use Date::Holidays::NZ;
382             my ($year, $month, $day) = (localtime)[ 5, 4, 3 ];
383             $year += 1900;
384             $month += 1;
385             print "Woohoo" if is_nz_holiday( $year, $month, $day );
386              
387             # supply Census 2001 region codes (1-19), or names like
388             # "Wellington", "Canterbury (South)", etc
389             print "Yes!" if is_nz_holiday( $year, $month, $day, $region );
390              
391             my $h = nz_holidays($year);
392             printf "Dec. 25th is named '%s'\n", $h->{'1225'};
393              
394             =head1 DESCRIPTION
395              
396             Determines whether a given date is a New Zealand public holiday or
397             not.
398              
399             As described at
400             L<http://www.ers.dol.govt.nz/holidays_act_2003/dates/>, the system of
401             determining holidays in New Zealand is a complicated matter. Not only
402             do you need to know what region the country is living in to figure out
403             the relevant Anniversary Day, but sometimes the district too (for the
404             West Coast and Canterbury). As regions are free to pick the actual
405             days observed for particular holidays, this module cannot guarantee
406             dates past the last time it was checked against the Employment
407             Relations Service web site (currently returns known good values for
408             statutory holidays 2003 - 2009 and regional holidays for 2005).
409              
410             This module hopes to return values that are Mostly Right(tm) when
411             passed in census 2001 region codes (widely used throughout government
412             and industry), and can also be passed in textual region labels, which
413             includes the appropriate Anniversary Day.
414              
415             Also, there is a difference between what is considered a holiday by
416             the Holidays Act 2003 - and hence entitling a person to time in lieu
417             and/or extra pay - and what is considered to be a Bank Holiday. This
418             module returns Bank Holiday dates, so if Christmas Day falls on a
419             Sunday, the 26th will be called "Boxing Day", and the 27th "Christmas
420             Day Holiday".
421              
422              
423             =head1 Functions
424              
425             =over 4
426              
427             =item is_nz_holiday($year, $month, $date, [$region])
428              
429             Returns the name of the Holiday that falls on the given day, or undef
430             if there is none.
431              
432             Optionally, a region may be specified, which also checks the
433             anniversary day applicable to that region.
434              
435             =item nz_holidays($year, [$region])
436              
437             Returns a hashref of all defined holidays in the year. Keys in the
438             hashref are in 'mmdd' format, the values are the names of the
439             holidays.
440              
441             As per the previous function, a region name may be specified. If you
442             do not specify a region, then no regional holidays are included in the
443             returned hash.
444              
445             =item nz_regional_day($region)
446              
447             Returns the name of the regional holiday for the specified region.
448              
449             This can be passed into the next function to find the actual date for
450             a given year.
451              
452             Valid regions are:
453              
454             Number Region Name
455             1 Northland
456             1 Northland
457             2 Auckland
458             3 Waikato
459             4 Bay of Plenty
460             5 Gisbourne
461             6 Hawkes' Bay
462             7 Taranaki
463             8 Manuwatu-Wanganui
464             9 Wellington
465             12 West Coast
466             13 Canterbury
467             -13 Canterbury (South)
468             14 Otago
469             15 Southland
470             16 Tasman
471             17 Nelson
472             18 Marlborough
473             99 Outside Regional Authority
474              
475             Note: for the purposes of calculating a holiday, 99 is considered to
476             be Chatham Islands, as there is no Regional Authority there.
477              
478             Sorry about the -13 for South Canterbury. That's a bit of a hack.
479             Better ideas welcome.
480              
481             =item nz_holiday_date($year, $holiday)
482              
483             Return the actual day that a given holiday falls on for a particular
484             year.
485              
486             Valid holiday names are:
487              
488             ANZAC Day
489             Boxing Day
490             Christmas Day
491             Day after New Years Day
492             Dominion Day
493             Easter Monday
494             Good Friday
495             Labour Day
496             New Years Day
497             Queens Birthday
498             Waitangi Day
499            
500             Auckland Anniversary Day
501             Chatham Islands Anniversary Day
502             Christchurch Show Day
503             Hawkes' Bay Anniversary Day
504             Marlborough Anniversary Day
505             Nelson Anniversary Day
506             Otago Anniversary Day
507             Southland Anniversary Day
508             Taranaki Anniversary Day
509             Wellington Anniversary Day
510             Westland Anniversary Day
511              
512             Somebody let me know if any of those are incorrect.
513              
514             C<Date::Holidays::NZ> version 1.00 and later also supports referring
515             to Queen's Birthday as "Birthday of the Reigning Sovereign". Not that
516             it has anything to do with the Queen's Birthday, really - it's just
517             another day off to stop us British subjects getting all restless,
518             revolutionary and whatnot.
519              
520             =back
521              
522             =head1 ERRATA
523              
524             Otago Anniversary Day is due to fall on Easter Monday in 2035 and 2046.
525             When this happened in 2008, the council made Easter Tuesday the
526             anniversary day; however this is not currently codified as a general
527             rule, as it is up to the council to declare this in advance. This was
528             only fixed in Date::Holidays::NZ 1.02, which was released very close
529             to the actual anniversary day - apologies for the delay in the update.
530             For those days (in 2035 and beyond), depending on which method you
531             call you might get a different answer as to why that day is a holiday
532             for that region.
533              
534             Also in 1.02 was a fix which affected functions which would return
535             the "normal" day for a holiday, rather than the day listed on the
536             official government site, for a couple of regional days which did not
537             match the general rule for when they were due.
538              
539             In 1.03 was a fix to correct for the newly Monday-ised ANZAC and
540             Waitangi days, which came into force on 1 January 2014, but had no
541             effect until 25 April 2015. Pre-2014 instances of these holidays are
542             unaffected and will continue to match their original dates.
543              
544             Note that district councils are free to alter the holidays schedule at
545             any time. Also, strictly speaking, it is the Pope who decides the date
546             of Easter, upon which Easter Friday and Monday are based.
547              
548             I'm not entirely sure on which Anniversary Day the following NZ regions
549             observe, so if it matters for you, please check that it is correct and
550             let me know if I need to fix anything:
551              
552             =over
553              
554             =item *
555              
556             Waikato (assumed Auckland)
557              
558             =item *
559              
560             BOP (assumed Auckland)
561              
562             =item *
563              
564             Gisborne (assumed Hawkes' Bay)
565              
566             =item *
567              
568             Tasman (assumed Nelson)
569              
570             =item *
571              
572             Area 99 - that is, areas outside regional authority. The biggest one
573             of these is the Chatham Islands, so this module assumes Region 99 is
574             the Chatham Islands.
575              
576             =back
577              
578             Maybe someone can shed some light on the situation in the West Coast,
579             although this is confounded by the matter that the whole concept of
580             holidays or even time there is only a loosely observed phenomenon.
581              
582             =head1 EXPORTS
583              
584             Exports the four listed functions in this manual page by default.
585              
586             You may also import various internal variables and methods used by this
587             module if you like. Log a ticket if you want any of them to be added to
588             the documentation.
589              
590             =head1 BUGS
591              
592             This module does not support Te Reo Māori. If you would be interested
593             in translating the holiday names, region names or manual page to Māori,
594             please contact the author.
595              
596             Please report issues via CPAN RT:
597              
598             http://rt.cpan.org/NoAuth/Bugs.html?Dist=Date-Holidays-NZ
599              
600             or by sending mail to
601              
602             bug-Date-Holidays-NZ@rt.cpan.org
603              
604             =head1 AUTHORS
605              
606             Modified for NZ holidays by Sam Vilain <samv@cpan.org>, from
607             Date::Holidays::DK, by Lars Thegler <lars@thegler.dk>
608              
609             =head1 COPYRIGHT
610              
611             portions:
612              
613             Copyright (c) 2004 Lars Thegler. All rights reserved.
614              
615             some modifications
616              
617             Copyright (c) 2005, 2008, Sam Vilain. All rights reserved.
618              
619             further modifications
620              
621             Copyright (c) 2015, Haydn Newport. All rights reserved.
622              
623             This program is free software; you can redistribute it and/or modify
624             it under the same terms as Perl itself.
625              
626             =cut
627