Deborah Pickett said:
This works for any Gregorian Calendar day. Apologies for the formatting; my
news client likes to wrap text and I can't be bothered turning off the
feature for one lousy post. I'll leave it as an exercise for you to
extract the day, month and year from the string to pass to this function.
If you know that the date is a valid one, you can omit the function body
from the first comment until "don't try this at home".
Well, that's some compact bit of code. A few remarks:
# weekday
# input: three scalar parameters: year, month (1-origin), day (1-origin).
Add
# which are evaluated in scalar context.
# output: scalar from 0 to 6 representing Sunday to Saturday respectively.
# returns undef if month or day is out of bounds or any input is
# not integral.
sub weekday ($$$)
If you use the prototype ($$$) the user must be told. Actually, I'd leave
it out. It gives the function non-standard behavior for no good reason.
{
my ($y,$m,$d) = @_;
# Year must be made positive for modulus to work correctly.
The problem seems to be the missing year 0. The modulo operator
works fine for negative numerators.
$y += 400 * (1 + abs int $y/400) if $y <= 0;
# Return undef if anything is out of bounds.
return undef if
Don't return undef to signal an error, simply return with no argument.
Undef may do the wrong thing when the function is called in list context.
In scalar context, the effect is the same.
$y != int $y or
$m != int $m or
$d != int $d or
$m < 1 || $m > 12 or
$d < 1 ||
$d > [[31,28,31,30,31,30,31,31,30,31,30,31]
There's a comma missing at the end of this line. Did you *retype* the
code?
[31,29,31,30,31,30,31,31,30,31,30,31]] ->
[ ((! ($y % 400) || ($y % 100)) && (! ($y % 4))) || 0 ][ $m-1 ];
The leap year calculation is duplicated below. I'd make it a subroutine.
# The calculation. Don't try this at home, kids.
(
[[6,2,2,5,0,3,5,1,4,6,2,4],[6,2,3,6,1,4,6,2,5,0,3,5]] ->
[ ((! ($y % 400) || ($y % 100)) && (! ($y % 4))) || 0 ][ $m-1 ]
+ $d
+ $y
+ int(($y-1)/4)
- int(($y-1)/100)
+ int(($y-1)/400)
Come to think of it, in the last three lines you're calculating the
number of leap years before a given year. Make *that* a subroutine,
and find if a given year is a leap year in terms of that.
Appending a revised version below. Indentation could use improvement
too, but I left that alone.
I have also used constants for the four auxiliary arrays. That actually
adds some code, but it makes the relation between the arrays explicit.
Anno
# weekday
# input: three scalar parameters: year, month (1-origin), day (1-origin).
# output: scalar from 0 to 6 representing Sunday to Saturday respectively.
# returns undef if month or day is out of bounds or any input is
# not integral.
use constant DAYS_IN_MONTH => do {
my @dim_l = my @dim_n = (31,28,31,30,31,30,31,31,30,31,30,31);
$dim_l[ 1]++; # Feb one day longer in leap years
[\ @dim_n, \ @dim_l];
};
use constant WDAY_OF_FIRST => do {
my @wof_n = ( 6); # Jan 1st is Saturday in reference year
push @wof_n, ( $wof_n[-1] + $_) % 7 for @{ DAYS_IN_MONTH->[0]};
pop @wof_n; # we generated a thirteenth
my @wof_l = ( 6);
push @wof_l, ( $wof_l[-1] + $_) % 7 for @{ DAYS_IN_MONTH->[1]};
pop @wof_l; # we have a thirteenth
[\ @wof_n, \ @wof_l]
};
sub weekday
{
my ($y,$m,$d) = @_;
# Year must be made positive (missing year 0)
$y += 400 * (1 + abs int $y/400) if $y <= 0;
# Return undef if anything is out of bounds.
return if
$y != int $y or
$m != int $m or
$d != int $d or
$m < 1 || $m > 12 or
$d < 1 || $d > DAYS_IN_MONTH -> [ is_leap_year( $y)][ $m-1 ];
# The calculation. Don't try this at home, kids.
(
WDAY_OF_FIRST -> [ is_leap_year( $y) ][ $m-1 ]
+ $d
+ $y
+ n_leap_years( $y - 1)
) % 7;
}
sub n_leap_years {
my $y = shift;
int($y/4) - int($y/100) + int($y/400);
}
sub is_leap_year {
my $y = shift;
n_leap_years( $y) - n_leap_years( $y - 1);
}