package HTML::CalendarMonth::DateTool; # Base class for determining what date calculation package to use. use strict; use Carp; use vars qw($VERSION); $VERSION = '0.01'; my $DEBUG = 0; my %Toolmap = ( 'Time::Local' => 'TimeLocal', 'Date::Calc' => 'DateCalc', 'DateTime' => 'DateTime', 'Date::Manip' => 'DateManip', 'cal' => 'Cal', ); sub toolmap { shift; my $str = shift; my $tool = $Toolmap{$str}; unless ($tool) { foreach (values %Toolmap) { if ($str =~ /^$_$/i) { $tool = $_; last; } } } return undef unless $tool; join('::', __PACKAGE__, $tool); } sub new { my $class = shift; my $self = {}; bless $self, $class; my %parms = @_; $self->{year} = $parms{year} or croak "missing year (YYYY)\n"; $self->{month} = $parms{month} or croak "missing month num (1-12)\n"; $self->{weeknum} = $parms{weeknum}; $self->{historic} = $parms{historic}; if ($parms{datetool}) { $self->{datetool} = $self->toolmap($parms{datetool}) or croak "Sorry, didn't find a tool for datetool '$parms{datetool}'\n"; } my $dc = $self->_summon_date_class; unless (eval "require $dc") { croak "Problem loading $dc ($@)\n"; } print STDERR "Using date class $dc\n" if $DEBUG; # rebless into new class bless $self, $dc; } sub year { shift->{year} } sub month { shift->{month} } sub weeknum { shift->{weeknum} } sub historic { shift->{historic} } sub datetool { shift->{datetool} } sub cal_cmd { my $self = shift; unless (exists $self->{cal_cmd}) { my $cal; foreach (qw(/usr/bin /bin /usr/local/bin)) { if (-x "$_/cal") { $cal = "$_/cal"; last; } } $self->{cal_cmd} = $cal || undef; } $self->{cal_cmd}; } sub day_epoch { # in case our subclasses are lazy my($self, $day, $month, $year) = @_; $month ||= $self->month; $year ||= $self->year; Time::Local::timegm(0,0,0,1,$month,$year); } sub skips { my $self = shift; @_ ? $self->{skips} = shift : $self->{skips}; } sub dow1st { (shift->dow1st_and_lastday)[0] } sub lastday { (shift->dow1st_and_lastday)[1] } sub _summon_date_class { my $self = shift; return $self->datetool if $self->datetool; my $dc; if ( $self->_test_for_timelocal ) { $dc = __PACKAGE__ . '::TimeLocal'; } elsif ( $self->_test_for_cal ) { $dc = __PACKAGE__ . '::Cal'; } elsif ( $self->_test_for_datecalc ) { $dc = __PACKAGE__ . '::DateCalc'; } elsif ( $self->_test_for_datetime ) { $dc = __PACKAGE__ . '::DateTime'; } elsif( $self->_test_for_datemanip ) { $dc = __PACKAGE__ . '::DateManip'; } else { croak <<__NOTOOL; No valid date mechanism found. Install Date::Calc, DateTime, or Date::Manip, or try using a date between 1970 and 2038 so that Time::Local can be used. __NOTOOL } $dc; } sub _dump_tests { my $self = shift; print "Time::Local : ", $self->_test_for_timelocal, "\n"; print " cal : ", $self->_test_for_cal, "\n"; print " Date::Calc : ", $self->_test_for_datecalc, "\n"; print " DateTime : ", $self->_test_for_datetime, "\n"; print "Date::Manip : ", $self->_test_for_datemanip, "\n"; } sub _test_for_timelocal { my $self = shift; my $year = $self->year; my $weeknum = $self->weeknum; !$weeknum && eval "require Time::Local" && (!defined $year || (($year >= 1970) && ($year < 2038))); } sub _test_for_cal { my $self = shift; my $weeknum = $self->weeknum; my $historic = $self->historic; my $cal = $self->cal_cmd; !$weeknum && $historic && $cal; } sub _test_for_datecalc { eval "require Date::Calc"; return !$@ } sub _test_for_datetime { eval "require DateTime"; return !$@ } sub _test_for_datemanip { eval "require Date::Manip"; return !$@ } 1; __END__ =head1 NAME HTML::CalendarMonth::DateTool - Base class for determining which date package to use for calendrical calculations. =head1 SYNOPSIS my $date_tool = HTML::CalendarMonth::DateTool->new( year => $YYYY_year, month => $one_thru_12_month, weeknum => $weeknum_mode, historic => $historic_mode, datetool => $specific_datetool_if_desired, ); =head1 DESCRIPTION This module attempts to utilize the best date calculation package available on the current system. For most contemporary dates this usually ends up being the internal Time::Local package of perl. For more exotic dates, or when week number of the years are desired, other methods are attempted including DateTime, Date::Calc, Date::Manip, and the unix 'cal' command. Each of these has a specific subclass of this module offering the same utility methods needed by HTML::CalendarMonth. =head1 METHODS =over =item new() Constructor. Takes the following parameters: =over =item year Year of calendar in question (required). If you are rendering exotic dates (i.e. dates outside of 1970 to 2038) then something besides Time::Local will be used for calendrical calculations. =item month Month of calendar in question (required). 1 through 12. =item weeknum Optional. When specified, will limit class excursions to those that are currently set up for week of year calculations. =item historic Optional. If the 'cal' command is available, use it rather than other available date modules since the 'cal' command accurately handles some specific historical artifacts such as the transition from Julian to Gregorian. =item datetool Optional. Mostly for debugging, this option can be used to indicate a specific HTML::CalendarMonth::DateTool subclass for instantiation. The value can be either the actual utility class, e.g., Date::Calc, or the name of the CalendarMonth handler leaf class, e.g. DateCalc. For the 'cal' command, use 'cal'. =back =back There are number of methods automatically available: =over =item month() =item year() =item weeknum() =item historical() =item datetool() Accessors for the parameters provided to C above. =item dow1st() Returns the day of week number for the 1st of the C and C specified during the call to C. Relies on the presence of C. =item lastday() Returns the last day of the month for the C and C specified during the call to C. Relies on the presence of C. =back =head1 Overridden methods Subclasses of this module must provide at least the C and C methods. =over =item dow1st_and_lastday() Required. Provides a list containing the day of the week of the first day of the month along with the last day of the month. =item day_epoch() Optional unless interested in epoch values for wacky dates. For a given day, and optionally C and C if they are different from those specified in C, provide the unix epoch in seconds for that day at midnight. =back If the subclass is expected to provide week of year numbers, three more methods are necessary: =over =item dow() For a given day, and optionally C and C if they are different from those specified in C, provide the day of week number. (1=Sunday, 6=Saturday). =item add_days($days, $delta, $day, [$month], [$year]) For a given day, and optionally C and C if they are different from those specified in C, provide a list of year, month, and day once C days have been added. =item week_of_year($day, [$month], [$year]) For a given day, and optionally C and C if they are different from those specified in C, provide a list with the week number of the year along with the year. (some days of a particular year can end up belonging to the prior or following years). =back =head1 AUTHOR Matthew P. Sisk, EFE =head1 COPYRIGHT Copyright (c) 2005 Matthew P. Sisk. All rights reserved. All wrongs revenged. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO HTML::CalendarMonth(3), Time::Local(3), DateTime(3), Date::Calc(3), Date::Manip(3), cal(1)