]> git.donarmstrong.com Git - deb_pkgs/libhtml-calendarmonth-perl.git/blob - lib/HTML/CalendarMonth.pm
425f9c7518706c5bc72788453570e1124411b1b0
[deb_pkgs/libhtml-calendarmonth-perl.git] / lib / HTML / CalendarMonth.pm
1 package HTML::CalendarMonth;
2 BEGIN {
3   $HTML::CalendarMonth::VERSION = '1.25';
4 }
5
6 use strict;
7 use warnings;
8 use Carp;
9
10 BEGIN { $HTML::CalendarMonth::VERSION = 1.25 }
11
12 use HTML::ElementTable 1.18;
13 use HTML::CalendarMonth::Locale;
14 use HTML::CalendarMonth::DateTool;
15
16 use base qw( Class::Accessor HTML::ElementTable );
17
18 my %Objects;
19
20 # default complex attributes
21 my %Calmonth_Attrs = (
22   head_m      => 1,     # month heading mode
23   head_y      => 1,     # year heading mode
24   head_dow    => 1,     # DOW heading mode
25   head_week   => 0,     # weak of year
26   year_span   => 2,     # default col span of year
27
28   today       => undef, # DOM, if not now
29   week_begin  => 1,     # what DOW (1-7) is the 1st DOW?
30
31   historic    => 1,     # if able to choose, use ncal/cal
32                         # rather than Date::Calc, which
33                         # blindly extrapolates Gregorian
34
35   alias       => {},    # what gets displayed if not
36                         # the default item
37
38   month       => undef, # these will get initialized
39   year        => undef,
40
41   locale      => 'en_US',
42   full_days   => 0,
43   full_months => 1,
44
45   datetool    => undef,
46
47   enable_css   => 1,
48   semantic_css => 0,
49
50   # internal muckety muck
51   _cal      => undef,
52   _itoch    => {},
53   _ctoih    => {},
54   _caltool  => undef,
55   _weeknums => undef,
56   _today    => undef,
57
58   dow1st   => undef,
59   lastday  => undef,
60   loc      => undef,
61
62   # deprecated
63   row_offset => undef,
64   col_offset => undef,
65 );
66
67 __PACKAGE__->mk_accessors(keys %Calmonth_Attrs);
68
69 # Class::Accessor overrides
70
71 sub set {
72   my($self, $key) = splice(@_, 0, 2);
73   if (@_ == 1) {
74     $Objects{$self}{$key} = $_[0];
75   }
76   elsif (@_ > 1) {
77     $Objects{$self}{$key} = [@_];
78   }
79   else {
80     Carp::confess("wrong number of arguments received");
81   }
82 }
83
84 sub get {
85   my $self = shift;
86   if (@_ == 1) {
87     return $Objects{$self}{$_[0]};
88   }
89   elsif ( @_ > 1 ) {
90     return @{$Objects{$self}{@_}};
91   }
92   else {
93     Carp::confess("wrong number of arguments received.");
94   }
95 }
96
97 sub _is_calmonth_attr { shift; exists $Calmonth_Attrs{shift()} }
98
99 sub _set_defaults {
100   my $self = shift;
101   foreach (keys %Calmonth_Attrs) {
102     $self->$_($Calmonth_Attrs{$_});
103   }
104   $self;
105 }
106
107 sub DESTROY { delete $Objects{shift()} }
108
109 # last dow col, first week row
110
111 use constant LDC => 6;
112 use constant FWR => 2;
113
114 # alias
115
116 sub item_alias {
117   my($self, $item) = splice(@_, 0, 2);
118   defined $item or croak "item name required";
119   $self->alias->{$item} = shift if @_;
120   $self->alias->{$item} || $item;
121 }
122
123 sub item_aliased {
124   my($self, $item) = splice(@_, 0, 2);
125   defined $item or croak "item name required.\n";
126   defined $self->alias->{$item};
127 }
128
129 # header toggles
130
131 sub _head {
132   # Set/test entire heading (month,year,and dow headers) (does not
133   # affect week number column). Return true if either heading active.
134   my $self = shift;
135   $self->head_m(@_) && $self->head_dow(@_) if @_;
136   $self->_head_my || $self->head_dow;
137 }
138
139 sub _head_my {
140   # Set/test month and year header mode
141   my($self, $mode) = splice(@_, 0, 2);
142   $self->head_m($mode) && $self->head_y($mode) if defined $mode;
143   $self->head_m || $self->head_y;
144 }
145
146 sub _initialized {
147   my $self = shift;
148   @_ ? $self->{_initialized} = shift : $self->{_initialized};
149 }
150
151 # circa interface
152
153 sub _date {
154   # set target month, year
155   my $self = shift;
156   if (@_) {
157     my ($month, $year) = @_;
158     $month && defined $year || croak "date method requires month and year";
159     croak "Date already set" if $self->_initialized();
160
161     # get rid of possible leading 0's
162     $month += 0;
163     $year  += 0;
164
165     $month <= 12 && $month >= 1 or croak "Month $month out of range (1-12)\n";
166     $year > 0 or croak "Negative years are unacceptable\n";
167
168     $self->month($self->monthname($month));
169     $self->year($year);
170     $month = $self->monthnum($month);
171
172     # trigger _gencal...this should be the only place where this occurs
173     $self->_gencal;
174   }
175   return($self->month, $self->year);
176 }
177
178 # class factory access
179
180 use constant CLASS_HET      => 'HTML::ElementTable';
181 use constant CLASS_DATETOOL => 'HTML::CalendarMonth::DateTool';
182 use constant CLASS_LOCALE   => 'HTML::CalendarMonth::Locale';
183
184 sub _gencal {
185   # generate internal calendar representation
186   my $self = shift;
187
188   # new calendar...clobber day-specific settings
189   my $itoc = $self->_itoch({});
190   my $ctoi = $self->_ctoih({});
191
192   # figure out dow of 1st day of the month as well as last day of the
193   # month (uses date calculator backends)
194   $self->_anchor_month();
195
196   # row count for weeks in grid
197   my $wcnt = 0;
198
199   my ($dowc) = $self->dow1st;
200   my $skips  = $self->_caltool->_skips;
201
202   # for each day
203   foreach (1 .. $self->lastday) {
204     next if $skips->{$_};
205     my $r = $wcnt + FWR;
206     my $c = $dowc;
207     # this is a bootstrap until we know the number of rows in the month.
208     $itoc->{$_} = [$r, $c];
209     $dowc = ++$dowc % 7;
210     ++$wcnt unless $dowc || $_ == $self->lastday;
211   }
212
213   $self->{_week_rows} = $wcnt;
214
215   my $row_extent = $wcnt + FWR;
216   my $col_extent = LDC;
217   $col_extent += 1 if $self->head_week;
218
219   $self->SUPER::extent($row_extent, $col_extent);
220
221   # table can contain the days now, so replace our bootstrap coordinates
222   # with references to the actual elements.
223   foreach (keys %$itoc) {
224     my $cellref = $self->cell(@{$itoc->{$_}});
225     $self->_itoc($_, $cellref);
226     $self->_ctoi($cellref, $_);
227   }
228
229   # week num affects month/year spans
230   my $width = $self->head_week ? 8 : 7;
231
232   # month/year headers
233   my $cellref = $self->cell(0, 0);
234   $self->_itoc($self->month, $cellref);
235   $self->_ctoi($cellref, $self->month);
236   $cellref = $self->cell(0, $width - $self->year_span);
237   $self->_itoc($self->year,  $cellref);
238   $self->_ctoi($cellref, $self->year);
239
240   $self->item($self->month)->replace_content($self->item_alias($self->month));
241   $self->item($self->year)->replace_content($self->item_alias($self->year));
242
243   if ($self->_head_my) {
244     if ($self->head_m) {
245       $self->item($self->month)->attr('colspan',$width - $self->year_span);
246     }
247     else {
248       $self->item($self->month)->mask(1);
249       $self->item($self->year)->attr('colspan', $width);
250     }
251     if ($self->head_y) {
252       $self->item($self->year)->attr('colspan',$self->year_span);
253     }
254     else {
255       $self->item($self->year)->mask(1);
256       $self->item($self->month)->attr('colspan', $width);
257     }
258   }
259   else {
260     $self->row(0)->mask(1);
261   }
262
263   # DOW headers
264   my $trans;
265   my $days = $self->loc->days;
266   foreach (0..$#$days) {
267     # Transform for week_begin 1..7
268     $trans = ($_ + $self->week_begin - 1) % 7;
269     my $cellref = $self->cell(1, $_);
270     $self->_itoc($days->[$trans], $cellref);
271     $self->_ctoi($cellref, $days->[$trans]);
272   }
273   if ($self->head_dow) {
274     grep($self->item($_)->replace_content($self->item_alias($_)), @$days);
275   }
276   else {
277     $self->row(1)->mask(1);
278   }
279
280   # week number column
281   if ($self->head_week) {
282     # week nums can collide with days. Use "w" in front of the number
283     # for uniqueness, and automatically alias to just the number (unless
284     # already aliased, of course).
285     $self->_gen_week_nums();
286     my $ws;
287     my $row_count = FWR;
288     foreach ($self->_numeric_week_nums) {
289       $ws = "w$_";
290       $self->item_alias($ws, $_) unless $self->item_aliased($ws);
291       my $cellref = $self->cell($row_count, $self->last_col);
292       $self->_itoc($ws, $cellref);
293       $self->_ctoi($cellref, $ws);
294       $self->item($ws)->replace_content($self->item_alias($ws));
295       ++$row_count;
296     }
297   }
298
299   # fill in days of the month
300   my $i;
301   foreach my $r (FWR .. $self->last_row) {
302     foreach my $c (0 .. LDC) {
303       $self->cell($r,$c)->replace_content($self->item_alias($i))
304         if ($i = $self->item_at($r,$c));
305     }
306   }
307
308   # css classes
309   if ($self->enable_css) {
310     $self                             ->push_attr(class => 'hcm-table'     );
311     $self->item_row($self->dayheaders)->push_attr(class => 'hcm-day-head'  );
312     $self->item    ($self->year)      ->push_attr(class => 'hcm-year-head' );
313     $self->item    ($self->month)     ->push_attr(class => 'hcm-month-head');
314     $self->item    ($self->week_nums) ->push_attr(class => 'hcm-week-head' )
315       if $self->head_week;
316   }
317
318   if ($self->semantic_css) {
319     my $today = $self->today;
320     if ($today < 0) {
321       $self->item($self->days)->push_attr(class => 'hcm-past');
322     }
323     elsif ($today == 0) {
324       $self->item($self->days)->push_attr(class => 'hcm-future');
325     }
326     else {
327       for my $d ($self->days) {
328         if ($d < $today) {
329           $self->item($d)->push_attr(class => 'hcm-past');
330         }
331         elsif ($d > $today) {
332           $self->item($d)->push_attr(class => 'hcm-future');
333         }
334         else {
335           $self->item($d)->push_attr(class => 'hcm-today');
336         }
337       }
338     }
339   }
340
341   $self;
342 }
343
344 sub default_css {
345   my $hbgc = '#DDDDDD';
346   my $bc   = '#888888';
347
348   my $str = <<__CSS;
349 <style type="text/css">
350   <!--
351
352   table.hcm-table {
353     border: thin solid $bc;
354     border-collapse: collapse;
355     text-align: right;
356   }
357
358   .hcm-table td, th {
359     padding-left:  2px;
360     padding-right: 2px;
361   }
362
363   .hcm-year-head  {
364     text-align: right;
365     background-color: $hbgc;
366   }
367
368   .hcm-month-head {
369     text-align: left;
370     background-color: $hbgc;
371   }
372
373   .hcm-day-head   {
374     text-align: right;
375     background-color: $hbgc;
376     border-bottom: thin solid $bc;
377    }
378
379   .hcm-week-head  {
380     font-size: small;
381     background-color: $hbgc;
382     border-left: thin solid $bc;
383   }
384
385 -->
386 </style>
387 __CSS
388
389 }
390
391 sub _datetool {
392   my $self = shift;
393   my $ct;
394   if (! ($ct = $self->_caltool)) {
395     $ct = $self->_caltool(CLASS_DATETOOL->new(
396       year     => $self->year,
397       month    => $self->month,
398       weeknum  => $self->head_week,
399       historic => $self->historic,
400       datetool => $self->datetool,
401     ));
402   }
403   $ct;
404 }
405
406 sub _anchor_month {
407   # Figure out what our month grid looks like.
408   # Let HTML::CalendarMonth::DateTool determine which method is
409   # appropriate.
410   my $self = shift;
411
412   my $month = $self->monthnum($self->month);
413   my $year  = $self->year;
414
415   my $tool = $self->_datetool;
416
417   my $dow1st  = $tool->dow1st; # 0..6, starting with Sun
418   my $lastday = $tool->lastday;
419
420   # week_begin given as 1..7 starting with Sun
421   $dow1st = ($dow1st - ($self->week_begin - 1)) % 7;
422
423   $self->dow1st($dow1st);
424   $self->lastday($lastday);
425
426   $self;
427 }
428
429 sub _gen_week_nums {
430   # Generate week-of-the-year numbers. The first week is generally
431   # agreed upon to be the week that contains the 4th of January.
432   #
433   # For purposes of shenanigans with 'week_begin', we anchor the week
434   # number off of Thursday in each row.
435
436   my $self = shift;
437
438   my($year, $month, $lastday) = ($self->year, $self->monthnum, $self->lastday);
439
440   my $tool = $self->_caltool;
441   croak "Oops. " . ref $tool . " not set up for week of year calculations.\n"
442     unless $tool->can('week_of_year');
443
444   my $fdow = $self->dow1st;
445   my $delta = 4 - $fdow;
446   if ($delta < 0) {
447     $delta += 7;
448   }
449   my @ft = $tool->add_days($delta, 1);
450
451   my $ldow = $tool->dow($lastday);
452   $delta = 4 - $ldow;
453   if ($delta > 0) {
454     $delta -= 7;
455   }
456   my @lt = $tool->add_days($delta, $lastday);
457
458   my $fweek = $tool->week_of_year(@ft);
459   my $lweek = $tool->week_of_year(@lt);
460   my @wnums = $fweek > $lweek ? ($fweek, 1 .. $lweek) : ($fweek .. $lweek);
461
462   # do we have days above our first Thursday?
463   if ($self->row_of($ft[0]) != FWR) {
464     unshift(@wnums, $wnums[0] -1);
465   }
466
467   # do we have days below our last Thursday?
468   if ($self->row_of($lt[0]) != $self->last_row) {
469     push(@wnums, $wnums[-1] + 1);
470   }
471
472   # first visible week is from last year
473   if ($wnums[0] == 0) {
474     $wnums[0] = $tool->week_of_year($tool->add_days(-7, $ft[0]));
475   }
476
477   # last visible week is from subsequent year
478   if ($wnums[-1] > $lweek) {
479     $wnums[-1] = $tool->week_of_year($tool->add_days(7, $lt[0]));
480   }
481
482   $self->_weeknums(\@wnums);
483 }
484
485 # month hooks
486
487 sub row_items {
488   # given a list of items, return all items in rows shared by the
489   # provided items.
490   my $self = shift;
491   my %items;
492   foreach my $item (@_) {
493     my $row = ($self->coords_of($item))[0];
494     foreach my $col (0 .. $self->last_col) {
495       my $i = $self->item_at($row, $col) || next;
496       ++$items{$i};
497     }
498   }
499   keys %items > 1 ? keys %items : (keys %items)[0];
500 }
501
502 sub col_items {
503   # return all item cells in the columns occupied by the provided list
504   # of items.
505   my $self = shift;
506   $self->_col_items(0, $self->last_row, @_);
507 }
508
509 sub daycol_items {
510   # same as col_items(), but excludes header cells.
511   my $self = shift;
512   $self->_col_items(FWR, $self->last_row, @_);
513 }
514
515 sub _col_items {
516   # given row bounds and a list of items, return all item elements
517   # in the columns occupied by the provided items. Does not return
518   # empty cells.
519   my($self, $rfirst, $rlast) = splice(@_, 0, 3);
520   my %items;
521   my($item, $row, $col, %i);
522   foreach my $item (@_) {
523     my $col = ($self->coords_of($item))[1];
524     foreach my $row ($rfirst .. $rlast) {
525       my $i = $self->item_at($row,$col) || next;
526       ++$items{$i};
527     }
528   }
529   keys %items > 1 ? keys %items : (keys %items)[0];
530 }
531
532 sub daytime {
533   # return seconds since epoch for a given day
534   my($self, $day) = splice(@_, 0, 2);
535   $day or croak "must specify day of month";
536   croak "day does not exist" unless $self->_daycheck($day);
537   $self->_caltool->day_epoch($day);
538 }
539
540 sub week_nums {
541   # return list of all week number labels
542   my @wnums = map("w$_", shift->_numeric_week_nums);
543   wantarray ? @wnums : \@wnums;
544 }
545
546 sub _numeric_week_nums {
547   # return list of all week numbers as numbers
548   my $self = shift;
549   return unless $self->head_week;
550   wantarray ? @{$self->_weeknums} : $self->_weeknums;
551 }
552
553 sub days {
554   # return list of all days of the month (1..$c->lastday).
555   my $self = shift;
556   my $skips = $self->_caltool->_skips;
557   my @days = grep { !$skips->{$_} } (1 .. $self->lastday);
558   wantarray ? @days : \@days;
559 }
560
561 sub dayheaders {
562   # return list of all day headers (Su..Sa).
563   shift->loc->days;
564 }
565
566 sub headers {
567   # return list of all headers (month,year,dayheaders)
568   my $self = shift;
569   wantarray ? ($self->year, $self->month, $self->dayheaders)
570             : [$self->year, $self->month, $self->dayheaders];
571 }
572
573 sub items {
574   # return list of all items (days, headers)
575   my $self = shift;
576   wantarray ? ($self->headers, $self->days)
577             : [$self->headers, $self->days];
578 }
579
580 sub last_col {
581   # what's the max col of the calendar?
582   my $self = shift;
583   $self->head_week ? LDC + 1 : LDC;
584 }
585
586 sub last_day_col { LDC }
587
588 sub last_row {
589   # last row of the calendar
590   my $self = shift;
591   return ($self->coords_of($self->lastday))[0];
592 }
593
594 *last_week_row = \&last_row;
595
596 sub first_week_row { FWR };
597
598 sub past_days {
599   my $self  = shift;
600   my $today = $self->_today;
601   if ($today < 0) {
602     return $self->days;
603   }
604   elsif ($today == 0) {
605     return;
606   }
607   return(1 .. $today);
608 }
609
610 sub future_days {
611   my $self  = shift;
612   my $today = $self->_today;
613   if ($today < 0) {
614     return;
615   }
616   elsif ($today == 0) {
617     return $self->days;
618   }
619   return($today .. $self->last_day);
620 }
621
622 # custom glob interfaces
623
624 sub item {
625   # return TD elements containing items
626   my $self = shift;
627   @_ || croak "item(s) must be provided";
628   $self->cell(grep(defined $_, map($self->coords_of($_), @_)));
629 }
630
631 sub item_row {
632   # return a glob of the rows of a list of items, including empty cells.
633   my $self = shift;
634   $self->row(map { $self->row_of($_) } @_);
635 }
636
637 sub item_day_row {
638   # same as item_row, but excludes possible week number cells
639   my $self = shift;
640   return $self->item_row(@_) unless $self->head_week;
641   my(%rows, @coords);
642   for my $r (map { $self->row_of($_) } @_) {
643     next if ++$rows{$r} > 1;
644     for my $c (0 .. 6) {
645       push(@coords, ($r, $c));
646     }
647   }
648   $self->cell(@coords);
649 }
650
651 sub item_week_nums {
652   # glob of all week numbers
653   my $self = shift;
654   $self->item($self->week_nums);
655 }
656
657 sub item_col {
658   # return a glob of the cols of a list of items, including empty cells.
659   my $self = shift;
660   $self->_item_col(0, $self->last_row, @_);
661 }
662
663 sub item_daycol {
664   # same as item_col(), but excludes header cells.
665   my $self = shift;
666   $self->_item_col(2, $self->last_row, @_);
667 }
668
669 sub _item_col {
670   # given row bounds and a list of items, return a glob representing
671   # the cells in the columns occupied by the provided items, including
672   # empty cells.
673   my($self, $rfirst, $rlast) = splice(@_, 0, 3);
674   defined $rfirst && defined $rlast or Carp::confess "No items provided";
675   my(%seen, @coords);
676   foreach my $col (map { $self->col_of($_) } @_) {
677     next if ++$seen{$col} > 1;
678     foreach my $row ($rfirst .. $rlast) {
679       push(@coords, $row, $col);
680     }
681   }
682   $self->cell(@coords);
683 }
684
685 sub item_box {
686   # return a glob of the box defined by two items
687   my($self, $item1, $item2) = splice(@_, 0, 3);
688   defined $item1 && defined $item2 or croak "Two items required";
689   $self->box($self->coords_of($item1), $self->coords_of($item2));
690 }
691
692 sub all {
693   # return a glob of all calendar cells, including empty cells.
694   my $self = shift;
695   $self->box( 0,0 => $self->last_row, $self->last_col );
696 }
697
698 sub alldays {
699   # return a glob of all cells other than header cells
700   my $self = shift;
701   $self->box( 2, 0 => $self->last_row, 6 );
702 }
703
704 sub allheaders {
705   # return a glob of all header cells
706   my $self = shift;
707   $self->item($self->headers);
708 }
709
710 # transformation Methods
711
712 sub coords_of {
713   # convert an item into grid coordinates
714   my $self = shift;
715   croak "undefined value passed to coords_of()" if @_ && ! defined $_[0];
716   my $ref = $self->_itoc(@_);
717   my @pos = ref $ref ? $ref->position : ();
718   @pos ? (@pos[$#pos - 1, $#pos]) : ();
719 }
720
721 sub item_at {
722   # convert grid coords into item
723   my $self = shift;
724   $self->_ctoi($self->cell(@_));
725 }
726
727 sub _itoc {
728   # item to grid
729   my($self, $item, $ref) = splice(@_, 0, 3);
730   defined $item or croak "item required";
731   my $itoc = $self->_itoch;
732   if ($ref) {
733     croak "Reference required" unless ref $ref;
734     $itoc->{$item} = $ref;
735   }
736   $itoc->{$item};
737 }
738
739 sub _ctoi {
740   # cell reference to item
741   my($self, $refstring, $item) = splice(@_, 0, 3);
742   defined $refstring or croak "cell id required";
743   my $ctoi = $self->_ctoih;
744   if (defined $item) {
745     $ctoi->{$refstring} = $item;
746   }
747   $ctoi->{$refstring};
748 }
749
750 sub row_of {
751   my $self = shift;
752   ($self->coords_of(@_))[0];
753 }
754
755 sub col_of {
756   my $self = shift;
757   ($self->coords_of(@_))[1];
758 }
759
760 sub monthname {
761   # check/return month...returns name. Accepts month number or string.
762   my $self = shift;
763   return $self->month unless @_;
764   my $loc = $self->loc;
765   my @names;
766   for my $m (@_) {
767     $m = ($m - 1) % 12 if $m && $m =~ /^\d+$/;
768     $m = $loc->monthname($m) || croak "month not found " . join(', ', @_);
769     return $m if @_ == 1;
770     push(@names, $m);
771   }
772   @names;
773 }
774
775 sub monthnum {
776   # check/return month, returns number. Accepts month number or string.
777   my $self   = shift;
778   my @months = @_ ? @_ : $self->month;
779   my $loc = $self->loc;
780   my @nums;
781   for my $m (@months) {
782     $m = ($m - 1) % 12 if $m && $m =~ /^\d+$/;
783     $m = $loc->monthnum($m);
784     croak "month not found ", join(', ', @_) unless defined $m;
785     $m += 1;
786     return $m if @_ == 1;
787     push(@nums, $m);
788   }
789   @nums;
790 }
791
792 sub dayname {
793   # check/return day...returns name. Accepts 1..7, or Su..Sa
794   my $self = shift;
795   @_ || croak "day string or num required";
796   my $loc = $self->loc;
797   my @names;
798   for my $d (@_) {
799     if ($d =~ /^\d+$/) {
800       $d = (($d - 1) % 7) + $self->week_begin - 1;
801     }
802     $d = $loc->dayname($d) || croak "day not found ", join(', ', @_);
803     return $d if @_ == 1;
804     push(@names, $d);
805   }
806   @names;
807 }
808
809 sub daynum {
810   # check/return day number 1..7, returns number. Accepts 1..7,
811   # or Su..Sa
812   my $self = shift;
813   @_ || croak "day string or num required";
814   my $loc  = $self->loc;
815   my @nums;
816   for my $d (@_) {
817     if ($d =~ /^\d+$/) {
818       $d = (($d - 1) % 7) + $self->week_begin - 1;
819     }
820     $d = $loc->daynum($d);
821     croak "day not found ", join(', ', @_) unless defined $d;
822     $d += 1;
823     return $d if @_ == 1;
824     push(@nums, $d);
825   }
826   @nums;
827 }
828
829 # tests-n-checks
830
831 sub _dayheadcheck {
832   # test day head names
833   my($self, $name) = splice(@_, 0, 2);
834   $name or croak "name missing";
835   return if $name =~ /^\d+$/;
836   $self->daynum($name);
837 }
838
839 sub _daycheck {
840   # check if an item is a day of the month (1..31)
841   my($self, $item) = splice(@_, 0, 2);
842   croak "item required" unless $item;
843   # can't just invert _headcheck because coords_of() needs _daycheck,
844   # and _headcheck uses coords_of()
845   $item =~ /^\d{1,2}$/ && $item <= 31;
846 }
847
848 sub _headcheck {
849   # check if an item is a header
850   !_daycheck(@_);
851 }
852
853 # constructors/destructors
854
855 sub new {
856   my $class = shift;
857   my %parms = @_;
858   my(%attrs, %tattrs);
859   foreach (keys %parms) {
860     if (__PACKAGE__->_is_calmonth_attr($_)) {
861       $attrs{$_} = $parms{$_};
862     }
863     else {
864       $tattrs{$_} = $parms{$_};
865     }
866   }
867
868   my $self = CLASS_HET->new(%tattrs);
869   bless $self, $class;
870
871   # set defaults
872   $self->_set_defaults;
873
874   my $month = delete $attrs{month};
875   my $year  = delete $attrs{year};
876   if (!$month || !$year) {
877     my ($nmonth,$nyear) = (localtime(time))[4,5];
878     ++$nmonth; $nyear += 1900;
879     $month ||= $nmonth;
880     $year  ||= $nyear;
881   }
882   $self->month($month);
883   $self->year($year);
884
885   # set overrides
886   for my $k (keys %attrs) {
887     $self->$k($attrs{$k}) if defined $attrs{$k};
888   }
889
890   my $loc = CLASS_LOCALE->new(
891     id          => $self->locale,
892     full_days   => $self->full_days,
893     full_months => $self->full_months,
894   ) or croak "Problem creating locale " . $self->locale . "\n";
895   $self->loc($loc);
896
897   my $dt = CLASS_DATETOOL->new(
898       year     => $self->year,
899       month    => $self->month,
900       weeknum  => $self->head_week,
901       historic => $self->historic,
902       datetool => $self->datetool,
903   );
904   $self->_caltool($dt);
905
906   $self->week_begin($loc->first_day_of_week + 1)
907     unless defined $attrs{week_begin};
908
909   my $dom_now = defined $attrs{today} ? $dt->_dom_now(delete $attrs{today})
910                                       : $dt->_dom_now;
911   $self->_today($dom_now);
912   $self->today($dom_now) if $dom_now > 0;
913
914   my $alias = $attrs{alias} || {};
915   if ($self->full_days < 0) {
916     my @full   = $self->loc->days;
917     my @narrow = $self->loc->narrow_days;
918     for my $i (0 .. $#narrow) {
919       $alias->{$full[$i]} = $narrow[$i];
920     }
921   }
922   if ($self->full_months < 0) {
923     my @full   = $self->loc->months;
924     my @narrow = $self->loc->narrow_months;
925     for my $i (0 .. $#narrow) {
926       $alias->{$full[$i]} = $narrow[$i];
927     }
928   }
929   $self->alias($alias) if keys %$alias;
930
931   # for now, this is the only time this will every happen for this
932   # object. It is now 'initialized'.
933   $self->_date($month, $year);
934
935   $self;
936 }
937
938 ### overrides (our table is static)
939
940 sub extent { }
941 sub maxrow { shift->SUPER::maxrow }
942 sub maxcol { shift->SUPER::maxcol }
943
944 ### deprecated
945
946 use constant row_offset     => 0;
947 use constant col_offset     => 0;
948 use constant first_col      => 0;
949 use constant first_row      => 0;
950 use constant first_week_col => 0;
951 use constant last_week_col  => 6;
952
953 ###
954
955 1;
956
957 __END__
958
959 =head1 NAME
960
961 HTML::CalendarMonth - Generate and manipulate HTML calendar months
962
963 =head1 SYNOPSIS
964
965  use HTML::CalendarMonth;
966
967  # Using regular HTML::Element creation
968  my $c = HTML::CalendarMonth->new( month => 8, year => 2010 );
969  print $c->as_HTML;
970
971  # Full locale support via DateTime::Locale
972  my $c2 = HTML::CalendarMonth->new(
973    month  => 8,
974    year   => 2010,
975    locale => 'zu_ZA'
976  );
977  print $c2->as_HTML;
978
979  # Full locale support via DateTime::Locale
980  $c3 = HTML::CalendarMonth->new( month => 8, year => 79, locale => 'fr' );
981  print $c3->as_HTML
982
983  # HTML-Tree integration
984  my $tree = HTML::TreeBuilder->parse_file('cal.html');
985  $tree->find_by_attribute(class => 'hcm-calendar')->replace_with($c);
986  print $tree->as_HTML;
987
988  # clean up if you're not done, HTML::Element structures must be
989  # manually destroyed
990  $c->delete; $c2->delete;
991
992 =head1 DESCRIPTION
993
994 HTML::CalendarMonth is a subclass of HTML::ElementTable. See
995 L<HTML::ElementTable(3)> for how that class works, for it affects this
996 module on many levels. Like HTML::ElementTable, HTML::CalendarMonth is
997 an enhanced HTML::Element with methods added to facilitate the
998 manipulation of the calendar table elements as a whole.
999
1000 The primary interaction with HTML::CalendarMonth is through I<items>
1001 rather than cell coordinates like HTML::ElementTable uses. An I<item> is
1002 merely a string that represents the content of the cell of interest
1003 within the calendar. For instance, the element representing the 14th day
1004 of the month would be returned by C<$c-E<gt>item(14)>. Similarly, the
1005 element representing the header for Monday would be returned by C<$c-
1006 E<gt>item('Mo')>. If the year happened to by 2010, then C<$c-
1007 E<gt>item(2010)> would return the cell representing the year. Since
1008 years and particular months change frequently, it is probably more
1009 useful to take advantage of the C<month()> and C<year()> methods, which
1010 return their respective values. The following is therefore the same as
1011 explicitly referencing the year: C<$c-E<gt>item($c- E<gt>year())>.
1012
1013 Multiple cells of the calendar can be manipulated as if they were a
1014 single element. For instance, C<$c-E<gt>item(15)-E<gt>attr(class =E<gt>
1015 'fancyday')> would alter the class of the cell representing the 15th. By
1016 the same token, C<$c-E<gt>item(15, 16, 17,
1017 23)-E<gt>attr(class =E<gt> 'fancyday')> would do the same thing for all
1018 cells containing the days passed to the C<item()> method.
1019
1020 Underneath, the calendar is still nothing more than a table structure,
1021 the same as provided by the HTML::ElementTable class. In addition to the
1022 I<item> based access methods above, calendar cells can still be accessed
1023 using row and column grid coordinates using the C<cell()> method
1024 provided by the table class. All coordinate-based methods in the table
1025 class are accessible to the calendar class.
1026
1027 The module includes support for week-of-the-year numbering, arbitrary
1028 1st day of the week definitions, and locale support.
1029
1030 Dates that are beyond the range of the built-in time functions of perl
1031 are handled either by the ncal/cal command, Date::Calc, DateTime, or
1032 Date::Manip. The presence of any one of these utilities and modules will
1033 suffice for these far flung date calculations. One of these utilities
1034 (with the exception of 'cal') is also required if you want to use week-of-
1035 year numbering.
1036
1037 Full locale support is offered via DateTime::Locale. For a full list of
1038 supported locale id's, look at HTML::CalendarMonth::Locale->locales().
1039
1040 =head1 METHODS
1041
1042 All arguments appearing in [brackets] are optional, and do not represent
1043 anonymous array references.
1044
1045 =head2 Constructor
1046
1047 =over
1048
1049 =item new()
1050
1051 With no arguments, the constructor will return a calendar object
1052 representing the current month with a default appearance. The initial
1053 configuration of the calendar is controlled by special attributes. Non-
1054 calendar related attributes are passed along to HTML::ElementTable. Any
1055 non-table related attributes left after that are passed to HTML::Element
1056 while constructing the E<lt>tableE<gt> tag. See L<HTML::ElementTable> if
1057 you are interested in attributes that can be passed along to that class.
1058
1059 Special Attributes for HTML::CalendarMonth:
1060
1061 =over
1062
1063 =item month
1064
1065 1-12, or Jan-Dec.  Defaults to current month.
1066
1067 =item year
1068
1069 Four digit representation. Defaults to current year.
1070
1071 =item head_m
1072
1073 Specifies whether to display the month header. Default 1.
1074
1075 =item head_y 
1076
1077 Specifies whether to display the year header. Default 1.
1078
1079 =item head_dow
1080
1081 Specifies whether to display days of the week header. Default 1.
1082
1083 =item locale
1084
1085 Specifies the id of the locale in which to render the calendar. Default
1086 is 'en_US'. By default, this will also control determine which day is
1087 considered to be the first day of the week. See
1088 L<HTML::CalendarMonth::Locale> for more information. If for some reason
1089 you prefer to use different labels than those provided by C<locale>, see
1090 the C<alias> attribute below.
1091
1092 =item full_days
1093
1094 Specifies whether or not to use full day names or their abbreviated
1095 names. Default is 0, use abbreviated names. Use -1 for 'narrow' mode,
1096 the shortest (not guaranteed to be unique) abbreviations.
1097
1098 =item full_months
1099
1100 Specifies whether or not to use full month names or their abbreviated
1101 names. Default is 1, use full names. Use -1 for 'narrow' mode, the
1102 shortest (not guaranteed to be unique) abbreviations.
1103
1104 =item alias
1105
1106 Takes a hash reference mapping labels provided by C<locale> to any
1107 custom label you prefer. Lookups, such as C<day('Sun')>, will still use
1108 the locale string, but when the calendar is rendered the aliased value
1109 will appear.
1110
1111 =item head_week
1112
1113 Specifies whether to display the week-of-year numbering. Default 0.
1114
1115 =item week_begin
1116
1117 Specify first day of the week, which can be 1..7, starting with Sunday.
1118 In order to specify Monday, set this to 2, and so on. By default, this
1119 is determined based on the locale.
1120
1121 =item enable_css
1122
1123 Set some handy CSS class attributes on elements, enabled by default.
1124 Currently the classes are:
1125
1126   hcm-table       Set on the <lt>table<gt> tag of the calendar
1127   hcm-day-head    Set on the day-of-week <lt>tr<gt> or <lt>td<gt> tags
1128   hcm-year-head   Set on the <lt>td<gt> tag for the year
1129   hcm-month-head  Set on the <lt>td<gt> tag for the month
1130   hcm-week-head   Set on the <lt>td<gt> tags for the week-of-year
1131
1132 =item semantic_css
1133
1134 Sets some additional CSS class attributes on elements, disabled by
1135 default. The notion of 'today' is taken either from the system clock
1136 (default) or from the 'today' parameter as provided to new(). Currently
1137 these classes are:
1138
1139   hcm-today    Set on the <lt>td<gt> tag for today, if present
1140   hcm-past     Set on the <lt>td<gt> tags for prior days, if present
1141   hcm-future   Set on the <lt>td<gt> tags for subsequent days, if present
1142
1143 =item today
1144
1145 Specify the value for 'today' if different from the local time as
1146 reported by the system clock (the default). If specified as two or less
1147 digits, it is assumed to be one of the days of the month in the current
1148 calendar. If more than two digits, it is assumed to be a epoch time in
1149 seconds. Otherwise it must be given as a string of the form 'YYYY-mm-
1150 dd'. Note that the default value as determined by the system clock uses
1151 localtime rather than gmtime.
1152
1153 =item historic
1154
1155 This option is ignored for dates that do not exceed the range of the built-
1156 in perl time functions. For dates that B<do> exceed these ranges, this
1157 option specifies the default calculation method. When set, if the 'ncal'
1158 or 'cal' command is available on your system, that will be used rather
1159 than the Date::Calc or Date::Manip modules. This can be an issue since
1160 the date modules blindly extrapolate the Gregorian calendar, whereas
1161 ncal/cal will revert to the Julian calendar during September 1752. If
1162 either ncal or cal are not available on your system, this attribute is
1163 meaningless. Defaults to 1.
1164
1165 =back
1166
1167 =back
1168
1169 =head2 Item Query Methods
1170
1171 The following methods return lists of item *symbols* (28, 29, 'Thu',
1172 ...) that are related in some way to the provided list of items. The
1173 returned symbols may then be used as arguments to the glob methods
1174 detailed further below.
1175
1176 =over
1177
1178 =item row_items(item1, [item2, ...])
1179
1180 Returns all item symbols in rows shared by the provided item symbols.
1181
1182 =item col_items(item1, [item2, ...])
1183
1184 Returns all item symbols in columns shared by the provided item symbols.
1185
1186 =item daycol_items(col_item1, [col_item2, ...])
1187
1188 Same as col_items(), but the returned item symbols are limited to those
1189 that are not header items (month, year, day-of-week).
1190
1191 =item row_of(item1, [item2, ...])
1192
1193 Returns the row indices of rows containing the provided item symbols.
1194
1195 =item col_of(item1, [item2, ...])
1196
1197 Returns the column indices of columns containing the provided
1198 item symbols.
1199
1200 =item lastday()
1201
1202 Returns the day number (symbol) of the last day of the month.
1203
1204 =item dow1st()
1205
1206 Returns the column index for the first day of the month.
1207
1208 =item days()
1209
1210 Returns a list of all days of the month as numbers.
1211
1212 =item week_nums()
1213
1214 Returns a list of week-of-year numbers for this month.
1215
1216 =item dayheaders()
1217
1218 Returns a list of all day headers (Su..Sa)
1219
1220 =item headers()
1221
1222 Returns a list of all headers (month, year, dayheaders)
1223
1224 =item items()
1225
1226 Returns a list of all item symbols (day number, header values) in
1227 the calendar.
1228
1229 =item last_col()
1230
1231 Returns the index of the last column of the calendar (note that this
1232 could be the week-of-year column if head_week is enabled).
1233
1234 =item last_day_col()
1235
1236 Returns the index of the last column of the calendar containing days of
1237 the month (same as last_col() unless week-of-year is enabled).
1238
1239 =item first_week_row()
1240
1241 Returns the index of the first row of the calendar containing day items
1242 (ie, the first week).
1243
1244 =item last_row()
1245
1246 Returns the index of the last row of the calendar.
1247
1248 =item today()
1249
1250 Returns the day of month for 'today', if present in the current
1251 calendar.
1252
1253 =item past_days()
1254
1255 Returns a list of days prior to 'today'. If 'today' is in a future
1256 month, all days are returned. If 'today' is in a past month, no days
1257 are returned.
1258
1259 =item future_days()
1260
1261 Returns a list of days after 'today'. If 'today' is in a past
1262 month, all days are returned. If 'today' is in a future month, no
1263 days are returned.
1264
1265 =back
1266
1267 =head2 Glob Methods
1268
1269 Glob methods return references that are functionally equivalent to an
1270 individual calendar cell. Mostly, they provide item based analogues to
1271 the glob methods provided in HTML::ElementTable. In methods dealing with
1272 rows, columns, and boxes, the globs include empty calendar cells (which
1273 would otherwise need to be accessed through native HTML::ElementTable
1274 methods). The row and column numbers returned by the item methods above
1275 are compatible with the grid based methods in HTML::ElementTable.
1276
1277 For details on how these globs work, check out L<HTML::ElementTable> and
1278 L<HTML::ElementGlob>.
1279
1280 =over
1281
1282 =item item(item1, [item2, ...])
1283
1284 Returns all cells containing the provided item symbols.
1285
1286 =item item_row(item1, [item2, ...])
1287
1288 Returns all cells in all rows occupied by the provided item symbols.
1289
1290 =item item_day_row(item1, [item2, ...])
1291
1292 Same as item_row() except excludes week-of-year cells, if present.
1293
1294 =item item_col(item1, [item2, ...])
1295
1296 Returns all cells in all columns occupied by the provided item symbols.
1297
1298 =item item_daycol(item1, [item2, ...])
1299
1300 Same as item_col() except limits the cells to non header cells.
1301
1302 =item item_week_nums()
1303
1304 Returns all week-of-year cells, if present.
1305
1306 =item item_box(item1a, item1b, [item2a, item2b, ...])
1307
1308 Returns all cells in the boxes defined by the item pairs provided.
1309
1310 =item allheaders()
1311
1312 Returns all header cells.
1313
1314 =item alldays()
1315
1316 Returns all non header cells, including empty cells.
1317
1318 =item all()
1319
1320 Returns all cells in the calendar, including empty cells.
1321
1322 =back
1323
1324 =head2 Transformation Methods
1325
1326 The following methods provide ways of translating between various item
1327 symbols, coordinates, and other representations.
1328
1329 =over
1330
1331 =item coords_of(item)
1332
1333 Returns the row and column coordinates of the provided item symbol, for
1334 use with the grid based methods in HTML::ElementTable.
1335
1336 =item item_at(row,column)
1337
1338 Returns the item symbol of the item at the provided coordinates, for use
1339 with the item based methods of HTML::CalendarMonth.
1340
1341 =item monthname(monthnum)
1342
1343 Returns the name (item symbol) of the month number provided, where
1344 I<monthnum> can be 1..12.
1345
1346 =item monthnum(monthname)
1347
1348 Returns the number (1..12) of the month name provided. Only a minimal
1349 case-insensitive match on the month name is necessary; the proper item
1350 symbol for the month will be determined from this match.
1351
1352 =item dayname(daynum)
1353
1354 Returns the name (item symbol) of the day of week header for a number of
1355 a day of the week, where I<daynum> is 1..7.
1356
1357 =item daynum(dayname)
1358
1359 Returns the number of the day of the week given the symbolic name for
1360 that day (Su..Sa).
1361
1362 =item daytime(day)
1363
1364 Returns the number in seconds since the epoch for a given day. The day
1365 must be present in the current calendar.
1366
1367 =back
1368
1369 =head2 Other Methods
1370
1371 =over
1372
1373 =item default_css()
1374
1375 Returns a simple style sheet as a string that can be used in an HTML
1376 document in conjunction with the classes assigned to elements when css
1377 is enabled.
1378
1379 =back
1380
1381 =head1 REQUIRES
1382
1383 HTML::ElementTable
1384
1385 =head1 OPTIONAL
1386
1387 Date::Calc, DateTime, or Date::Manip (only if you want week-of-
1388 year numbering or non-contemporary dates on a system without the
1389 I<cal> command)
1390
1391 =head1 AUTHOR
1392
1393 Matthew P. Sisk, E<lt>F<sisk@mojotoad.com>E<gt>
1394
1395 =head1 COPYRIGHT
1396
1397 Copyright (c) 1998-2010 Matthew P. Sisk. All rights reserved. All wrongs
1398 revenged. This program is free software; you can redistribute it and/or
1399 modify it under the same terms as Perl itself.
1400
1401 =head1 SEE ALSO
1402
1403 A useful page of examples can be found at
1404 http://www.mojotoad.com/sisk/projects/HTML-CalendarMonth.
1405
1406 For information on iso639 standards for abbreviations for language
1407 names, see http://www.loc.gov/standards/iso639-2/englangn.html
1408
1409 HTML::ElementTable(3), HTML::Element(3), perl(1)
1410
1411 =for Pod::Coverage col_offset row_offset item_alias item_aliased last_week_row