Date-Holidays-CA

 view release on metacpan or  search on metacpan

lib/Date/Holidays/CA.pm  view on Meta::CPAN





sub get {
    croak 'Wrong number of arguments to get()' if scalar @_ != 2;
    my $self  = shift;
    my $field = shift;

    if (exists $self->{$field}) {
        return $self->{$field};
    }

    croak "No such field $field";
}



sub set {
    croak 'Wrong number of arguments to set()' if scalar @_ != 2;
    my $self     = shift;
    my $args_ref = shift;

    while (my ($field, $value) = each %{$args_ref}) {
        my $new_value;

        if ($new_value = _validate($field, $value)) {
            $self->{$field} = $new_value;
        }
    }

    return 1;
}



sub is_holiday {
    return ( is_ca_holiday(@_) ? 1 : 0 );
}



sub is_ca_holiday {
    my $self;
    $self = shift if (ref $_[0]);               # invoked in OO style

    my $year    = shift;
    my $month   = shift;
    my $day     = shift;
    my $options = shift;

    if (defined $options and defined $options->{province} ) {
        $self->{province} = $options->{province}
    }
    _assert_valid_date($year, $month, $day);

    unless (defined $self) {
        $self = Date::Holidays::CA->new($options);
    }

    my $calendar = $self->_generate_calendar($year);

    # assumption: there is only one holiday for any given day.
    while (my ($holiday_name, $holiday_dt) = each %$calendar) {
        if ($month == $holiday_dt->month and $day == $holiday_dt->day) {
            return $holiday_name;
        }
    }

    return;
}



sub is_holiday_dt {
    my ($self, $dt, $options);

    my @args = map {
        ref $_ eq 'DateTime' ? ($_->year, $_->month, $_->day) : $_
    } @_;

    return is_holiday(@args);
}



sub holidays {
    my $calendar = holidays_dt(@_);

    my %holidays = map {
       $calendar->{$_}->strftime('%m%d') => $_
    } keys %$calendar;

    return \%holidays;
}



sub ca_holidays {
    return holidays(@_);
}



sub holidays_dt {
    my $self;
    $self = shift if (ref $_[0]);               # invoked in OO style

    my $year     = shift;
    my $args_ref = shift;

    unless (defined $self) {
        $self = Date::Holidays::CA->new($args_ref);
    }

    return $self->_generate_calendar($year);
}



### internal functions

my @VALID_PROVINCES = qw{ CA AB BC MB NB NL NS NT NU ON PE QC SK YT };
my @VALID_LANGUAGES = qw{ EN/FR FR/EN EN FR };
my %VALUES_FOR = (
    'PROVINCE' => \@VALID_PROVINCES,
    'LANGUAGE' => \@VALID_LANGUAGES,
);


# _validate($field, $value)
#
# accepts: field name ( 'province' | 'language' )
#          possible value for that field
# returns: if $value is a valid value for $field, canonicalize and return
#          it (eg, upcase it).
#          if $value isn't valid, throw an exception.


sub _validate {
    my $field = shift;
    my $value = shift;

    my @valid_values = @{ $VALUES_FOR{uc($field)} };
    croak "No such field $field" unless @valid_values;

    foreach my $valid_value (@valid_values) {
        return uc($value) if uc($value) eq $valid_value;
    }

    croak "$value is not a recognized setting for $field";
}


# _assert_valid_date
#
# accepts: numeric year, month, day
# returns: nothing
#
# throw an exception on invalid dates; otherwise, do nothing.

sub _assert_valid_date {
    my ($year, $month, $day) = @_;

    # DateTime does date validation when a DT object is created.
    my $dt = DateTime->new(
        year => $year, month => $month, day => $day,
    );
}


# format: each holiday is listed as a triplet:
#   * function that returns a DateTime object for that holiday
#   * english name
#   * french name
# listing the names each time makes for a verbose list with a lot of

lib/Date/Holidays/CA.pm  view on Meta::CPAN

            \&_family_day,               'Family Day', 'Jour de la Famille',
            \&_good_friday,              'Good Friday', 'Vendredi Saint',
            \&_victoria_day,             'Victoria Day', 'Fête de la Reine',
            \&_canada_day,               'Canada Day', 'Fête du Canada',
            \&_civic_holiday,            'Civic Holiday', 'Congé Statutaire',
            \&_thanksgiving_day,         'Thanksgiving Day', 'Action de Grâce',
            \&_christmas_day,            'Christmas Day', 'Noël',
            \&_boxing_day,               'Boxing Day', 'Lendemain de Noël',
    ],

    PE => [
            \&_new_years_day,            'New Year\'s Day', 'Jour de l\'An',
            \&_family_day,               'Islander Day', 'Fête des Insulaires',
            \&_good_friday,              'Good Friday', 'Vendredi Saint',
            \&_canada_day,               'Canada Day', 'Fête du Canada',
            \&_labour_day,               'Labour Day', 'Fête du Travail',
            \&_truth_reconciliation_day, 'National Day for Truth and Reconciliation', 'Journée nationale de la vérité et de la réconciliation',
            \&_remembrance_day,          'Remembrance Day', 'Jour du Souvenir',
            \&_christmas_day,            'Christmas Day', 'Noël',
    ],

    QC => [
            \&_new_years_day,            'New Year\'s Day', 'Jour de l\'An',
            \&_good_friday,              'Good Friday', 'Vendredi Saint',
            \&_victoria_day,             'National Patriot\'s Day', 'Journée Nationale des Patriotes / Fête de la Reine',
            \&_st_john_baptiste_day,     'Saint-Jean-Baptiste Day', 'La Saint-Jean',
            \&_canada_day,               'Canada Day', 'Fête du Canada',
            \&_labour_day,               'Labour Day', 'Fête du Travail',
            \&_thanksgiving_day,         'Thanksgiving Day', 'Action de Grâce',
            \&_christmas_day,            'Christmas Day', 'Noël',
    ],

    SK => [
            \&_new_years_day,            'New Year\'s Day', 'Jour de l\'An',
            \&_family_day,               'Family Day', 'Jour de la Famille',
            \&_good_friday,              'Good Friday', 'Vendredi Saint',
            \&_victoria_day,             'Victoria Day', 'Fête de la Reine',
            \&_canada_day,               'Canada Day', 'Fête du Canada',
            \&_civic_holiday,            'Saskatchewan Day', 'Fête de la Saskatchewan',
            \&_labour_day,               'Labour Day', 'Fête du Travail',
            \&_thanksgiving_day,         'Thanksgiving Day', 'Action de Grâce',
            \&_remembrance_day,          'Remembrance Day', 'Jour du Souvenir',
            \&_christmas_day,            'Christmas Day', 'Noël',
    ],

    YT => [
            \&_new_years_day,            'New Year\'s Day', 'Jour de l\'An',
            \&_good_friday,              'Good Friday', 'Vendredi Saint',
            \&_victoria_day,             'Victoria Day', 'Fête de la Reine',
            \&_national_aboriginal_day,  'National Aboriginal Day', 'Journée Nationale des Autochtones',
            \&_canada_day,               'Canada Day', 'Fête du Canada',
            \&_yt_discovery_day,         'Discovery Day', 'Jour du découverte',
            \&_labour_day,               'Labour Day', 'Fête du Travail',
            \&_thanksgiving_day,         'Thanksgiving Day', 'Action de Grâce',
            \&_remembrance_day,          'Remembrance Day', 'Jour du Souvenir',
            \&_christmas_day,            'Christmas Day', 'Noël',
    ],
);


# _generate_calendar
#
# accepts: numeric year
# returns: hashref (string $holiday_name => DateTime $holiday_dt)
#
# generate a holiday calendar for the specified year -- a hash mapping
# holiday names to datetime objects.
sub _generate_calendar {
    my $self = shift;
    my $year = shift;
    my $calendar = {};

    my @holiday_list = @{ $HOLIDAYS_FOR{$self->{'province'}} };

    while(@holiday_list) {
        my $holiday_dt = (shift @holiday_list)->($year);  # fn invokation
        my $name_en    = shift @holiday_list;
        my $name_fr    = shift @holiday_list;

        my $holiday_name =
              $self->{'language'} eq 'EN'    ? $name_en
            : $self->{'language'} eq 'FR'    ? $name_fr
            : $self->{'language'} eq 'EN/FR' ? "$name_en/$name_fr"
            : $self->{'language'} eq 'FR/EN' ? "$name_fr/$name_en"
            : "$name_en/$name_fr";  # sane default, should never get here

        $calendar->{$holiday_name} = $holiday_dt;
    }

    return $calendar;
}

### toolkit functions

# _nth_monday
#
# accepts:  year, month, ordinal of which monday to find
# returns:  numeric date of the requested monday
#
# find the day of week for the first day of the month,
# calculate the number of day to skip forward to hit the first monday,
# then skip forward the requisite number of weeks.
#
# in general, the number of days we need to skip forward from the
# first of the month is (target_dow - first_of_month_dow) % 7

sub _nth_monday {
    my $year  = shift;
    my $month = shift;
    my $n     = shift;

    my $first_of_month = DateTime->new(
        year  => $year,
        month => $month,
        day   => 1,
    );

    my $date_of_first_monday = 1 + ( (1 - $first_of_month->dow()) % 7);

    return $date_of_first_monday + 7 * ($n - 1);
}

# _nearest_monday
#
# accepts:  year, month, day for a given date
# returns:  day of the nearest monday to that date

sub _nearest_monday {
    my $year  = shift;
    my $month = shift;
    my $day   = shift;

    my $dt = DateTime->new(year => $year, month => $month, day => $day);

    my $delta_days = ((4 - $dt->dow) % 7) - 3;

    return $day + $delta_days;
}

# _round_to_monday
#
# accepts: year, month, day for a given date
# returns: day unless day is a Sat/Sun, then the next Mon
sub _round_to_monday {
    my $year  = shift;
    my $month = shift;
    my $day   = shift;

    my $dt = DateTime->new(year => $year, month => $month, day => $day);

lib/Date/Holidays/CA.pm  view on Meta::CPAN

        year  => $year,
        month => 11,
        day   => 11,
    );
}

sub _christmas_day {
    my $year = shift;

    # Christmas Day is December 25th but the Holiday is normally
    # observed on the Monday if it occurs on a week end day
    return DateTime->new(
        year  => $year,
        month => 12,
        day   => _round_to_monday($year, 12, 25)
    );
}

sub _boxing_day {
    my $year = shift;

    # Normally the day after the Christmas Holiday, except if
    # Christmas is on Friday, then our holiday is on Monday
    my $result = _christmas_day($year)->add(days=>1);
    return DateTime->new(
        year  => $year,
        month => 12,
        day   => _round_to_monday($year, 12, $result->day())
    );
}


1;  # all's well

__END__

=pod

=encoding UTF-8

=head1 NAME

Date::Holidays::CA - Date::Holidays::CA determines public holidays for Canadian jurisdictions

=head1 VERSION

version 0.07

=head1 SYNOPSIS

    # procedural approach

    use Date::Holidays::CA qw(:all);

    my ($year, $month, $day) = (localtime)[ 5, 4, 3 ];
    $year  += 1900;
    $month += 1;

    print 'Woot!' if is_holiday($year, $month, $day, {province => 'BC'});

    my $calendar = holidays($year, {province => 'BC'});
    #returns a hash reference
    print $calendar->{'0701'};              # "Canada Day/Fête du Canada"


    # object-oriented approach

    use DateTime;
    use Date::Holidays::CA;

    my $dhc = Date::Holidays::CA->new({ province => 'QC' });

    print 'Woot!' if $dhc->is_holiday(DateTime->today);

    my $calendar = $dhc->holidays_dt(DateTime->today->year);
    print join keys %$calendar, "\n";       # lists holiday names for QC

=head1 DESCRIPTION

Date::Holidays::CA determines public holidays for Canadian jurisdictions.
Its interface is a superset of that provided by Date::Holidays -- read
on for details.

=head1 NAME

Date::Holidays::CA - Holidays for Canadian locales

=head1 FUNCTIONS / METHODS

=head2 Class Methods

=head3 new()

Create a new Date::Holidays::CA object.  Parameters should be given as
a hashref of key-value pairs.

    my $dhc = Date::Holidays::CA->new();        # defaults

    my $dhc = Date::Holidays::CA->new({
        province => 'ON', language => 'EN'
    });

Two parameters can be specified: B<province> and B<language>.

=head4 Province

=over

=item * CA

Canadian Federal holidays (the default).

=item * AB

Alberta

=item * BC

British Columbia

=item * MB

Manitoba

=item * NB

New Brunswick

=item * NL

Newfoundland & Labrador

=item * NS

Nova Scotia

lib/Date/Holidays/CA.pm  view on Meta::CPAN


French text only.

=back

=head2 Object Methods

=head3 get()

Retrieve fields of a Date::Holidays::CA object.

    $prov = $dhc->('province');

=head3 set()

Alter fields of a Date::Holidays::CA object.  Specify parameters just
as with new().

    $dhc->set({province => 'QC', language => 'FR/EN'});

=head2 Combination Methods

These methods are callable in either object-oriented or procedural style.

=head3 is_holiday()

For a given year, month (1-12) and day (1-31), return 1 if the given
day is a holiday; 0 if not.  When using procedural calling style, an
additional hashref of options can be specified.

    $holiday_p = is_holiday($year, $month, $day);

    $holiday_p = is_holiday($year, $month, $day, {
        province => 'BC', language => 'EN'
    });

    $holiday_p = $dhc->is_holiday($year, $month, $day);

=head3 is_ca_holiday()

Similar to C<is_holiday>.  Return the name of the holiday occurring on
the specified date if there is one; C<undef> if there isn't.

    print $dhc->is_ca_holiday(2001, 1, 1);          # "New Year's Day"

=head3 is_holiday_dt()

As is_holiday, but accepts a DateTime object in place of a numeric year,
month, and day.

    $holiday_p = is_holiday($dt, {province => 'SK', language => 'EN'});

    $holiday_p = $dhc->is_holiday($dt);

=head3 holidays()

For the given year, return a hashref containing all the holidays for
that year.  The keys are the date of the holiday in C<mmdd> format
(eg '1225' for December 25); the values are the holiday names.

    my $calendar = holidays($year, {province => 'MB', language => 'EN'});
    #returns a hash reference
    print $calendar->{'0701'};               # "Canada Day"

    my $calendar = $dhc->holidays($year);
    #returns a hash reference
    print $calendar->{'1111'};               # "Remembrance Day"

=head3 ca_holidays()

Same as C<holidays()>.

=head3 holidays_dt()

Similar to C<holidays()>, after a fashion: returns a hashref with the
holiday names as the keys and DateTime objects as the values.

    my $calendar = $dhc->holidays_dt($year);

=head1 SPECIFICATIONS

The following holidays are recognized:

=over

=item I<New Year's Day>

January 1.

=item I<Islander Day>

PE.  Originally added in 2009 as the second Monday in February, this
holiday will be revised to the third Monday in February starting in
2010.  I<This module shows Islander Day as falling on the third Monday>
-- see the I<KNOWN BUGS> section.

=item I<Family Day / Louis Riel Day>

The Third Monday of February is Family Day in AB, SK, and ON, and
Louis Riel Day in MB.

=item I<St. Patrick's Day>

NL.  Nearest Monday to March 17.

=item I<Good Friday>

The Friday falling before Easter Sunday.

=item I<Easter Monday>

CA, QC.  The Monday following Easter Sunday.

=item I<St. Patrick's Day>

NL.  Nearest Monday to April 23.

=item I<Victoria Day>

Monday falling on or before May 24.

=item I<National Aboriginal Day>

NT.  June 21.

=item I<Saint-Jean-Baptiste Day>

QC.  June 24.

=item I<Discovery Day>

There are actually two holidays named "Discovery Day".  Newfoundland observes
Discovery Day on the Monday nearest June 24, and the Yukon observes Discovery
Day on the third Monday of August.

=item I<Canada Day>

July 1.



( run in 3.163 seconds using v1.01-cache-2.11-cpan-364913b4093 )