=head1 NAME
Date::Calendar::Profiles - Some sample profiles
for
Date::Calendar
and Date::Calendar::Year
=head1 SYNOPSIS
$cal_US_AK
= Date::Calendar->new(
$Profiles
->{
'US-AK'
} [,LANG[,WEEKEND]] );
$cal_DE_BY
= Date::Calendar->new(
$Profiles
->{
'DE-BY'
} [,LANG[,WEEKEND]] );
or
$year_2000_US_FL
= Date::Calendar::Year->new( 2000,
$Profiles
->{
'US-FL'
} [,LANG[,WEEKEND]] );
$year_2001_DE_NW
= Date::Calendar::Year->new( 2001,
$Profiles
->{
'DE-NW'
} [,LANG[,WEEKEND]] );
and also
qw(
&Previous_Friday
&Next_Monday
&Next_Monday_or_Tuesday
&Nearest_Workday
&Sunday_to_Monday
&Advent1
&Advent2
&Advent3
&Advent4
&Advent
)
;
=head1 PREFACE
This module provides some sample profiles (i.e., holiday schemes)
for
use
with
the Date::Calendar(3) and Date::Calendar::Year(3)
module.
You are not required to
use
these, you can always roll your own
(this is very easy). See the section
"HOW TO ROLL YOUR OWN"
below
for
more instructions on how to
do
this, and take the profiles
from this module as examples.
I intend not to make any fixes to any of the calendar profiles
in this module anymore
unless
there are VERY compelling reasons
to
do
so. These profiles are merely meant as examples.
The suggested way of using these profiles is to copy them to
your own code and then to modify them as needed. Otherwise
many people could be negatively affected
if
I made any changes
to a profile someone
has
been using
for
years.
Any improvements are therefore left as an exercise
to the inclined reader.
=head1 DESCRIPTION
The method
"init()"
in module Date::Calendar::Year(3) is
responsible
for
parsing the calendar schemes contained
here in the Date::Calendar::Profiles module.
This method offers a
"mini-language"
which allows to
specify common date formulas, like
for
instance a simple
fixed date (in various different formats, e.g. american
or european), or things like
"the second Sunday of May"
(Mother's Day), or
"Easter Sunday minus 46 days"
(Ash
Wednesday), to cite just a few.
See the section
"DATE FORMULA SYNTAX"
below
for
more
details.
There are some more complicated formulas, however, which
cannot be expressed in such simple terms.
The rule that
if
a holiday falls on a weekend, it will
be substituted by either the adjacent Friday or Monday
(whichever lies closer), is an example of this.
In order to be able to deal
with
such formulas, and in
order to be as flexible as possible, the
"init()"
method
offers the possibility of using callback functions to
deal
with
such dates and formulas.
See the section
"CALLBACK INTERFACE"
below
for
more
details on this topic.
In order to assist you
with
more common cases of odd
formulas, the module Date::Calendar::Profiles exports
the following utility subroutines (which are meant to
be used as
"filters"
in callback functions of your own):
=over 2
=item *
C<(
$year
,
$month
,
$day
[,ANYTHING]) = Previous_Friday(
$year
,
$month
,
$day
[,ANYTHING]);>
If the
given
date falls on a Saturday or Sunday, this
function changes the date to the adjacent Friday
before
that, and returns this new date.
Otherwise the
given
date is returned unchanged.
The rest of the input parameters,
if
any, are simply
copied to the output.
=item *
C<(
$year
,
$month
,
$day
[,ANYTHING]) = Next_Monday(
$year
,
$month
,
$day
[,ANYTHING]);>
If the
given
date falls on a Saturday or Sunday, this
function changes the date to the adjacent Monday
after
that, and returns this new date.
Otherwise the
given
date is returned unchanged.
The rest of the input parameters,
if
any, are simply
copied to the output.
=item *
C<(
$year
,
$month
,
$day
[,ANYTHING]) = Next_Monday_or_Tuesday(
$year
,
$month
,
$day
[,ANYTHING]);>
If the
given
date falls on a Saturday, the date of the
next
Monday (
after
that weekend) is returned.
If the
given
date falls on a Sunday, the date of the
next
Tuesday (
after
that weekend) is returned.
If the
given
date falls on a Monday, the date of the
next
Tuesday (the day
after
the Monday) is returned.
Otherwise the
given
date is returned unchanged.
The rest of the input parameters,
if
any, are simply
copied to the output.
This function is used
for
the second of two adjacent
holidays, where the first holiday obeys the "Next
Monday" rule (see the description of the function
immediately above).
Examples of this are Christmas and Boxing Day, among
others.
When the first holiday falls on Friday, the second one
falls on Saturday and is substituted by Monday.
When the first holiday falls on a Saturday, the second
one falls on Sunday, so the first holiday is substituted
by Monday and the second one by Tuesday.
When the first holiday falls on a Sunday, the second
one falls on a Monday. Therefore the first holiday is
substituted by Monday, and consequently the second
holiday must be substituted by Tuesday.
Or, in other terms:
Fri
Sat
=> Fri Mon
Sat
Sun
=> Mon Tue
Sun
Mon
=> Mon Tue
Note that there is
no
filter subroutine yet
for
the
second of two adjacent holidays
when
the first holiday
obeys the
"Nearest Workday"
rule (see the function
described immediately below), i.e.,
Fri
Sat
=> Fri Mon
Sat
Sun
=> Fri Mon
Sun
Mon
=> Mon Tue
This is left as an excercise to the inclined reader. C<:-)>
=item *
C<(
$year
,
$month
,
$day
[,ANYTHING]) = Nearest_Workday(
$year
,
$month
,
$day
[,ANYTHING]);>
If the
given
date falls on a Saturday, this function
returns the date of the Friday on the day
before
.
If the
given
date falls on a Sunday, this function
returns the date of the Monday on the day
after
.
Otherwise the
given
date is returned unchanged.
The rest of the input parameters,
if
any, are simply
copied to the output.
=item *
C<(
$year
,
$month
,
$day
[,ANYTHING]) = Sunday_to_Monday(
$year
,
$month
,
$day
[,ANYTHING]);>
If the
given
date falls on a Sunday, this function
returns the date of the Monday on the day
after
.
Otherwise the
given
date is returned unchanged.
The rest of the input parameters,
if
any, are simply
copied to the output.
=back
The typical
use
of these filter subroutines is in a
"return"
statement at the end of callback functions of your own,
when
you already have calculated the holiday in question and only
need to adjust it according to the rule implemented by the
filter subroutine in question.
See also the implementation of the Date::Calendar::Profiles
module
for
examples of how to
use
these functions.
=head1 DATE FORMULA SYNTAX
- Fixed dates:
"Christmas"
=>
"24.12"
,
"Christmas"
=>
"24.12."
,
"Christmas"
=>
"24Dec"
,
"Christmas"
=>
"24.Dec"
,
"Christmas"
=>
"24Dec."
,
"Christmas"
=>
"24.Dec."
,
"Christmas"
=>
"24-12"
,
"Christmas"
=>
"24-12-"
,
"Christmas"
=>
"24-Dec"
,
"Christmas"
=>
"24-Dec-"
,
"Christmas"
=>
"12/25"
,
"Christmas"
=>
"Dec25"
,
"Christmas"
=>
"Dec/25"
,
- Dates relative to Easter Sunday:
"Ladies' Carnival"
=>
"-52"
,
"Carnival Monday"
=>
"-48"
,
"Mardi Gras"
=>
"-47"
,
"Ash Wednesday"
=>
"-46"
,
"Palm Sunday"
=>
"-7"
,
"Maundy Thursday"
=>
"-3"
,
"Good Friday"
=>
"-2"
,
"Easter Sunday"
=>
"+0"
,
"Easter Monday"
=>
"+1"
,
"Ascension"
=>
"+39"
,
"Whitsunday"
=>
"+49"
,
"Whitmonday"
=>
"+50"
,
"Corpus Christi"
=>
"+60"
,
- The 1st, 2nd, 3rd, 4th or
last
day of week:
"Thanksgiving"
=>
"4Thu11"
,
"Thanksgiving"
=>
"4/Thu/Nov"
,
"Columbus Day"
=>
"2/Mon/Oct"
,
"Columbus Day"
=>
"2/Mon/10"
,
"Columbus Day"
=>
"2/1/Oct"
,
"Columbus Day"
=>
"2/1/10"
,
"Memorial Day"
=>
"5/Mon/May"
,
- Half holidays, commemorative days:
"Christmas"
=>
":24.12."
,
"Valentine's Day"
=>
"#Feb/14"
, # not an official holiday
=head1 CALLBACK INTERFACE
The interface of the callback functions to
use
with
the
"init()"
method of the Date::Calendar::Year(3) module is
very simple:
The callback function receives two arguments
when
called,
first the year number
for
which the holiday is to be
calculated, and second the name (the
"label"
) of the
holiday in question (which serves as key in the hash
of a holiday scheme).
This second parameter allows you to
use
the same callback
function
for
different holidays, which might be more practical
(than separate callback functions)
if
for
instance you have
a set of similar holidays to calculate, like
for
instance
the four Sundays
before
Christmas (
"Advent"
).
The callback function
"Advent()"
(exported by the
Date::Calendar::Profiles module) exemplifies this
technique.
The callback function is expected to
return
a list
"C<($year,$month,$day)>"
with
the exact date of the
holiday (the year number in the output must of course
match the year number passed as parameter).
A fatal error occurs
if
the returned list does not
constitute a valid date, in the requested year.
Optionally, the callback function may
return
a fourth
value (
after
the date) containing a string, which may
be either
"#"
or
":"
.
The string
"#"
signifies that the date in question is
a purely commemorative date, i.e., that you don't get
a day off from work on that day.
The string
":"
means that the date in question is a
"half"
holiday, i.e., a day on which you get half a
day off from work.
In case the holiday in question was not observed or did
not exist in the requested year, the callback function
may also
return
an empty list. This will cause the
"init()"
method to simply drop this holiday
for
that year.
The module Date::Calendar::Profiles exports the sample
callback functions
"Advent1()"
,
"Advent2()"
,
"Advent3()"
,
"Advent4()"
and
"Advent()"
, which might assist you in
rolling your own profiles.
=head1 HOW TO ROLL YOUR OWN
Every calendar profile (holiday scheme) is a hash.
The name of the holiday (like
"Christmas"
,
for
instance)
serves as the key in this hash and must therefore be
unique (
unless
you want to
override
a
default
which was
set previously, but see below
for
more on this).
The value
for
each
key is either a string, which specifies
a simple date formula, or the reference of a callback function.
See the section
"CALLBACK INTERFACE"
above
for
a description
of the interface (in and out) of these callback functions.
See the section
"DATE FORMULA SYNTAX"
above and the description
of the
"init()"
method in L<Date::Calendar::Year(3)>
for
the
exact syntax of date formula strings.
B<BEWARE> that
if
keys
are not unique in the source code,
later entries will overwrite previous ones! I.e.,
...
"My special holiday"
=>
"01-11"
,
"My special holiday"
=>
"02-11"
,
...
will B<NOT> set two holidays of the same name, one on November
first, the other on November second, but only one, on November
second!
Therefore, in order to
use
sets of defaults and to be able
to
override
some of them, you must B<FIRST> include any hash
containing the
default
definitions, and B<THEN>
write
down
your own definitions (see also the Date::Calendar::Profiles
module
for
examples of this!), like this:
$defaults
=
{
"Holiday #1"
=>
"01-01"
,
"Holiday #2"
=>
"02-02"
,
"Holiday #3"
=>
"03-03"
};
$variant1
=
{
%$defaults
,
"Holiday #2"
=>
"09-02"
,
"Holiday #4"
=>
"04-04"
};
This is because of the way hashes work in Perl.
Now let's suppose that you want to
write
a profile containing
all your relatives
' and friends'
birthdays or anniversaries.
Simply go ahead and list them in your program, in any order
you like, as follows (
for
example):
$Birthdays
=
{
"Spouse 1971"
=>
"30.12."
,
"Wedding Day 1992"
=>
"01.09."
,
"Valentine's Day"
=>
"14.02."
,
"Son Richard 1996"
=>
"11.05."
,
"Daughter Irene 1994"
=>
"17.01."
,
"Mom 1939"
=>
"19.08."
,
"Dad 1937"
=>
"23.04."
,
"Brother Timothy 1969"
=>
"24.04."
,
"Sister Catherine 1973"
=>
"21.10."
,
"Cousin Paul 1970"
=>
"16.10."
,
"Aunt Marjorie 1944"
=>
"09.06."
,
"Uncle George 1941"
=>
"02.08."
,
"Friend Alexander 1968"
=>
"12.06."
,
};
The year numbers
after
the names are not really necessary,
but they allow us to display the person's current age. If
this year number is omitted, we simply don't display the age.
Now in order to query this birthday database, we can
use
the
following little program:
no
strict
"vars"
;
$Birthdays
=
{
...
};
@today
= Today();
$calendar
= Date::Calendar->new(
$Birthdays
);
$calendar
->year(
$today
[0] );
foreach
$key
(
@ARGV
)
{
if
(
@list
=
$calendar
->search(
$key
))
{
foreach
$date
(
@list
)
{
@labels
=
$calendar
->labels(
$date
);
$dow
=
shift
(
@labels
);
$name
=
$key
;
foreach
$person
(
@labels
)
{
if
(
index
(
lc
(
$person
),
lc
(
$key
)) >= 0)
{
$name
=
$person
;
last
;
}
}
$delta
= Delta_Days(
@today
,
$date
->date());
$age
=
''
;
if
(
$name
=~ s!\s*(\d+)\s*$!!)
{
$age
=
$today
[0] - $1;
$age
--
if
(
$delta
> 0);
$age
=
sprintf
(
" (%2d years old)"
,
$age
);
}
printf
(
"%-20.20s: %+5d days => %3.3s %2d-%3.3s-%4d%s\n"
,
$name
,
$delta
,
$dow
,
$date
->day(),
Month_to_Text(
$date
->month()),
$date
->year(),
$age
);
}
}
else
{
print
"No entry found in birthday list for '$key'!\n"
}
}