]> git.donarmstrong.com Git - deb_pkgs/libhtml-calendarmonth-perl.git/blobdiff - lib/HTML/CalendarMonth/DateTool.pm
Imported Upstream version 2.04
[deb_pkgs/libhtml-calendarmonth-perl.git] / lib / HTML / CalendarMonth / DateTool.pm
diff --git a/lib/HTML/CalendarMonth/DateTool.pm b/lib/HTML/CalendarMonth/DateTool.pm
new file mode 100644 (file)
index 0000000..702a30f
--- /dev/null
@@ -0,0 +1,483 @@
+package HTML::CalendarMonth::DateTool;
+{
+  $HTML::CalendarMonth::DateTool::VERSION = '1.26';
+}
+
+# Base class for determining what date calculation package to use.
+
+use strict;
+use warnings;
+use Carp;
+
+use File::Which qw( which );
+
+my %Toolmap = (
+  'Time::Local' => 'TimeLocal',
+  'Date::Calc'  => 'DateCalc',
+  'DateTime'    => 'DateTime',
+  'Date::Manip' => 'DateManip',
+  'ncal'        => 'Ncal',
+  'cal'         => 'Cal',
+);
+
+my %Classmap;
+$Classmap{lc $Toolmap{$_}} = $_ foreach keys %Toolmap;
+
+my($Cal_Cmd, $Ncal_Cmd);
+
+sub _toolmap {
+  shift;
+  my $str = shift;
+  my $tool = $Toolmap{$str};
+  unless ($tool) {
+    foreach (values %Toolmap) {
+      if ($str =~ /^$_$/i) {
+        $tool = $_;
+        last;
+      }
+    }
+  }
+  return unless $tool;
+  join('::', __PACKAGE__, $tool);
+}
+
+sub new {
+  my $class = shift;
+  my $self = {};
+  bless $self, $class;
+  my %parms = @_;
+  $self->{year}     = $parms{year};
+  $self->{month}    = $parms{month};
+  $self->{weeknum}  = $parms{weeknum};
+  $self->{historic} = $parms{historic};
+  if (! $self->{year}) {
+    my @dmy = $self->_dmy_now;
+    $self->{year}    = $dmy[2];
+    $self->{month} ||= $dmy[1];
+  }
+  $self->{month} ||= 1;
+  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";
+  }
+  # 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 _name {
+  my $class = shift;
+  $class = ref $class || $class;
+  lc((split(/::/, $class))[-1]);
+}
+
+sub _cal_cmd {
+  my $self = shift;
+  if (! defined $Cal_Cmd) {
+    $Cal_Cmd = which('cal') || '';
+    if ($Cal_Cmd) {
+      my @out = grep { ! /^\s*$/ } `$Cal_Cmd 9 1752`;
+      #   September 1752
+      #Su Mo Tu We Th Fr Sa
+      #       1  2 14 15 16
+      #17 18 19 20 21 22 23
+      #24 25 26 27 28 29 30
+      my @pat = (
+        qr/^\s*\S+\s+\d+$/,
+        qr/^\s*\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s*$/,
+        qr/^\s*\d+\s+\d+\s+\d+\s+\d+\s+\d+\s*$/,
+        qr/^\s*\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s*$/,
+        qr/^\s*\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s*$/,
+      );
+      if (@out == @pat) {
+        for my $i (0 .. $#out) {
+          if ($out[$i] !~ $pat[$i]) {
+            $Cal_Cmd = '';
+            last;
+          }
+        }
+      }
+      else {
+        $Cal_Cmd = '';
+      }
+    }
+  }
+  $Cal_Cmd;
+}
+
+sub _ncal_cmd {
+  my $self = shift;
+  if (! defined $Ncal_Cmd) {
+    $Ncal_Cmd = which('ncal') || '';
+    if ($Ncal_Cmd) {
+      my @out = grep { ! /^\s*$/ } map { s/^\s*//; $_ } `$Ncal_Cmd 9 1752`;
+      #    September 1752
+      #Mo    18 25
+      #Tu  1 19 26
+      #We  2 20 27
+      #Th 14 21 28
+      #Fr 15 22 29
+      #Sa 16 23 30
+      #Su 17 24
+      my @pat = (
+        qr/^\s*\S+\s+\d+$/,
+        qr/^\s*\S+\s+\d+\s+\d+\s*$/,
+        qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
+        qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
+        qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
+        qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
+        qr/^\s*\S+\s+\d+\s+\d+\s+\d+\s*$/,
+        qr/^\s*\S+\s+\d+\s+\d+\s*$/,
+      );
+      if (@out == @pat) {
+        for my $i (0 .. $#out) {
+          if ($out[$i] !~ $pat[$i]) {
+            $Ncal_Cmd = '';
+            last;
+          }
+        }
+      }
+      else {
+        $Ncal_Cmd = '';
+      }
+    }
+  }
+  $Ncal_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 _dmy_now {
+  my $self = shift;
+  my $ts = @_ ? shift : time;
+  my($d, $m, $y) = (localtime($ts))[3,4,5];
+  ++$m; $y += 1900;
+  ($d, $m, $y);
+}
+
+sub _dom_now {
+  my $self = shift;
+  my $ts = @_ ? shift : time;
+  my($d, $m, $y);
+  if ($ts =~ /^\d+$/) {
+    if (length $ts <= 2) {
+      ($d, $m, $y) = ($ts, $self->month, $self->year);
+      croak "invalid day of month (1 .. " . $self->lastday . ") '$ts'"
+        unless $ts >= 1 && $ts <= $self->lastday;
+    }
+    else {
+      ($d, $m, $y) = $self->_dmy_now($ts);
+    }
+  }
+  else {
+    ($y, $m, $d) = $ts =~ m{^(\d+)/(\d\d)/(\d\d)$};
+    croak "invalid yyyy/mm/dd date string '$ts'" unless defined $d;
+  }
+  my($cy, $cm) = ($self->year, $self->month);
+  my $first = sprintf("%04d/%02d/%02d", $cy, $cm, 1);
+  my $last  = sprintf("%04d/%02d/%02d", $cy, $cm, $self->lastday);
+  my $pivot = sprintf("%04d/%02d/%02d", $y, $m, $d);
+  return -1 if $pivot gt $last;
+  return  0 if $pivot lt $first;
+  $d;
+}
+
+sub _summon_date_class {
+  my $self = shift;
+  my @tools;
+  if (my $c = $self->datetool) {
+    eval "use $c";
+    die "invalid date tool $c : $@" if $@;
+    @tools = $c->_name;
+  }
+  else {
+    @tools = qw( timelocal datecalc datetime datemanip ncal cal );
+  }
+  my($dc, @fails);
+  for my $tool (@tools) {
+    my $method = join('_', '', lc($tool), 'fails');
+    if (my $f = $self->$method) {
+      push(@fails, [$tool, $f]);
+    }
+    else {
+      $dc = $self->_toolmap($tool);
+      last;
+    }
+  }
+  return $dc if $dc;
+  if (@tools == 1) {
+    croak "invalid date tool " . join(': ', @{$fails[0]});
+  }
+  else {
+    croak join("\n",
+      "no valid date tool found:",
+      map(sprintf("%11s: %s", @$_), @fails),
+      "\n"
+    );
+  }
+}
+
+sub _dump_tests {
+  my $self = shift;
+  print "Time::Local : ", $self->_timelocal_fails || 1, "\n";
+  print " Date::Calc : ", $self->_datecalc_fails  || 1, "\n";
+  print "   DateTime : ", $self->_datetime_fails  || 1, "\n";
+  print "Date::Manip : ", $self->_datemanip_fails || 1, "\n";
+  print "       ncal : ", $self->_ncal_fails      || 1, "\n";
+  print "        cal : ", $self->_cal_fails       || 1, "\n";
+}
+
+sub _is_julian {
+  my $self = shift;
+  my $y = $self->year;
+  $y < 1752 || ($y == 1752 && $self->month <= 9);
+}
+
+sub _timelocal_fails {
+  my $self = shift;
+  return "not installed" unless $self->_timelocal_present;
+  return "week-of-year numbering unsupported" if $self->weeknum;
+  my $y = $self->year;
+  return "only years between 1970 and 2038 supported"
+    if $y < 1970 || $y >= 2038;
+  return;
+}
+
+sub _ncal_fails {
+  my $self = shift;
+  return "command not found" unless $self->_ncal_present;
+  return "week-of-year numbering not supported prior to 1752/09"
+    if $self->weeknum && $self->_is_julian;
+  return;
+}
+
+sub _cal_fails  {
+  my $self = shift;
+  return "command not found" unless $self->_cal_present;
+  return "week-of-year numbering not supported" if $self->weeknum;
+  return;
+}
+
+sub _datecalc_fails {
+  my $self = shift;
+  return "not installed" unless $self->_datecalc_present;
+  return "historic mode prior to 1752/09 not supported"
+    if $self->historic && $self->_is_julian;
+  return;
+}
+
+sub _datetime_fails {
+  my $self = shift;
+  return "not installed" unless $self->_datetime_present;
+  return "historic mode prior to 1752/09 not supported"
+    if $self->historic && $self->_is_julian;
+  return;
+}
+
+sub _datemanip_fails {
+  my $self = shift;
+  return "not installed" unless $self->_datemanip_present;
+  return "historic mode prior to 1752/09 not supported"
+    if $self->historic && $self->_is_julian;
+  eval { require Date::Manip && Date::Manip::Date_Init() };
+  return "init failure: $@" if $@;
+  return;
+}
+
+sub _timelocal_present { eval "require Time::Local"; return !$@ }
+sub _datecalc_present  { eval "require Date::Calc";  return !$@ }
+sub _datetime_present  { eval "require DateTime";    return !$@ }
+sub _datemanip_present { eval "require Date::Manip"; return !$@ }
+sub _ncal_present      { shift->_ncal_cmd }
+sub _cal_present       { shift->_cal_cmd  };
+
+
+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 linux/unix 'ncal' or 'cal' commands. 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 the ncal or cal commands are available, use one of them
+rather than other available date modules since these utilities
+accurately handle 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. Use 'ncal'
+or 'cal', respectively, for the wrappers around those commands.
+
+=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<new()> above.
+
+=item dow1st()
+
+Returns the day of week number for the 1st of the C<year> and C<month>
+specified during the call to C<new()>. Relies on the presence of
+C<dow1st_and_lastday()>. Should be 0..6 starting with Sun.
+
+=item lastday()
+
+Returns the last day of the month for the C<year> and C<month> specified
+during the call to C<new()>. Relies on the presence of
+C<dow1st_and_lastday()>.
+
+=back
+
+=head1 Overridden methods
+
+Subclasses of this module must provide at least the C<day_epoch()> and
+C<dow1st_and_lastday()> methods.
+
+=over
+
+=item dow1st_and_lastday()
+
+Required. Provides a list containing the day of the week of the first
+day of the month (0..6 starting with Sun) 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<month> and C<year> if they are different from
+those specified in C<new()>, 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<month> and C<year> if they are
+different from those specified in C<new()>, provide the day of week
+number. (0=Sunday, 6=Saturday).
+
+=item add_days($days, $delta, $day, [$month], [$year])
+
+For a given day, and optionally C<month> and C<year> if they are
+different from those specified in C<new()>, provide a list of year,
+month, and day once C<delta> days have been added.
+
+=item week_of_year($day, [$month], [$year])
+
+For a given day, and optionally C<month> and C<year> if they are
+different from those specified in C<new()>, 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, E<lt>F<sisk@mojotoad.com>E<gt>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2010 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)