| Date-Holidays-NZ documentation | Contained in the Date-Holidays-NZ distribution. |
Date::Holidays::NZ - Determine New Zealand public holidays
use Date::Holidays::NZ;
my ($year, $month, $day) = (localtime)[ 5, 4, 3 ];
$year += 1900;
$month += 1;
print "Woohoo" if is_nz_holiday( $year, $month, $day );
# supply Census 2001 region codes (1-19), or names like
# "Wellington", "Canterbury (South)", etc
print "Yes!" if is_nz_holiday( $year, $month, $day, $region );
my $h = nz_holidays($year);
printf "Dec. 25th is named '%s'\n", $h->{'1225'};
Determines whether a given date is a New Zealand public holiday or not.
As described at http://www.ers.dol.govt.nz/holidays_act_2003/dates/, the system of determining holidays in New Zealand is a complicated matter. Not only do you need to know what region the country is living in to figure out the relevant Anniversary Day, but sometimes the district too (for the West Coast and Canterbury). As regions are free to pick the actual days observed for particular holidays, this module cannot guarantee dates past the last time it was checked against the Employment Relations Service web site (currently returns known good values for statutory holidays 2003 - 2009 and regional holidays for 2005).
This module hopes to return values that are Mostly Right(tm) when passed in census 2001 region codes (widely used throughout government and industry), and can also be passed in textual region labels, which includes the appropriate Anniversary Day.
Also, there is a difference between what is considered a holiday by the Holidays Act 2003 - and hence entitling a person to time in lieu and/or extra pay - and what is considered to be a Bank Holiday. This module returns Bank Holiday dates, so if Christmas Day falls on a Sunday, the 26th will be called "Boxing Day", and the 27th "Christmas Day Holiday".
Returns the name of the Holiday that falls on the given day, or undef if there is none.
Optionally, a region may be specified, which also checks the anniversary day applicable to that region.
Returns a hashref of all defined holidays in the year. Keys in the hashref are in 'mmdd' format, the values are the names of the holidays.
As per the previous function, a region name may be specified. If you do not specify a region, then no regional holidays are included in the returned hash.
Returns the name of the regional holiday for the specified region.
This can be passed into the next function to find the actual date for a given year.
Valid regions are:
Number Region Name
1 Northland
1 Northland
2 Auckland
3 Waikato
4 Bay of Plenty
5 Gisbourne
6 Hawkes' Bay
7 Taranaki
8 Manuwatu-Wanganui
9 Wellington
12 West Coast
13 Canterbury
-13 Canterbury (South)
14 Otago
15 Southland
16 Tasman
17 Nelson
18 Marlborough
99 Outside Regional Authority
Note: for the purposes of calculating a holiday, 99 is considered to be Chatham Islands, as there is no Regional Authority there.
Sorry about the -13 for South Canterbury. That's a bit of a hack. Better ideas welcome.
Return the actual day that a given holiday falls on for a particular year.
Valid holiday names are:
ANZAC Day Boxing Day Christmas Day Day after New Years Day Dominion Day Easter Monday Good Friday Labour Day New Years Day Queens Birthday Waitangi Day Auckland Anniversary Day Chatham Islands Anniversary Day Christchurch Show Day Hawkes' Bay Anniversary Day Marlborough Anniversary Day Nelson Anniversary Day Otago Anniversary Day Southland Anniversary Day Taranaki Anniversary Day Wellington Anniversary Day Westland Anniversary Day
Somebody let me know if any of those are incorrect.
Date::Holidays::NZ version 1.00 and later also supports referring
to Queen's Birthday as "Birthday of the Reigning Sovereign". Not that
it has anything to do with the Queen's Birthday, really - it's just
another day off to stop us British subjects getting all restless,
revolutionary and whatnot.
Otago Anniversary Day is due to fall on Easter Monday in 2035 and 2046. When this happened in 2008, the council made Easter Tuesday the anniversary day; however this is not currently codified as a general rule, as it is up to the council to declare this in advance. This was only fixed in Date::Holidays::NZ 1.02, which was released very close to the actual anniversary day - apologies for the delay in the update. For those days (in 2035 and beyond), depending on which method you call you might get a different answer as to why that day is a holiday for that region.
Also in 1.02 was a fix which affected functions which would return the "normal" day for a holiday, rather than the day listed on the official government site, for a couple of regional days which did not match the general rule for when they were due.
Note that district councils are free to alter the holidays schedule at any time. Also, strictly speaking, it is the Pope that decides the date of Easter, upon which Easter Friday and Monday are based.
I'm not entirely sure on which Anniversary Day the following NZ regions observe, so if it matters for you, please check that it is correct and let me know if I need to fix anything:
Maybe someone can shed some light on the situation in the West Coast, although this is confounded by the matter that the whole concept of holidays or even time there is only a loosely observed phenomenon.
Exports the four listed functions in this manual page by default.
You may also import various internal variables and methods used by this module if you like. Log a ticket if you want any of them to be added to the documentation.
This module does not support Te Reo Māori. If you would be interested in translating the holiday names, region names or manual page to Māori, please contact the author.
Please report issues via CPAN RT:
http://rt.cpan.org/NoAuth/Bugs.html?Dist=Date-Holidays-NZ
or by sending mail to
bug-Date-Holidays-NZ@rt.cpan.org
Modified for NZ holidays by Sam Vilain <samv@cpan.org>, from Date::Holidays::DK, by Lars Thegler <lars@thegler.dk>
portions:
Copyright (c) 2004 Lars Thegler. All rights reserved.
some modifications
Copyright (c) 2005, 2008, Sam Vilain. All rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Date-Holidays-NZ documentation | Contained in the Date-Holidays-NZ distribution. |
package Date::Holidays::NZ; use strict; use base qw(Exporter); our $SET; BEGIN { eval { require Set::Scalar }; if ($@) { require Set::Object; die "Need at least Set::Object 1.09" if ($Set::Object::VERSION < 1.09); $SET = "Set::Object"; } else { $SET = "Set::Scalar"; } } use vars qw($VERSION @EXPORT @EXPORT_OK); $VERSION = '1.02'; @EXPORT = qw(is_nz_holiday nz_holidays nz_regional_day nz_holiday_date); @EXPORT_OK = qw(%HOLIDAYS $NATIONAL_HOLIDAYS %regions nz_region_code nz_region_name %holiday_cache); my $AD = "Anniversary Day"; our %HOLIDAYS = ( # holidays that everyone gets, unless they fall on a weekend, # in which case you only get them "if you would normally have # worked on that day (of the week) "Waitangi Day" => "0206", "ANZAC Day" => "0425", # holidays which are fixed, but unless you would "normally have # worked on that day", then they are moved to the next working # day "New Years Day" => "0101+", "Day after New Years Day" => '0102+', "Christmas Day" => "1225+", "Boxing Day" => '1226+', # other nationwide holidays "Easter Monday" => "Easter + 1day", "Good Friday" => "Easter - 2days", "Queens Birthday" => '1st Monday in June', "Labour Day" => "4th Monday in October", # Anniversary days - these are the official dates, but regional # authorities and sometimes district councils pick the actual # dates. "Auckland $AD" => "Closest Monday to 0129", #"Taranaki $AD" => "Closest Monday to 0331", # Moves to 2nd Monday in March to avoid Easter. "Taranaki $AD" => "2nd Monday in March", #"Hawkes' Bay $AD" => "Closest Monday to 1101", # Moved to Friday before Labour Day. "Hawkes' Bay $AD" => "4th Monday in October - 3days", "Wellington $AD" => "Closest Monday to 0122", # "Marlborough $AD" => "Closest Monday to 1101", # Observed 1st Monday after Labour Day. "Marlborough $AD" => "4th Monday in October + 7days", "Nelson $AD" => "Closest Monday to 0201", #"Westland $AD" => "Closest Monday to 1201", "Westland $AD" => "1st monday in december", # ``Varies throughout Westland, but Greymouth observes the # official day.'' - that's the West Coast for you "Otago $AD" => "Closest Monday to 0323", "Southland $AD" => "Closest Monday to 0117", "Chatham Islands $AD" => "Closest Monday to 1130", # oddballs #"Canterbury $AD" => "Closest Monday to 1216", "Dominion Day" => "4th Monday in September", "Christchurch Show Day" => "1st Tuesday in November + 10d", ); our $NATIONAL_HOLIDAYS = $SET->new( "Waitangi Day", "ANZAC Day", "New Years Day", "Day after New Years Day", "Christmas Day", "Boxing Day", "Easter Monday", "Good Friday", "Queens Birthday", "Labour Day", ); our %holiday_aliases = ( "Birthday of the Reigning Sovereign" => "Queens Birthday", ); # These are Census 2001 region codes. our %regions = ( 1 => "Northland", 2 => "Auckland", 3 => "Waikato", 4 => "Bay of Plenty", 5 => "Gisbourne", 6 => "Hawkes' Bay", 7 => "Taranaki", 8 => "Manuwatu-Wanganui", 9 => "Wellington", 12 => "West Coast", 13 => "Canterbury", -13 => "Canterbury (South)", # tsk! naughty use of sign bit 14 => "Otago", 15 => "Southland", 16 => "Tasman", 17 => "Nelson", 18 => "Marlborough", 99 => "Outside Regional Authority (incl. Chatham Is)", ); our %rev_regions; # which anniversary days are followed by each region is largely # educated guesses, please e-mail samv@cpan.org if this list is # incorrect. our %FOLLOW = ( 1 => 2, 3 => 2, 4 => 5, 5 => 6, 8 => 9, 12 => "Westland", 13 => "Christchurch Show Day", -13 => "Dominion Day", 16 => 17, 99 => "Chatham Islands", ); use Scalar::Util qw(looks_like_number); use Carp qw(croak); sub rev_regions { my $region_name = shift; unless ( $rev_regions{Auckland} ) { %rev_regions = map { lc($_) } reverse %regions; } return $rev_regions{lc($region_name)} || croak "`$region_name' is not a valid NZ region name"; } sub nz_region_name { my $region = shift; return undef unless defined $region; if ( looks_like_number($region) ) { return $regions{$region} || croak "no such NZ region code `$region'"; } else { return $regions{rev_regions($region)}; } } sub nz_region_code { my $region = shift; return undef unless defined $region; if ( looks_like_number($region) ) { exists $regions{$region} or croak "no such NZ region code `$region'"; return $region; } else { return rev_regions($region); } } # try to guess the regional day observed from the region sub nz_regional_day { my $label = shift or return undef; if ( !looks_like_number($label) and exists $HOLIDAYS{$label." $AD"} ) { return "$label $AD"; } my $region = nz_region_code($label); my $followed; if ( $followed = $FOLLOW{$region} ) { if ( looks_like_number($followed) ) { $followed = nz_region_name($followed); } } else { $followed = nz_region_name($region); } if ( $followed !~ /Day$/ ) { $followed .= " $AD"; } return $followed; } sub check_falling_on { my ($h, $year, $date) = @_; my $falls_on = UnixDate($year.$date, "%w"); if ( $falls_on >= 6 ) { my $name = delete $h->{$date}; #print STDERR "Blast, $name falls on a ".UnixDate($year.$date, "%A"). " ($falls_on)\n"; my $add = ($falls_on + 2) % 7; #print STDERR "Adding $add days to it.\n"; my $to_fall_on = UnixDate(DateCalc($year.$date, "+${add}d"), "%m%d"); #print STDERR "Trying $to_fall_on instead.\n"; while ( exists $h->{$to_fall_on} or UnixDate($year.$to_fall_on, "%w") >= 6 ) { $to_fall_on = UnixDate(DateCalc($year.$to_fall_on, "+1d"), "%m%d"); #print STDERR "That's no bloody good, trying $to_fall_on instead.\n"; } #print STDERR "Settling on $to_fall_on ($name Holiday)\n"; $h->{$to_fall_on} = "$name Holiday"; } } use Date::Manip qw(DateCalc ParseDate UnixDate ParseRecur); sub interpret_date { my $year = shift; my $value = shift; my ($date, $add); if ( $value =~ m/^(\d\d)(\d\d)\+?$/ ) { return $value; } elsif ( $value =~ m/^(\d+(?:st|nd|rd|th) \w+day in \w+)(.*)$/i) { (my $spec, $add) = ($1, $2); $date = ParseDate($spec. " $year"); } elsif ( $value =~ m/^Closest (\w+day) to (\d\d)(\d\d)(.*)$/i) { #print STDERR "**** $value($year):\n"; (my ($day, $month, $dom), $add) = ($1, $2, $3, $4); $date = ParseDate("$year-$month-$dom"); my $wanted = UnixDate($day, "%w"); my $got = UnixDate($date, "%w"); #print STDERR " $year-$month-$dom is a ".UnixDate($date, "%A") #." ($got)\n"; if ( my $diff = ($wanted - $got + 7) % 7 ) { #print STDERR " difference is $diff days\n"; if ( $diff < 4 ) { #print STDERR "Adding $diff days\n"; $date = DateCalc($date, "+${diff}d"); } else { $diff = 7 - $diff; #print STDERR "Subtracting $diff days\n"; $date = DateCalc($date, "-${diff}d"); } } else { #print STDERR "Which is good\n"; } } elsif ( $value =~ m/^Easter(.*)$/i) { ($date) = ParseRecur("*$year:0:0:0:0:0:0*EASTER"); $add = $1; } $date = DateCalc($date, "$add") if $add; return UnixDate($date, "%m%d"); } sub nz_stat_holidays { my $year = shift; # build the relative dates my (%h, @tentative); foreach my $holiday ($NATIONAL_HOLIDAYS->members) { my $when = interpret_date($year, $HOLIDAYS{$holiday}||die) or die "couldn't interpret $year, $HOLIDAYS{$holiday}"; if ( $when =~ s/\+// ) { push @tentative, $when; } $h{$when} = $holiday; } for my $date ( sort { $a <=> $b } @tentative ) { check_falling_on(\%h, $year, $date); } return \%h; } our %regional_holiday_cache = # exceptions ( "2004/Westland $AD" => "1129", "2008/Hawkes' Bay $AD" => "1017", "2008/Otago $AD" => "0325", ); our %holiday_cache; sub nz_holidays { my ($year, $region) = @_; my $hols = $holiday_cache{$year} ||= nz_stat_holidays($year); if ( $region ) { my $rd = nz_regional_day($region); my $when = $regional_holiday_cache{"$year/$rd"} ||= (interpret_date($year, $HOLIDAYS{$rd}||die) or die "couldn't interpret $year, $HOLIDAYS{$rd}"); $hols = { %$hols, $when => "$rd" }; } return $hols; } sub is_nz_holiday { my ($year, $month, $day, $region) = @_; my $mmdd = sprintf("%.2d%.2d", $month, $day); my $hols = $holiday_cache{$year} ||= nz_stat_holidays($year); if ( exists $hols->{$mmdd} ) { return $hols->{$mmdd}; } if ( $region ) { my $rd = nz_regional_day($region); my $when = $regional_holiday_cache{"$year/$rd"} ||= (interpret_date($year, $HOLIDAYS{$rd}||die) or die "couldn't interpret $year, $HOLIDAYS{$rd}"); if ( $when eq $mmdd ) { return $rd; } } return undef; } sub nz_holiday_date { my ($year, $holname) = @_; $holname = $holiday_aliases{$holname} if $holiday_aliases{$holname}; exists $HOLIDAYS{$holname} or croak "no such holiday $holname"; if ( $NATIONAL_HOLIDAYS->has($holname) ) { my $date = interpret_date($year, $HOLIDAYS{$holname}); my $hols = nz_holidays($year); if ( $date =~ s/\+$// ) { while ( not exists $hols->{$date} or $hols->{$date} !~ m/^\Q$holname\E/i ) { die "couldn't find it!" if $date eq "1231"; $date = UnixDate(DateCalc($year.$date, "+1d"), "%m%d"); } } return $date; } else { return $regional_holiday_cache{"$year/$holname"} || interpret_date($year, $HOLIDAYS{$holname}); } } 1; __END__