/usr/local/CPAN/Date-Holidays-AU/Date/Holidays/AU.pm


package Date::Holidays::AU;

use Time::Local();
use Date::Easter();
use Exporter();
use Carp;

our @ISA       = qw(Exporter);
our @EXPORT_OK = qw(is_holiday holidays);
our $VERSION   = '0.08';

use warnings;
use strict;

our (%cached);

use constant DEFAULT_STATE => 'VIC';

sub holidays {
	my (%params) = @_;
	unless ((exists $params{year}) && (defined $params{year})) {
		$params{year} = (localtime(time))[5];
		$params{year} += 1900;
	}
	unless ($params{year} =~ /^\d{4}$/) {
		croak("Year must be numeric and four digits, eg '2004'\n");
	}
	my ($year) = $params{year};
	unless (defined $params{state}) {
		carp "State not defined, setting state to default: ".DEFAULT_STATE;
		$params{state} = DEFAULT_STATE;	
	}

	my ($state) = uc($params{state});
	unless (($state eq 'VIC') ||
			($state eq 'WA') ||
			($state eq 'NT') ||
			($state eq 'QLD') ||
			($state eq 'TAS') ||
			($state eq 'NSW') ||
			($state eq 'SA') ||
			($state eq 'ACT'))
	{
		croak("State must be one of 'VIC','WA','NT','QLD','TAS','NSW','SA','ACT'\n");
	}
	my ($concat) = $state;
	my ($key);
	foreach $key (%params) {
		next if ($key eq 'year');
		next if ($key eq 'state');
		next unless ($params{$key});
		if (ref $params{$key}) {
			if ((ref $params{$key}) eq 'ARRAY') {
				$concat .= '_' . $key;
				foreach (@{$params{$key}}) {
					$concat .= '_' . $_;
				}	
			}
		} else {
			$concat .= '_' . $key . '_' . $params{$key};
		}
		$concat = lc($concat);
		$concat =~ s/\s*//g;
	}
	my (%holidays);
	my ($date);
	if ((exists $cached{$concat}) && (exists $cached{$concat}{$year})) {
		foreach $date (keys %{$cached{$concat}{$year}}) {
			$holidays{$date} = $cached{$concat}{$year}{$date};
		}
	} else {
		my ($holiday);
		if ($state eq 'TAS') {	
			if (exists $params{holidays}) {
				if ((ref $params{holidays}) && ((ref $params{holidays}) eq 'ARRAY')) {
					my ($allowed);
					foreach $allowed (@{$params{holidays}}) {
						$allowed = lc($allowed);
						$allowed =~ s/\s*//g;
						if ($allowed eq 'devonportcup') {
							foreach $holiday (_compute_devonport_cup($year)) { # TAS devonport cup
								$holidays{$holiday} = 'Devonport Cup';
							}
						}
					}
				} else {
					croak("Holidays parameter must be a reference to an array\n");
				}
			}
		}
		foreach $holiday (_compute(1,1,$year,{ 'day_in_lieu' => 1 })) { # new years day
			if ($holiday eq '0101') {
				$holidays{$holiday} = 'New Years Day';
			} else {
				$holidays{$holiday} = 'New Years Day Holiday';
			}
		}
		foreach $holiday (_compute(26,1,$year,{ 'day_in_lieu' => 1 })) { # australia day
			if ($holiday eq '2601') {
				$holidays{$holiday} = 'Australia Day';
			} else {
				$holidays{$holiday} = 'Australia Day Holiday';
			}
		}
		if ($state eq 'VIC') {
			foreach $holiday (_compute_vic_labour_day($year)) { # VIC labour day
				$holidays{$holiday} = 'Labour Day';
			}
		} elsif ($state eq 'WA') {
			foreach $holiday (_compute_wa_labour_day($year)) { # WA labour day
				$holidays{$holiday} = 'Labour Day';
			}
		} elsif ($state eq 'SA') {
			foreach $holiday (_compute_sa_adelaide_cup_day($year)) { # adelaide cup day
				$holidays{$holiday} = 'Adelaide Cup Day';
			}
		} elsif ($state eq 'ACT') {	
			foreach $holiday (_compute_canberra_day($year)) { # canberra day
				$holidays{$holiday} = 'Canberra Day';
			}
		} elsif ($state eq 'TAS') {
			if (exists $params{holidays}) {
				if ((ref $params{holidays}) && ((ref $params{holidays}) eq 'ARRAY')) {
					my ($allowed);
					foreach $allowed (@{$params{holidays}}) {
						$allowed = lc($allowed);
						$allowed =~ s/\s*//g;
						if ($allowed eq 'devonportcup') {
							foreach $holiday (_compute_devonport_cup($year)) { # TAS devonport cup
								$holidays{$holiday} = 'Devonport Cup';
							}
						} elsif ($allowed eq 'hobartregatta') {
							foreach $holiday (_compute_hobart_regatta($year)) { # TAS hobart regatta
								$holidays{$holiday} = 'Hobart Regatta';
							}
						} elsif ($allowed eq 'launcestoncup') {
							foreach $holiday (_compute_launceston_cup($year)) { # TAS launceston cup
								$holidays{$holiday} = 'Launceston Cup';
							}
						} elsif ($allowed eq 'kingislandshow') {
							foreach $holiday (_compute_king_island_show($year)) { # TAS king island show
								$holidays{$holiday} = 'King Island Show';
							}
						}
					}
				} else {
					croak("Holidays parameter must be a reference to an array\n");
				}
			}
			foreach $holiday (_compute_eight_hours_day($year)) { # TAS eight hours day
				$holidays{$holiday} = 'Eight Hours Day';
			}
		}
		my ($count) = 0;
		foreach $holiday (_compute_easter($year,$state)) { # easter
			if ($count == 0) {
				$holidays{$holiday} = 'Good Friday';
			} elsif ($count == 1) {
				$holidays{$holiday} = 'Easter Saturday';
			} elsif ($count == 2) {
				$holidays{$holiday} = 'Easter Sunday';
			} elsif ($count == 3) {
				$holidays{$holiday} = 'Easter Monday';
			} elsif (($count == 4) && ($state eq 'TAS')) {
				$holidays{$holiday} = 'Easter Tuesday';
			} else {
				croak("Too many days in easter\n");
			}
			$count += 1;
		}	
		if (($state eq 'VIC') || ($state eq 'TAS')) {
			foreach $holiday (_compute(25,4,$year)) { # ANZAC day
				$holidays{$holiday} = 'Anzac Day';
			}
		} else {
			foreach $holiday (_compute(25,4,$year,{ 'day_in_lieu' => 1 })) { # ANZAC day
				if ($holiday eq '2504') {
					$holidays{$holiday} = 'Anzac Day';
				} else {
					$holidays{$holiday} = 'Anzac Day Holiday';
				}
			}
		}
		if ($state eq 'SA') {
			foreach $holiday (_compute_sa_volunteers_day($year)) { # SA Volunteers day
				$holidays{$holiday} = 'Volunteers Day';
			}
		} elsif ($state eq 'NT') {
			foreach $holiday (_compute_nt_may_day($year)) { # NT May day
				$holidays{$holiday} = 'May Day';
			}
		} elsif ($state eq 'TAS') {
			if (exists $params{holidays}) {
				if ((ref $params{holidays}) && ((ref $params{holidays}) eq 'ARRAY')) {
					my ($allowed);
					foreach $allowed (@{$params{holidays}}) {
						$allowed = lc($allowed);
						$allowed =~ s/\s*//g;
						if ($allowed eq 'agfest') {
							foreach $holiday (_compute_agfest($year)) { # TAS Agfest
								$holidays{$holiday} = 'Agfest';
							}
						}
					}
				} else {
					croak("Holidays parameter must be a reference to an array\n");
				}
			}
		}
		if ($state eq 'WA') {
			foreach $holiday (_compute_wa_foundation_day($year)) { # WA Foundation day
				$holidays{$holiday} = 'Foundation Day';
			}
		} else {
			foreach $holiday (_compute_queens_bday($year)) { # Queens Birthday day
				$holidays{$holiday} = 'Queens Birthday';
			}
		}
		my ($holiday_hashref);
		if ($state eq 'VIC') {
			unless ((exists $params{no_melbourne_cup}) && ($params{no_melbourne_cup})) {
				foreach $holiday (_compute_melbourne_cup_day($year)) { # Melbourne Cup day
					$holidays{$holiday} = 'Melbourne Cup Day';
				}
			}
		} elsif ($state eq 'QLD') {
			unless ((exists $params{no_show_day}) && ($params{no_show_day})) {
				foreach $holiday (_compute_qld_show_day($year)) { # Queensland Show day
					$holidays{$holiday} = 'Queensland Show Day';
				}
			}
		} elsif ($state eq 'NSW') {
			if ((exists $params{include_bank_holiday}) && ($params{include_bank_holiday})) {
				foreach $holiday (_compute_nsw_act_bank_holiday($year)) { # NSW bank holiday
					$holidays{$holiday} = 'Bank Holiday';
				}
			}
			foreach $holiday (_compute_nsw_sa_act_labour_day($year)) { # NSW labour day
				$holidays{$holiday} = 'Labour Day';
			}
		} elsif ($state eq 'SA') {
			foreach $holiday (_compute_nsw_sa_act_labour_day($year)) { # SA labour day
				$holidays{$holiday} = 'Labour Day';
			}
		} elsif ($state eq 'NT') {
			foreach $holiday_hashref (_compute_nt_show_day_hash($year, \%params)) { # NT regional show days
				$holidays{$holiday_hashref->{date}} = $holiday_hashref->{name};
			}
			foreach $holiday (_compute_nt_picnic_day($year)) { # NT picnic day
				$holidays{$holiday} = 'Picnic Day';
			}
		} elsif ($state eq 'WA') {
			foreach $holiday (_compute_wa_queens_bday($year)) { # WA Queens Birthday day
				$holidays{$holiday} = 'Queens Birthday';
			}
		} elsif ($state eq 'ACT') {
			if ((exists $params{include_bank_holiday}) && ($params{include_bank_holiday})) {
				foreach $holiday (_compute_nsw_act_bank_holiday($year)) { # ACT bank holiday
					$holidays{$holiday} = 'Bank Holiday';
				}
			}
			foreach $holiday (_compute_nsw_sa_act_labour_day($year)) { # ACT labour day
				$holidays{$holiday} = 'Labour Day';
			}
		} elsif ($state eq 'TAS') {
			if (exists $params{holidays}) {
				if ((ref $params{holidays}) && ((ref $params{holidays}) eq 'ARRAY')) {
					my ($allowed);
					foreach $allowed (@{$params{holidays}}) {
						$allowed = lc($allowed);
						$allowed =~ s/\s*//g;
						if ($allowed eq 'burnieshow') {
							foreach $holiday (_compute_burnie_show($year)) { # TAS burnie show day
								$holidays{$holiday} = 'Burnie Show';
							}
						} elsif ($allowed eq 'launcestonshow') {
							foreach $holiday (_compute_launceston_show($year)) { # TAS launceston show day
								$holidays{$holiday} = 'Launceston Show';
							}
						} elsif ($allowed eq 'flindersislandshow') {
							foreach $holiday (_compute_flinders_island_show($year)) { # TAS flinders island show day
								$holidays{$holiday} = 'Flinders Island Show';
							}
						} elsif ($allowed eq 'hobartshow') {
							foreach $holiday (_compute_hobart_show($year)) { # TAS hobart show day
								$holidays{$holiday} = 'Hobart Show';
							}
						} elsif ($allowed eq 'recreationday') {
							foreach $holiday (_compute_recreation_day($year)) { # TAS recreation day
								$holidays{$holiday} = 'Recreation Day';
							}
						} elsif ($allowed eq 'devonportshow') {
							foreach $holiday (_compute_devonport_show($year)) { # TAS devonport show day
								$holidays{$holiday} = 'Devonport Show';
							}
						}
					}
				} else {
					croak("Holidays parameter must be a reference to an array\n");
				}
			}
		}
		foreach $holiday_hashref (_compute_christmas_hash($year, $state)) { # christmas day + boxing day
			$holidays{$holiday_hashref->{date}} = $holiday_hashref->{name};
		}
		foreach $date (keys %holidays) {
			$cached{$concat}{$year}{$date} = $holidays{$date};
		}
	}
	return (\%holidays);
}

sub is_holiday {
	my ($year, $month, $day, $state, $params) = @_;
	my ($key);
	my ($concat) = $state ||= DEFAULT_STATE;
	foreach $key (%$params) {
		next unless ($params->{$key});
		if (ref $params->{$key}) {
			if ((ref $params->{$key}) eq 'ARRAY') {
				$concat .= '_' . $key;
				foreach (@{$params->{$key}}) {
					$concat .= '_' . $_;
				}	
			}
		} else {
			$concat .= '_' . $key . '_' . $params->{$key};
		}
		$concat = lc($concat);
		$concat =~ s/\s*//g;
	}
	unless ((exists $cached{$concat}) && (exists $cached{$concat}{$year})) {
		holidays( 'year' => $year, 'state' => $state, %$params);
	}
	my ($date) = sprintf("%02d%02d",$month,$day);
	if ((exists $cached{$concat}) && (exists $cached{$concat}{$year}{$date})) {
		return 1;
	} else {
		return 0;
	}
}

our (@daysInMonth) = (31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); # feb will be calculated locally

sub _compute_christmas_hash {
	my ($year, $state) = @_;
	my ($day) = 25;
	my ($month) = 12;
	my ($date) = Time::Local::timelocal(0,0,0,$day,($month - 1),$year);
	my ($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
	my ($boxingDay) = 'Boxing Day';
	if ($state eq 'SA') {
		$boxingDay = 'Proclamation Day';
	}
	my (@holidays);
	push @holidays, {
				'name'	=> 'Christmas Day',
				'date'	=> sprintf("%02d%02d",$month,$day),
			};
	push @holidays, {
				'name'	=> $boxingDay,
				'date'	=> sprintf("%02d%02d",$month,($day + 1)),
			};
	if ($wday == 5) { # Christmas is on a Friday
		push @holidays, {
					'name'	=> "$boxingDay Holiday",
					'date'	=> sprintf("%02d%02d",$month,($day + 2)),
				};
	} elsif ($wday == 6) { # Christmas is on a Saturday
		push @holidays, {
					'name'	=> 'Christmas Day Holiday',
					'date'	=> sprintf("%02d%02d",$month,($day + 2)),
				};
		push @holidays, {
					'name'	=> "$boxingDay Holiday",
					'date'	=> sprintf("%02d%02d",$month,($day + 3)),
				};
	} elsif ($wday == 0) { # Christmas is on a Sunday
		push @holidays, {
					'name'	=> 'Christmas Day Holiday',
					'date'	=> sprintf("%02d%02d",$month,($day + 2)),
				};
	}
	return (@holidays);
}

sub _compute_nt_show_day_hash {
	my ($year, $params) = @_; 
	my ($month, $numFridays, $name);
	if ((exists $params->{region}) && (defined $params->{region})) {
		my ($region) = lc($params->{region});
		$region =~ s/\s*//g;
		if ($region eq 'alicesprings') {
			$name = 'Alice Springs Show Day';
			$month = 6;
			$numFridays = 1;
		} elsif ($region eq 'tennantcreek') {
			$name = 'Tennant Creek Show Day';
			$month = 6;
			$numFridays = 2;
		} elsif ($region eq 'katherine') {
			$name = 'Katherine Show Day';
			$month = 6;
			$numFridays = 3;
		} elsif ($region eq 'darwin') {
			$name = 'Darwin Show Day';
			$month = 6;
			$numFridays = 4;
		} elsif ($region eq 'borroloola') {
			$name = 'Borrolooda Show Day';
			$month = 7;
			$numFridays = 4;
		} else {
			croak("Unknown region\n");
		}
	} else {
		$name = 'Darwin Show Day';
		$month = 6;
		$numFridays = 4;
	}
	my ($day) = 1;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($fridays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($fridays < $numFridays) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 5) {
			$fridays += 1;
		}
		if ($fridays < $numFridays) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	my (@holidays) = ({
				'name'	=> $name,
				'date'	=> sprintf("%02d%02d",($month + 1),$day),
			});
	return (@holidays);
}

sub _compute_qld_show_day { # second wednesday in august, except when there are five wednesdays in august when it is the third wednesday
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 7;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($wednesdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	my ($numWednesdays);
	($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
	if (($wday >= 1) && ($wday <= 3)) {
		$numWednesdays = 3;
	} else {
		$numWednesdays = 2;
	}
	while($wednesdays < $numWednesdays) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 3) {
			$wednesdays += 1;
		}
		if ($wednesdays < $numWednesdays) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_devonport_show { # friday nearest last day in november, but not later than first day in december
	my ($year) = @_; 
	my ($month) = 10;
	my ($day) = $daysInMonth[$month];
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
	if ($wday == 4) { # thursday
		$day = 1;
		$month = 11;
	} elsif ($wday == 5) { # friday
	} elsif ($wday == 6) { # saturday
		$day -= 1;
	} elsif ($wday == 0) { # sunday
		$day -= 2;
	} elsif ($wday == 1) { # monday
		$day -= 3;
	} elsif ($wday == 2) { # tuesday
		$day -= 4;
	} elsif ($wday == 3) { # wednesday
		$day -= 5;
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_devonport_cup { # wednesday not earlier than fifth and not later than the eleventh of January
	my ($year) = @_; 
	my ($day) = 5;
	my ($month) = 0;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
	while($wday != 3) {
		$day += 1;
		$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_launceston_cup { # last wednesday in feb
	my ($year) = @_; 
	my ($month) = 1;
	my ($day);
	if (($year % 4) && ((not ($year % 100)) || ($year % 400))) {
		$day = 28;
	} else {
		$day = 29;
	}

	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($wednesdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($wednesdays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 3) {
			$wednesdays += 1;
		}
		if ($wednesdays < 1) {
			$day -= 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_eight_hours_day { # second monday in march
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 2;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 2) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 2) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_king_island_show { # first tuesday in march
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 2;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($tuesdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($tuesdays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 2) {
			$tuesdays += 1;
		}
		if ($tuesdays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_hobart_regatta { # second monday in feb
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 1;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 2) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 2) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_canberra_day { # third monday in march
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 2;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 3) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 3) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_recreation_day { # first monday in november
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 10;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_melbourne_cup_day { # first tuesday in november
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 10;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($tuesdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($tuesdays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 2) {
			$tuesdays += 1;
		}
		if ($tuesdays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_wa_foundation_day { # first monday in june
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 5;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_queens_bday { # second monday in june
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 5;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 2) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 2) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_sa_volunteers_day { # third monday in may up excluding 2006
	my ($year) = @_; 
	if ($year == 2006) {
		return ();
	}
	my ($day) = 1;
	my ($month) = 4;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 3) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 3) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_sa_adelaide_cup_day { # second monday in march in 2006
	my ($year) = @_; 
	if ($year != 2006) {
		return ();
	}
	my ($day) = 1;
	my ($month) = 2;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 2) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 2) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_vic_labour_day { # second monday in march
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 2;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 2) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 2) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_wa_labour_day { # first monday in march
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 2;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_nt_may_day { # first monday in may
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 4;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_agfest { # friday following first thursday in may
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 4;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($thursdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($thursdays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 4) {
			$thursdays += 1;
		}
		if ($thursdays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),($day + 1)));
}

sub _compute_burnie_show { # friday preceding first saturday in october
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 9;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($saturdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($saturdays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 6) {
			$saturdays += 1;
		}
		if ($saturdays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	if ($day == 1) {
		return (sprintf("%02d%02d",$month,$daysInMonth[$month - 1]));
	} else {
		return (sprintf("%02d%02d",($month + 1),($day - 1)));
	}
}

sub _compute_launceston_show { # thursday preceding second saturday in october
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 9;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($saturdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($saturdays < 2) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 6) {
			$saturdays += 1;
		}
		if ($saturdays < 2) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),($day - 2)));
}

sub _compute_flinders_island_show { # friday preceding third saturday in october
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 9;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($saturdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($saturdays < 3) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 6) {
			$saturdays += 1;
		}
		if ($saturdays < 3) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),($day - 1)));
}

sub _compute_hobart_show { # thursday preceding fourth saturday in october
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 9;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($saturdays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($saturdays < 4) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 6) {
			$saturdays += 1;
		}
		if ($saturdays < 4) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),($day - 2)));
}

sub _compute_nt_picnic_day { # first monday in august
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 7;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_nsw_act_bank_holiday { # first monday in august
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 7;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_nsw_sa_act_labour_day { # first monday in october
	my ($year) = @_; 
	my ($day) = 1;
	my ($month) = 9;
	my ($date) = Time::Local::timelocal(0,0,0,$day,$month,$year);
	my ($mondays) = 0;
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	while($mondays < 1) {
		($sec, $min, $hour, undef, undef, undef, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 1) {
			$mondays += 1;
		}
		if ($mondays < 1) {
			$day += 1;
			$date = Time::Local::timelocal(0,0,0,$day,$month,$year);
		}
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_wa_queens_bday { # monday closest to 30 september???  Formula unknown. Seems to have a 9 day spread???
	my ($year) = @_; 
	my ($day, $month);
	if ($year == 2004) {
		$day = 4;
		$month = 9;
	} elsif ($year == 2005) {
		$day = 26;
		$month = 8;
	} elsif ($year == 2006) {
		$day = 2;
		$month = 9;
	} elsif ($year == 2007) {
		$day = 1;
		$month = 9;
	} elsif ($year == 2008) {
		$day = 29;
		$month = 8;
	} elsif ($year == 2009) {
		$day = 28;
		$month = 8;
	} elsif ($year == 2010) {
		$day = 27;
		$month = 8;
	} elsif ($year == 2011) {
		$day = 3;
		$month = 9;
	} elsif ($year == 2012) {
		$day = 1;
		$month = 9;
	} else {
		croak("Don't know how to calculate Queen's Birthday in WA for this year\n");
	}
	return (sprintf("%02d%02d",($month + 1),$day));
}

sub _compute_easter {
	my ($year,$state) = @_;
	my ($month, $day) = Date::Easter::gregorian_easter($year);
	my ($date) = Time::Local::timelocal(0,0,0,$day,($month - 1),$year);
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime($date);
	unless ($wday == 0) {
		croak("Easter must fall on a Sunday\n");
	}
	my (@holidays);
	# good friday + easter saturday
	if ($month == 2) { # march
		push @holidays, sprintf("%02d%02d",($month + 1),($day - 2)); 
		push @holidays, sprintf("%02d%02d",($month + 1),($day - 1)); 
	} elsif ($month == 3) { # april 
		if ($day == 2) {
			push @holidays, sprintf("%02d%02d",$month,$daysInMonth[$month - 1]);
			push @holidays, sprintf("%02d%02d",($month + 1),1); 
		} elsif ($day == 1) {
			push @holidays, sprintf("%02d%02d",$month,($daysInMonth[$month - 1] - 1)); 
			push @holidays, sprintf("%02d%02d",$month,($daysInMonth[$month - 1])); 
		} else {
			push @holidays, sprintf("%02d%02d",($month + 1),($day - 2)); 
			push @holidays, sprintf("%02d%02d",($month + 1),($day - 1)); 
		}
	} else {
		croak("Easter has to fall in march or april\n");
	}
	# easter sunday
	push @holidays, sprintf("%02d%02d",($month + 1),$day);
	# easter monday
	if ($month == 2) { # march
		if ($day == $daysInMonth[$month]) {
			push @holidays, sprintf("%02d%02d",($month + 2),1); 
		} else {
			push @holidays, sprintf("%02d%02d",($month + 1),($day + 1)); 
		}
	} elsif ($month == 3) {
		push @holidays, sprintf("%02d%02d",($month + 1),($day + 1)); 
	}
	if ($state eq 'TAS') {
		if ($month == 2) { # march
			if ($day == $daysInMonth[$month]) {
				push @holidays, sprintf("%02d%02d",($month + 2),2); 
			} elsif (($day + 1) == $daysInMonth[$month]) {
				push @holidays, sprintf("%02d%02d",($month + 2),1); 
			} else {
				push @holidays, sprintf("%02d%02d",($month + 1),($day + 2)); 
			}
		} elsif ($month == 3) {
			push @holidays, sprintf("%02d%02d",($month + 1),($day + 1)); 
		}
	}
	return (@holidays);
}

sub _compute {
	my ($day, $month, $year, $params) = @_;
	my ($date) = Time::Local::timelocal(0,0,0,$day,($month - 1),$year);
	my ($sec, $min, $hour, $wday, $yday, $isdst);
	my (@holidays);
	push @holidays, sprintf("%02d%02d", $month, $day);
	if ($params->{day_in_lieu}) {
		($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime($date);
		if ($wday == 0) {
			if ($daysInMonth[$month] == $day) {
				if ($month == 11) {
					$day = 1;
					$month = 0;
					$year += 1;
				} else {
					$day = 1;
					$month += 1;
				}
			} else {
				$day += 1;
			}
			push @holidays, sprintf("%02d%02d",($month + 1),$day);
		} elsif ($wday == 6) {
			if ($daysInMonth[$month] == $day) {
				if ($month == 11) {
					$day = 1;
					$month = 0;
					$year += 1;
				} else {
					$day = 1;
					$month += 1;
				}
			} elsif ($daysInMonth[$month] == ($day + 1)) {
				if ($month == 11) {
					$day = 1;
					$month = 0;
					$year += 1;
				} else {
					$day = 1;
					$month += 1;
				}
			} else {
				$day += 2;
			}
			push @holidays, sprintf("%02d%02d",($month + 1),$day);
		}
	}	
	return (@holidays);
}