File Coverage

blib/lib/Date/Tolkien/Shire/Data.pm
Criterion Covered Total %
statement 327 337 97.0
branch 131 168 77.9
condition 47 66 71.2
subroutine 73 73 100.0
pod n/a
total 578 644 89.7


line stmt bran cond sub pod time code
1             package Date::Tolkien::Shire::Data;
2              
3 21     21   338677 use 5.006002;
  21         70  
4              
5 21     21   114 use strict;
  21         63  
  21         640  
6 21     21   91 use warnings;
  21         40  
  21         1096  
7              
8 21     21   9822 use charnames qw{ :full };
  21         179525  
  21         140  
9              
10 21     21   372750 use Carp ();
  21         36  
  21         262  
11 21     21   8616 use POSIX ();
  21         114749  
  21         579  
12 21     21   12670 use Text::Abbrev();
  21         1005  
  21         549  
13              
14             # We can't use 'use Exporter qw{ import }' because we need to run under
15             # Perl 5.6.2, and since as I write this the Perl porters are working on
16             # a security flaw in 'use base', I'm doing a Paleolithic subclass.
17 21     21   102 use Exporter ();
  21         37  
  21         5163  
18             our @ISA = qw{ Exporter };
19              
20             our $VERSION = '0.010_01';
21              
22             our @EXPORT_OK = qw{
23             __am_or_pm
24             __date_to_day_of_year
25             __day_of_year_to_date
26             __day_of_week
27             __format
28             __is_leap_year
29             __holiday_abbr __holiday_name __holiday_narrow
30             __holiday_name_to_number
31             __month_name __month_name_to_number __month_abbr
32             __on_date __on_date_accented
33             __quarter __quarter_name __quarter_abbr
34             __rata_die_to_year_day
35             __search_on_date
36             __trad_weekday_abbr __trad_weekday_name __trad_weekday_narrow
37             __valid_date_class
38             __weekday_abbr __weekday_name __weekday_narrow
39             __week_of_year
40             __year_day_to_rata_die
41             DAY_OF_YEAR_MIDYEARS_DAY
42             DAY_OF_YEAR_OVERLITHE
43             GREGORIAN_RATA_DIE_TO_SHIRE
44             HOLIDAY_2_YULE
45             HOLIDAY_1_LITHE
46             HOLIDAY_MIDYEARS_DAY
47             HOLIDAY_OVERLITHE
48             HOLIDAY_2_LITHE
49             HOLIDAY_1_YULE
50             };
51             our %EXPORT_TAGS = (
52             all => \@EXPORT_OK,
53             subs => [ grep { m/ \A __ /smx } @EXPORT_OK ],
54             consts => [ grep { m/ \A [[:upper:]] /smx } @EXPORT_OK ],
55             );
56              
57 21     21   150 use constant ARRAY_REF => ref [];
  21         30  
  21         2489  
58 21     21   102 use constant CODE_REF => ref sub {};
  21         29  
  21         1094  
59 21     21   118 use constant HASH_REF => ref {};
  21         29  
  21         1107  
60 21     21   80 use constant REGEX_REF => ref qr/foo/;
  21         25  
  21         775  
61              
62 21     21   75 use constant DAY_OF_YEAR_MIDYEARS_DAY => 183;
  21         29  
  21         589  
63 21     21   67 use constant DAY_OF_YEAR_OVERLITHE => 184;
  21         26  
  21         555  
64              
65 21     21   89 use constant HOLIDAY_2_YULE => 1;
  21         58  
  21         572  
66 21     21   64 use constant HOLIDAY_1_LITHE => 2;
  21         42  
  21         512  
67 21     21   57 use constant HOLIDAY_MIDYEARS_DAY => 3;
  21         30  
  21         473  
68 21     21   60 use constant HOLIDAY_OVERLITHE => 4;
  21         26  
  21         516  
69 21     21   61 use constant HOLIDAY_2_LITHE => 5;
  21         27  
  21         532  
70 21     21   68 use constant HOLIDAY_1_YULE => 6;
  21         22  
  21         610  
71              
72             # See the documentation below for where the value came from.
73              
74 21     21   69 use constant GREGORIAN_RATA_DIE_TO_SHIRE => 1995694;
  21         26  
  21         104707  
75              
76             {
77             my @name = qw{ AM PM };
78              
79             my $validate = _make_validator( qw{ UInt } );
80              
81             sub __am_or_pm {
82 21     21   37 my ( $hour ) = $validate->( @_ );
83 21 50       65 return $name[ $hour < 12 ? 0 : 1 ];
84             }
85             }
86              
87             {
88              
89             my @holiday = ( undef, 1, 7, 0, 0, 1, 7 );
90             my @month_zero = ( undef, 0, 2, 4, 6, 1, 3, 0, 2, 4, 6, 1, 3 );
91              
92             my $validate = _make_validator( qw{ UInt UInt } );
93              
94             sub __day_of_week {
95 411     411   525415 my ( $month, $day ) = $validate->( @_ );
96 411 100       1093 $month
97             or return $holiday[$day];
98 376         1485 return ( $month_zero[$month] + $day ) % 7 + 1;
99             }
100             }
101              
102             {
103             my @holiday_day = ( undef, 1, 182, 183, DAY_OF_YEAR_OVERLITHE, 185, 366 );
104             my @month_zero = ( undef, 1, 31, 61, 91, 121, 151, 185, 215, 245,
105             275, 305, 335 );
106              
107             my $validate_d2doy = _make_validator( qw{ UInt UInt UInt } );
108              
109             sub __date_to_day_of_year {
110 734     734   987844 my ( $year, $month, $day ) = $validate_d2doy->( @_ );
111              
112 734 100       2205 my $yd = $month ? $month_zero[$month] + $day :
113             $holiday_day[$day];
114              
115 734 100       1864 unless ( __is_leap_year( $year ) ) {
116 368 50 66     939 not $month
117             and HOLIDAY_OVERLITHE == $day
118             and Carp::croak( 'Overlithe only occurs in a leap year' );
119 368 100       797 $yd >= DAY_OF_YEAR_OVERLITHE
120             and --$yd;
121             }
122 734         2129 return $yd;
123             }
124              
125             my $validate_doy2d = _make_validator( qw{ UInt UInt } );
126              
127             sub __day_of_year_to_date {
128 731     731   488134 my ( $year, $yd ) = $validate_doy2d->( @_ );
129              
130 731 100       1999 unless ( __is_leap_year( $year ) ) {
131 365 100       873 $yd >= DAY_OF_YEAR_OVERLITHE
132             and $yd++;
133             }
134 731 50 33     2809 $yd > 0
135             and $yd <= 366
136             or Carp::croak( "Invalid year day $yd" );
137              
138 731         1938 for ( my $day = 1; $day < @holiday_day; $day++ ) {
139 4358 100       10877 $yd == $holiday_day[$day]
140             and return ( 0, $day );
141             }
142              
143 720         1452 $yd -= 2;
144 720 100       1528 $yd > 180
145             and $yd -= 4;
146 720         1233 my $day = $yd % 30;
147 720         1640 my $month = ( $yd - $day ) / 30;
148 720         3049 return ( $month + 1, $day + 1 );
149             }
150             }
151              
152             {
153              
154             my $validate = _make_validator( qw{ Hash|Object Scalar } );
155              
156             sub __format {
157 282     282   219738 my ( $date, $tplt ) = $validate->( @_ );
158              
159 282         674 $date = _make_date_object( $date );
160              
161 282         611 my $ctx = {
162             prefix_new_line_unless_empty => 0,
163             };
164              
165 282         2001 $tplt =~ s/ % (?: [{] ( \w+ ) [}] # method ($1)
166             | [{]{2} ( .*? ) [}]{2} # condition ($2)
167             | ( [-_0^#]* ) ( [0-9]* ) ( [EO]? . ) # conv spec ($3,$4,$5)
168             ) /
169 378 50       1490 $1 ? ( $date->can( $1 ) ? $date->$1() : "%{$1}" ) :
    100          
    100          
170             $2 ? _fmt_cond( $date, $2 ) :
171             _fmt_conv( $date, $5, $3, $4, $ctx )
172             /smxeg;
173              
174 282         1500 return $tplt;
175             }
176             }
177              
178             sub _fmt_cond {
179 30     30   74 my ( $date, $tplt ) = @_;
180 30         151 my @cond = split qr< [|]{2} >smx, $tplt;
181 30         64 foreach my $inx ( 1, 2 ) {
182 60 100 100     184 defined $cond[$inx]
183             and '' ne $cond[$inx]
184             or $cond[$inx] = undef;
185             }
186              
187 30         49 my $inx = 0;
188 30 100 100     78 defined $cond[1]
189             and not $date->__fmt_shire_month()
190             and $inx = 1;
191 30 100 100     75 defined $cond[2]
192             and not __day_of_week( $date->__fmt_shire_month(), $date->__fmt_shire_day() )
193             and $inx = 2;
194              
195 30         58 return __format( $date, $cond[$inx] );
196             }
197              
198             {
199             # NOTE - I _was_ using assignment to $_[2] followed by a goto to
200             # dispatch _fmt_number__2() and _fmt_number_02(). But this produced
201             # test failures under 5.8.5, which I was able to reproduce, though
202             # not under -d:ptkdb, which suggests it was an optimizer problem.
203             # Only _fmt_number__2() resulted in the failures, but I recoded
204             # both, plus the couple dispatches directly to _fmt_number() since
205             # the previous dispatch scheme for all three involved fiddling with
206             # the contents of @_. There is still a goto inside _fmt_number__2(),
207             # but since I no longer modify @_, I have let that stand.
208             my %spec = (
209             A => sub { $_[0]->__fmt_shire_traditional() ?
210             __trad_weekday_name( $_[0]->__fmt_shire_day_of_week() ) :
211             __weekday_name( $_[0]->__fmt_shire_day_of_week() );
212             },
213             a => sub { $_[0]->__fmt_shire_traditional() ?
214             __trad_weekday_abbr( $_[0]->__fmt_shire_day_of_week() ) :
215             __weekday_abbr( $_[0]->__fmt_shire_day_of_week() );
216             },
217             B => sub { __month_name( $_[0]->__fmt_shire_month() ) },
218             b => sub { __month_abbr( $_[0]->__fmt_shire_month() ) },
219             C => sub {
220             return _fmt_number_02( @_[ 0, 1 ],
221             int( $_[0]->__fmt_shire_year() / 100 ) );
222             },
223             c => sub { __format( $_[0], '%{{%a %x||||%x}} %X' ) },
224             D => sub { __format( $_[0], '%{{%m/%d||%Ee}}/%y' ) },
225             d => sub {
226             return _fmt_number_02( @_[ 0, 1 ],
227             $_[0]->__fmt_shire_day() );
228             },
229             Ea => sub { $_[0]->__fmt_shire_traditional() ?
230             __trad_weekday_narrow( $_[0]->__fmt_shire_day_of_week() ) :
231             __weekday_narrow( $_[0]->__fmt_shire_day_of_week() );
232             },
233             Ed => \&_fmt_on_date,
234             EE => sub { __holiday_name( $_[0]->__fmt_shire_month() ? 0 :
235             $_[0]->__fmt_shire_day() ) },
236             Ee => sub { __holiday_abbr( $_[0]->__fmt_shire_month() ? 0 :
237             $_[0]->__fmt_shire_day() ) },
238             El => sub { __format( $_[0], '%{{%b %d||%Ee}}' ) },
239             EL => sub { __format( $_[0], '%{{%B %d||%EE}}' ) },
240             En => sub { $_[1]{prefix_new_line_unless_empty}++; '' },
241             Eo => sub { __holiday_narrow( $_[0]->__fmt_shire_month() ? 0 :
242             $_[0]->__fmt_shire_day() ) },
243             Ex => sub { __format( $_[0],
244             '%{{%A %-e %B %Y||%A %EE %Y||%EE %Y}}' ) },
245             e => sub {
246             return _fmt_number__2( @_[ 0, 1 ],
247             $_[0]->__fmt_shire_day() );
248             },
249             F => sub { __format( $_[0], '%Y-%{{%m-%d||%Ee}}' ) },
250             # G Same as Y by definition of Shire calendar
251             H => sub {
252             return _fmt_number_02( @_[ 0, 1 ],
253             $_[0]->__fmt_shire_hour() );
254             },
255             # h Same as b by definition of strftime()
256             I => sub {
257             return _fmt_number_02( @_[ 0, 1 ],
258             ( $_[0]->__fmt_shire_hour() || 0 ) % 12 || 12,
259             );
260             },
261             j => sub {
262             defined $_[1]{wid}
263             or $_[1]{wid} = 3;
264             return _fmt_number( @_[ 0, 1 ],
265             __date_to_day_of_year(
266             $_[0]->__fmt_shire_year(),
267             $_[0]->__fmt_shire_month(),
268             $_[0]->__fmt_shire_day(),
269             ),
270             );
271             },
272             k => sub {
273             return _fmt_number__2( @_[ 0, 1 ],
274             $_[0]->__fmt_shire_hour() );
275             },
276             l => sub {
277             return _fmt_number__2( @_[ 0, 1 ],
278             ( $_[0]->__fmt_shire_hour() || 0 ) % 12 || 12 );
279             },
280             M => sub {
281             return _fmt_number_02( @_[ 0, 1 ],
282             $_[0]->__fmt_shire_minute() );
283             },
284             m => sub {
285             return _fmt_number_02( @_[ 0, 1 ],
286             $_[0]->__fmt_shire_month() );
287             },
288             N => sub {
289             defined $_[1]{wid}
290             or $_[1]{wid} = 9;
291             return _fmt_number( @_[ 0, 1 ],
292             $_[0]->__fmt_shire_nanosecond(),
293             );
294             },
295             n => sub { "\n" },
296             P => sub { lc __am_or_pm( $_[0]->__fmt_shire_hour() ) },
297             p => sub { uc __am_or_pm( $_[0]->__fmt_shire_hour() ) },
298             R => sub { __format( $_[0], '%H:%M' ) },
299             r => sub { __format( $_[0], '%I:%M:%S %p' ) },
300             S => sub {
301             return _fmt_number_02( @_[ 0, 1 ],
302             $_[0]->__fmt_shire_second() );
303             },
304             s => sub { $_[0]->__fmt_shire_epoch() },
305             T => sub { __format( $_[0], '%H:%M:%S' ) },
306             t => sub { "\t" },
307             U => sub {
308             return _fmt_number_02( @_[ 0, 1 ],
309             __week_of_year(
310             $_[0]->__fmt_shire_month(),
311             $_[0]->__fmt_shire_day(),
312             ),
313             );
314             },
315             u => sub { $_[0]->__fmt_shire_day_of_week() },
316             # V Same as U by definition of Shire calendar
317             v => sub { __format( $_[0], '%{{%e-%b-%Y||%Ee-%Y}}' ) },
318             # W Same as U by definition of Shire calendar
319             # X Same as r, I think
320             x => sub { __format( $_[0], '%{{%e %b %Y||%Ee %Y}}' ) },
321             Y => sub { $_[0]->__fmt_shire_year() },
322             y => sub {
323             return _fmt_number_02( @_[ 0, 1 ],
324             $_[0]->__fmt_shire_year() % 100 );
325             },
326             Z => sub { $_[0]->__fmt_shire_zone_name() },
327             z => sub { _fmt_offset( $_[0]->__fmt_shire_zone_offset() ) },
328             '%' => sub { '%' },
329             );
330             $spec{G} = $spec{Y}; # By definition of Shire calendar.
331             $spec{h} = $spec{b}; # By definition of strftime().
332             $spec{V} = $spec{U}; # By definition of Shire calendar.
333             $spec{W} = $spec{U}; # By definition of Shire calendar.
334             $spec{w} = $spec{u}; # Because the strftime() definition of
335             # %w makes no sense to me in terms of
336             # the Shire calendar.
337             $spec{X} = $spec{r}; # I think this is right ...
338             $spec{'{'} = $spec{'}'} = $spec{'|'} = $spec{'%'};
339              
340             my %modifier_map = (
341             0 => sub { $_[0]{pad} = '0' },
342             '-' => sub { $_[0]{pad} = '' },
343             _ => sub { $_[0]{pad} = ' ' },
344             '^' => sub { $_[0]{uc} = 1 },
345             '#' => sub { $_[0]{change_case} = 1 },
346             );
347              
348             my %case_change = map { $_ => sub { uc $_[0] } }
349             qw{ A a B b EE Ee h };
350             $case_change{p} = $case_change{Z} = sub { lc $_[0] };
351              
352             # Note that if I should choose to implement field widths, the width,
353             # if specified, causes padding with spaces if '-' (no padding) was
354             # specified.
355              
356             sub _fmt_conv {
357 345     345   1223 my ( $date, $conv, $mod, $wid, $ctx ) = @_;
358 345 50       689 defined $mod
359             or $mod = '';
360             $wid
361 345 100       564 and $ctx->{wid} = $wid;
362 345         438 my $code;
363 345         1300 foreach my $char ( split qr{}, $mod ) {
364 26 50       84 $code = $modifier_map{$char}
365             and $code->( $ctx );
366             }
367 345 100       686 if ( $wid ) {
368 4         5 $ctx->{wid} = $wid;
369             defined $ctx->{pad}
370             and '' eq $ctx->{pad}
371 4 100 100     13 and $ctx->{pad} = ' ';
372             }
373 345         436 my $rslt;
374 345 100 33     698 if ( $code = $spec{$conv} ) {
    50          
375 342         589 $rslt = $code->( $date, $ctx );
376             } elsif ( 1 < length $conv and $code = $spec{ substr $conv, 1 } ) {
377 3         8 $rslt = $code->( $date, $ctx );
378             } else {
379 0         0 $rslt = "%$mod$wid$conv";
380             }
381 345 100       573 defined $rslt
382             or $rslt = '';
383 345 100 66     678 if ( delete $ctx->{change_case} and $code = $case_change{$conv} ) {
384 6         8 delete $ctx->{uc};
385 6         10 $rslt = $code->( $rslt );
386             }
387             delete $ctx->{uc}
388 345 100       543 and $rslt = uc $rslt;
389 345         363 my $need;
390             $ctx->{wid}
391             and '' ne $ctx->{pad}
392             and ( $need = $ctx->{wid} - length $rslt ) > 0
393 345 100 100     942 and $rslt = ( $ctx->{pad} x $need ) . $rslt;
      100        
394 345         407 delete @{ $ctx }{ qw{ pad wid } };
  345         542  
395 345         1129 return $rslt;
396             }
397             }
398              
399             sub _fmt_number {
400 116     116   230 my ( undef, $ctx, $val ) = @_; # Invocant unused
401             defined $ctx->{pad}
402 116 100       236 or $ctx->{pad} = '0';
403             defined $ctx->{wid}
404 116 100       250 or $ctx->{wid} = 2;
405 116 50       283 return defined $val ? "$val" : '0';
406             }
407              
408             *_fmt_number_02 = \&_fmt_number;
409              
410             sub _fmt_number__2 {
411             defined $_[1]{pad}
412 14 100   14   37 or $_[1]{pad} = ' ';
413 14         36 goto &_fmt_number;
414             }
415              
416             sub _fmt_offset {
417 3     3   4 my ( $offset ) = @_;
418 3 100 66     16 defined $offset
419             and $offset =~ m/ \A [+-]? [0-9]+ \z /smx
420             or return '';
421 1 50       3 my $sign = $offset < 0 ? '-' : '+';
422 1         3 $offset = abs $offset;
423 1         2 my $sec = $offset % 60;
424 1         6 $offset = POSIX::floor( ( $offset - $sec ) / 60 );
425 1         2 my $min = $offset % 60;
426 1         3 my $hr = POSIX::floor( ( $offset - $min ) / 60 );
427 1 50       6 return $sec ?
428             sprintf( '%s%02d%02d%02d', $sign, $hr, $min, $sec ) :
429             sprintf( '%s%02d%02d', $sign, $hr, $min );
430             }
431              
432             sub _fmt_on_date {
433 24     24   50 my ( $date, $ctx ) = @_;
434 24         54 my $pfx = "\n" x $ctx->{prefix_new_line_unless_empty};
435 24         41 $ctx->{prefix_new_line_unless_empty} = 0;
436 24         56 my $month = $date->__fmt_shire_month();
437 24         46 my $day = $date->__fmt_shire_day();
438 24 100       43 defined( my $on_date = $date->__fmt_shire_accented() ?
    100          
439             __on_date_accented( $month, $day ) :
440             __on_date( $month, $day ) )
441             or return undef; ## no critic (ProhibitExplicitReturnUndef)
442 16         47 return "$pfx$on_date";
443             }
444              
445             {
446             my @name = ( '',
447             '2Yu', '1Li', 'Myd', 'Oli', '2Li', '1Yu',
448             );
449              
450             sub __holiday_abbr {
451 43     43   227372 return _lookup( $_[0], \@name );
452             }
453             }
454              
455             {
456             my @name = ( '',
457             '2 Yule', '1 Lithe', q, 'Overlithe', '2 Lithe',
458             '1 Yule',
459             );
460              
461             sub __holiday_name {
462 49     49   726798 return _lookup( $_[0], \@name );
463             }
464              
465             }
466              
467             {
468             my @name = ( '',
469             '2Y', '1L', 'My', 'Ol', '2L', '1Y',
470             );
471              
472             sub __holiday_narrow {
473 10     10   210592 return _lookup( $_[0], \@name );
474             }
475             }
476              
477             {
478             # This code needs to come after both __holiday_name() and
479             # __holiday_abbr(), because it calls them both and needs the name
480             # arrays to be set up.
481             my $lookup = _make_lookup_hash(
482             __holiday_name(),
483             __holiday_abbr(),
484             );
485              
486             my $validate = _make_validator( qw{ Scalar } );
487              
488             sub __holiday_name_to_number {
489 8     8   30 my ( $holiday ) = _normalize_for_lookup(
490             $validate->( @_ ) );
491              
492 8 50       37 $holiday =~ m/ \A [0-9]+ \z /smx
493             and return $holiday;
494 8   100     59 return $lookup->{$holiday} || 0;
495             }
496             }
497              
498             {
499             my $validate = _make_validator( qw{ UInt } );
500              
501             sub __is_leap_year {
502 1871     1871   846375 my ( $year ) = $validate->( @_ );
503 1871 100       7313 return $year % 4 ? 0 : $year % 100 ? 1 : $year % 400 ? 0 : 1;
    100          
    100          
504             }
505             }
506              
507             {
508             my @name = ( '',
509             'Afteryule', 'Solmath', 'Rethe', 'Astron', 'Thrimidge',
510             'Forelithe', 'Afterlithe', 'Wedmath', 'Halimath', 'Winterfilth',
511             'Blotmath', 'Foreyule',
512             );
513              
514             my $validate = _make_validator( qw{ UInt|Undef } );
515              
516             sub __month_name {
517 760     760   460955 my ( $month ) = $validate->( @_ );
518 760 100       1342 defined $month
519             or return [ @name ];
520 739         2554 return $name[ $month ];
521             }
522              
523             }
524              
525             {
526             my @name = ( '', 'Ayu', 'Sol', 'Ret', 'Ast', 'Thr', 'Fli', 'Ali',
527             'Wed', 'Hal', 'Win', 'Blo', 'Fyu' );
528              
529             my $validate = _make_validator( qw{ UInt|Undef } );
530              
531             sub __month_abbr {
532 44     44   219444 my ( $month ) = $validate->( @_ );
533 44 100       208 defined $month
534             or return [ @name ];
535 23   100     103 return $name[ $month || 0 ];
536             }
537             }
538              
539             {
540             my $lookup = _make_lookup_hash(
541             __month_name(),
542             __month_abbr(),
543             );
544              
545             my $validate = _make_validator( qw{ Scalar } );
546              
547             sub __month_name_to_number {
548 14     14   77 my ( $month ) = _normalize_for_lookup(
549             $validate->( @_ ) );
550              
551 14 50       44 $month =~ m/ \A [0-9]+ \z /smx
552             and return $month;
553 14   100     84 return $lookup->{$month} || 0;
554             }
555             }
556              
557             {
558             my @on_date;
559              
560             $on_date[0][3] = "Wedding of King Elessar and Arwen, 1419.\n";
561              
562             $on_date[1][8] = "The Company of the Ring reaches Hollin, 1419.\n";
563             $on_date[1][13] = "The Company of the Ring reaches the West-gate of Moria at nightfall, 1419.\n";
564             $on_date[1][14] = "The Company of the Ring spends the night in Moria Hall 21, 1419.\n";
565             $on_date[1][15] = "The Bridge of Khazad-dum, and fall of Gandalf, 1419.\n";
566             $on_date[1][17] = "The Company of the Ring comes to Caras Galadhon at evening, 1419.\n";
567             $on_date[1][23] = "Gandalf pursues the Balrog to the peak of Zirakzigil, 1419.\n";
568             $on_date[1][25] = "Gandalf casts down the Balrog, and passes away.\n" .
569             "His body lies on the peak of Zirakzigil, 1419.\n";
570              
571             $on_date[2][14] = "Frodo and Sam look in the Mirror of Galadriel, 1419.\n" .
572             "Gandalf returns to life, and lies in a trance, 1419.\n";
573             $on_date[2][16] = "Company of the Ring says farewell to Lorien --\n" .
574             "Gollum observes departure, 1419.\n";
575             $on_date[2][17] = "Gwaihir the eagle bears Gandalf to Lorien, 1419.\n";
576             $on_date[2][25] = "The Company of the Ring pass the Argonath and camp at Parth Galen, 1419.\n" .
577             "First battle of the Fords of Isen -- Theodred son of Theoden slain, 1419.\n";
578             $on_date[2][26] = "Breaking of the Fellowship, 1419.\n" .
579             "Death of Boromir; his horn is heard in Minas Tirith, 1419.\n" .
580             "Meriadoc and Peregrin captured by Orcs -- Aragorn pursues, 1419.\n" .
581             "Eomer hears of the descent of the Orc-band from Emyn Muil, 1419.\n" .
582             "Frodo and Samwise enter the eastern Emyn Muil, 1419.\n";
583             $on_date[2][27] = "Aragorn reaches the west-cliff at sunrise, 1419.\n" .
584             "Eomer sets out from Eastfold against Theoden's orders to pursue the Orcs, 1419.\n";
585             $on_date[2][28] = "Eomer overtakes the Orcs just outside of Fangorn Forest, 1419.\n";
586             $on_date[2][29] = "Meriadoc and Pippin escape and meet Treebeard, 1419.\n" .
587             "The Rohirrim attack at sunrise and destroy the Orcs, 1419.\n" .
588             "Frodo descends from the Emyn Muil and meets Gollum, 1419.\n" .
589             "Faramir sees the funeral boat of Boromir, 1419.\n";
590             $on_date[2][30] = "Entmoot begins, 1419.\n" .
591             "Eomer, returning to Edoras, meets Aragorn, 1419.\n";
592              
593             $on_date[3][1] = "Aragorn meets Gandalf the White, and they set out for Edoras, 1419.\n" .
594             "Faramir leaves Minas Tirith on an errand to Ithilien, 1419.\n";
595             $on_date[3][2] = "The Rohirrim ride west against Saruman, 1419.\n" .
596             "Second battle at the Fords of Isen; Erkenbrand defeated, 1419.\n" .
597             "Entmoot ends. Ents march on Isengard and reach it at night, 1419.\n";
598             $on_date[3][3] = "Theoden retreats to Helm's Deep; battle of the Hornburg begins, 1419.\n" .
599             "Ents complete the destruction of Isengard.\n";
600             $on_date[3][4] = "Theoden and Gandalf set out from Helm's Deep for Isengard, 1419.\n" .
601             "Frodo reaches the slag mound on the edge of the of the Morannon, 1419.\n";
602             $on_date[3][5] = "Theoden reaches Isengard at noon; parley with Saruman in Orthanc, 1419.\n" .
603             "Gandalf sets out with Peregrin for Minas Tirith, 1419.\n";
604             $on_date[3][6] = "Aragorn overtaken by the Dunedain in the early hours, 1419.\n";
605             $on_date[3][7] = "Frodo taken by Faramir to Henneth Annun, 1419.\n" .
606             "Aragorn comes to Dunharrow at nightfall, 1419.\n";
607             $on_date[3][8] = "Aragorn takes the \"Paths of the Dead\", and reaches Erech at midnight, 1419.\n" .
608             "Frodo leaves Henneth Annun, 1419.\n";
609             $on_date[3][9] = "Gandalf reaches Minas Tirith, 1419.\n" .
610             "Darkness begins to flow out of Mordor, 1419.\n";
611             $on_date[3][10] = "The Dawnless Day, 1419.\n" .
612             "The Rohirrim are mustered and ride from Harrowdale, 1419.\n" .
613             "Faramir rescued by Gandalf at the gates of Minas Tirith, 1419.\n" .
614             "An army from the Morannon takes Cair Andros and passes into Anorien, 1419.\n";
615             $on_date[3][11] = "Gollum visits Shelob, 1419.\n" .
616             "Denethor sends Faramir to Osgiliath, 1419.\n" .
617             "Eastern Rohan is invaded and Lorien assaulted, 1419.\n";
618             $on_date[3][12] = "Gollum leads Frodo into Shelob's lair, 1419.\n" .
619             "Ents defeat the invaders of Rohan, 1419.\n";
620             $on_date[3][13] = "Frodo captured by the Orcs of Cirith Ungol, 1419.\n" .
621             "The Pelennor is overrun and Faramir is wounded, 1419.\n" .
622             "Aragorn reaches Pelargir and captures the fleet of Umbar, 1419.\n";
623             $on_date[3][14] = "Samwise finds Frodo in the tower of Cirith Ungol, 1419.\n" .
624             "Minas Tirith besieged, 1419.\n";
625             $on_date[3][15] = "Witch King breaks the gates of Minas Tirith, 1419.\n" .
626             "Denethor, Steward of Gondor, burns himself on a pyre, 1419.\n" .
627             "The battle of the Pelennor occurs as Theoden and Aragorn arrive, 1419.\n" .
628             "Thranduil repels the forces of Dol Guldur in Mirkwood, 1419.\n" .
629             "Lorien assaulted for second time, 1419.\n";
630             $on_date[3][17] = "Battle of Dale, where King Brand and King Dain Ironfoot fall, 1419.\n" .
631             "Shagrat brings Frodo's cloak, mail-shirt, and sword to Barad-dur, 1419.\n";
632             $on_date[3][18] = "Host of the west leaves Minas Tirith, 1419.\n" .
633             "Frodo and Sam overtaken by Orcs on the road from Durthang to Udun, 1419.\n";
634             $on_date[3][19] = "Frodo and Sam escape the Orcs and start on the road toward Mount Doom, 1419.\n";
635             $on_date[3][22] = "Lorien assaulted for the third time, 1419.\n";
636             $on_date[3][24] = "Frodo and Sam reach the base of Mount Doom, 1419.\n";
637             $on_date[3][25] = "Battle of the Host of the West on the slag hill of the Morannon, 1419.\n" .
638             "Gollum siezes the Ring of Power and falls into the Cracks of Doom, 1419.\n" .
639             "Downfall of Barad-dur and the passing of Sauron!, 1419.\n" .
640             "Birth of Elanor the Fair, daughter of Samwise, 1421.\n" .
641             "Fourth age begins in the reckoning of Gondor, 1421.\n";
642             $on_date[3][27] = "Bard II and Thorin III Stonehelm drive the enemy from Dale, 1419.\n";
643             $on_date[3][28] = "Celeborn crosses the Anduin and begins destruction of Dol Guldur, 1419.\n";
644              
645             $on_date[4][6] = "The mallorn tree flowers in the Party Field, 1420.\n";
646             $on_date[4][8] = "Ring bearers are honored on the Field of Cormallen, 1419.\n";
647             $on_date[4][12] = "Gandalf arrives in Hobbiton, 1418\n";
648              
649             $on_date[5][1] = "Crowning of King Elessar, 1419.\n" .
650             "Samwise marries Rose, 1420.\n";
651              
652             $on_date[6][20] = "Sauron attacks Osgiliath, 1418.\n" .
653             "Thranduil is attacked, and Gollum escapes, 1418.\n";
654              
655             $on_date[7][4] = "Boromir sets out from Minas Tirith, 1418\n";
656             $on_date[7][10] = "Gandalf imprisoned in Orthanc, 1418\n";
657             $on_date[7][19] = "Funeral Escort of King Theoden leaves Minas Tirith, 1419.\n";
658              
659             $on_date[8][10] = "Funeral of King Theoden, 1419.\n";
660              
661             $on_date[9][18] = "Gandalf escapes from Orthanc in the early hours, 1418.\n";
662             $on_date[9][19] = "Gandalf comes to Edoras as a beggar, and is refused admittance, 1418\n";
663             $on_date[9][20] = "Gandalf gains entrance to Edoras. Theoden commands him to go:\n" .
664             "\"Take any horse, only be gone ere tomorrow is old\", 1418.\n";
665             $on_date[9][21] = "The hobbits return to Rivendell, 1419.\n";
666             $on_date[9][22] = "Birthday of Bilbo and Frodo.\n" .
667             "The Black Riders reach Sarn Ford at evening;\n" .
668             " they drive off the guard of Rangers, 1418.\n" .
669             "Saruman comes to the Shire, 1419.\n";
670             $on_date[9][23] = "Four Black Riders enter the shire before dawn. The others pursue \n" .
671             "the Rangers eastward and then return to watch the Greenway, 1418.\n" .
672             "A Black Rider comes to Hobbiton at nightfall, 1418.\n" .
673             "Frodo leaves Bag End, 1418.\n" .
674             "Gandalf having tamed Shadowfax rides from Rohan, 1418.\n";
675             $on_date[9][26] = "Frodo comes to Bombadil, 1418\n";
676             $on_date[9][28] = "The Hobbits are captured by a barrow-wight, 1418.\n";
677             $on_date[9][29] = "Frodo reaches Bree at night, 1418.\n" .
678             "Frodo and Bilbo depart over the sea with the three Keepers, 1421.\n" .
679             "End of the Third Age, 1421.\n";
680             $on_date[9][30] = "Crickhollow and the inn at Bree are raided in the early hours, 1418.\n" .
681             "Frodo leaves Bree, 1418.\n";
682              
683             $on_date[10][3] = "Gandalf attacked at night on Weathertop, 1418.\n";
684             $on_date[10][5] = "Gandalf and the Hobbits leave Rivendell, 1419.\n";
685             $on_date[10][6] = "The camp under Weathertop is attacked at night and Frodo is wounded, 1418.\n";
686             $on_date[10][11] = "Glorfindel drives the Black Riders off the Bridge of Mitheithel, 1418.\n";
687             $on_date[10][13] = "Frodo crosses the Bridge of Mitheithel, 1418.\n";
688             $on_date[10][18] = "Glorfindel finds Frodo at dusk, 1418.\n" .
689             "Gandalf reaches Rivendell, 1418.\n";
690             $on_date[10][20] = "Escape across the Ford of Bruinen, 1418.\n";
691             $on_date[10][24] = "Frodo recovers and wakes, 1418.\n" .
692             "Boromir arrives at Rivendell at night, 1418.\n";
693             $on_date[10][25] = "Council of Elrond, 1418.\n";
694             $on_date[10][30] = "The four Hobbits arrive at the Brandywine Bridge in the dark, 1419.\n";
695              
696             $on_date[11][3] = "Battle of Bywater and passing of Saruman, 1419.\n" .
697             "End of the War of the Ring, 1419.\n";
698              
699             $on_date[12][25] = "The Company of the Ring leaves Rivendell at dusk, 1418.\n";
700              
701             my $validate = _make_validator( qw{ UInt UInt|Undef } );
702              
703             sub __on_date {
704 387     387   1210 my ( $month, $day ) = $validate->( @_ );
705 387 50       720 defined $day
706             or ( $month, $day ) = ( 0, $month );
707 387         1448 return $on_date[$month][$day];
708             }
709              
710             sub __search_on_date {
711 3     3   220031 my @criteria = @_;
712 3         7 foreach ( @criteria ) {
713 3 100       58 if ( ref eq CODE_REF ) {
    100          
    50          
714             # Accept as-is
715             } elsif ( ref eq REGEX_REF ) {
716 1         2 my $criterion = $_;
717 1     71   5 $_ = sub { m/$criterion/ };
  71         170  
718             } elsif ( ref ) {
719 0         0 Carp::croak( ref, ' reference not supported' );
720             } else {
721 1         2 my $criterion = $_;
722 1     71   5 $_ = sub { index( $_, $criterion ) >= 0 };
  71         127  
723             }
724             }
725 3         45 my @rslt;
726 3         9 foreach my $month ( 0 .. $#on_date ) {
727 39 50       80 $on_date[$month]
728             or next;
729 39         43 foreach my $day ( 0 .. @{ $on_date[$month] } - 1 ) {
  39         51  
730 747 100       1124 local $_ = $on_date[$month][$day]
731             or next;
732 213         215 foreach my $criterion ( @criteria ) {
733 213 100       219 $criterion->()
734             or next;
735 6         13 push @rslt, [ $month, $day ];
736 6         9 last;
737             }
738             }
739             }
740              
741 3         20 return @rslt;
742             }
743              
744             my @on_date_accented;
745              
746             sub _make_on_date_accented {
747              
748             # This would be much easier with 'use utf8;', but
749             # unfortunately this was broken under Perl 5.6.
750 2     2   6 my $E_acute = "\N{LATIN CAPITAL LETTER E WITH ACUTE}";
751 2         43 my $e_acute = "\N{LATIN SMALL LETTER E WITH ACUTE}";
752 2         5 my $o_acute = "\N{LATIN SMALL LETTER O WITH ACUTE}";
753 2         3 my $u_acute = "\N{LATIN SMALL LETTER U WITH ACUTE}";
754 2         5 my $u_circ = "\N{LATIN SMALL LETTER U WITH CIRCUMFLEX}";
755              
756 2         5 foreach my $month ( @on_date ) {
757 26         35 push @on_date_accented, [];
758 26         27 foreach my $day ( @{ $month } ) {
  26         42  
759 498 100       807 if ( $day ) {
760 142         198 $day =~ s/ \b Anorien \b /An${o_acute}rien/smxgo;
761 142         165 $day =~ s/ \b Annun \b /Ann${u_circ}n/smxgo;
762 142         174 $day =~ s/ \b Barad-dur \b /Barad-d${u_circ}r/smxgo;
763 142         168 $day =~ s/ \b Dunedain \b /D${u_acute}nedain/smxgo;
764 142         204 $day =~ s/ \b Eomer \b /${E_acute}omer/smxgo;
765 142         158 $day =~ s/ \b Eowyn \b /${E_acute}owyn/smxgo;
766 142         166 $day =~ s/ \b Khazad-dum \b /Khazad-d${u_circ}m/smxgo;
767 142         199 $day =~ s/ \b Lorien \b /L${o_acute}rien/smxgo;
768 142         160 $day =~ s/ \b Nazgul \b /Nazg${u_circ}l/smxgo;
769 142         252 $day =~ s/ \b Theoden \b /Th${e_acute}oden/smxgo;
770 142         175 $day =~ s/ \b Theodred \b /Th${e_acute}odred/smxgo;
771 142         202 $day =~ s/ \b Udun \b /Ud${u_circ}n/smxgo;
772             }
773 498         496 push @{ $on_date_accented[-1] }, $day;
  498         854  
774             }
775             }
776 2         5 return;
777             }
778              
779             sub __on_date_accented {
780 387     387   1329 my ( $month, $day ) = $validate->( @_ );
781 387 50       677 defined $day
782             or ( $month, $day ) = ( 0, $month );
783              
784             @on_date_accented
785 387 100       619 or _make_on_date_accented();
786              
787 387         1730 return $on_date_accented[$month][$day];
788             }
789             }
790              
791             {
792             my @holiday_quarter = ( undef, 1, 2, 0, 0, 3, 4 );
793              
794             my $validate = _make_validator( qw{ UInt UInt|Undef } );
795              
796             sub __quarter {
797 366     366   486704 my ( $month, $day ) = $validate->( @_ );
798 366 50       906 defined $day
799             or ( $month, $day ) = ( 0, $month );
800 366 100       2674 return $month ?
801             POSIX::floor( ( $month - 1 ) / 3 ) + 1 :
802             $holiday_quarter[$day];
803             }
804             }
805              
806             {
807             my @name = ( '', '1st quarter', '2nd quarter', '3rd quarter',
808             '4th quarter' );
809              
810             my $validate = _make_validator( qw{ UInt } );
811              
812             sub __quarter_name {
813 5     5   3941 my ( $quarter ) = $validate->( @_ );
814 5         50 return $name[ $quarter ];
815             }
816             }
817              
818             {
819             my @name = ( '', qw{ Q1 Q2 Q3 Q4 } );
820              
821             my $validate = _make_validator( qw{ UInt } );
822              
823             sub __quarter_abbr {
824 5     5   19 my ( $quarter ) = $validate->( @_ );
825 5         58 return $name[ $quarter ];
826             }
827             }
828              
829             {
830             my $validate = _make_validator( qw{ Int } );
831              
832             sub __rata_die_to_year_day {
833 400     400   206218 my ( $rata_die ) = $validate->( @_ );
834              
835 400         551 --$rata_die; # The algorithm is simpler with zero-based days.
836 400         1073 my $cycle = POSIX::floor( $rata_die / 146097 );
837 400         728 my $day_of_cycle = $rata_die - $cycle * 146097;
838 400         1537 my $year = POSIX::floor( ( $day_of_cycle -
839             POSIX::floor( $day_of_cycle / 1460 ) +
840             POSIX::floor( $day_of_cycle / 36524 ) -
841             POSIX::floor( $day_of_cycle / 146096 ) ) / 365 ) +
842             400 * $cycle + 1;
843             # We pay here for the zero-based day by having to add back 2
844             # rather than 1.
845 400         830 my $year_day = $rata_die - __year_day_to_rata_die( $year ) + 2;
846 400         908 return ( $year, $year_day );
847             }
848             }
849              
850             {
851             my @name = ( '', 'Sterrendei', 'Sunnendei', 'Monendei',
852             'Trewesdei', 'Hevenesdei', 'Meresdei', 'Highdei' );
853              
854             sub __trad_weekday_name {
855 16     16   217157 return _lookup( $_[0], \@name );
856             }
857             }
858              
859             {
860             my @name = ( '', 'Ste', 'Sun', 'Mon', 'Tre', 'Hev', 'Mer', 'Hig' );
861              
862             sub __trad_weekday_abbr {
863 11     11   226177 return _lookup( $_[0], \@name );
864             }
865             }
866              
867             {
868             my @name = ( '', 'St', 'Su', 'Mo', 'Tr', 'He', 'Me', 'Hi' );
869              
870             sub __trad_weekday_narrow {
871 11     11   213936 return _lookup( $_[0], \@name );
872             }
873             }
874              
875             {
876             my @holiday = ( undef, 1, 26, 0, 0, 27, 52 );
877             my @month_offset = ( undef, ( 0 ) x 6, ( 2 ) x 6 );
878              
879             my $validate = _make_validator( qw{ UInt UInt } );
880              
881             sub __week_of_year {
882 375     375   411313 my ( $month, $day ) = $validate->( @_ );
883 375 100       690 $month
884             or return $holiday[$day];
885             return int( (
886 363         1379 ( $month - 1 ) * 30 + $month_offset[$month] + $day
887             ) / 7 ) + 1;
888             }
889             }
890              
891             {
892             my @name = ( '', 'Sterday', 'Sunday', 'Monday', 'Trewsday',
893             'Hevensday', 'Mersday', 'Highday' );
894              
895             sub __weekday_name {
896 16     16   205911 return _lookup( $_[0], \@name );
897             }
898             }
899              
900             {
901             my @name = ( '', 'Ste', 'Sun', 'Mon', 'Tre', 'Hev', 'Mer', 'Hig' );
902              
903             sub __weekday_abbr {
904 13     13   211969 return _lookup( $_[0], \@name );
905             }
906             }
907              
908             {
909             my @name = ( '', 'St', 'Su', 'Mo', 'Tr', 'He', 'Me', 'Hi' );
910              
911             sub __weekday_narrow {
912 11     11   225814 return _lookup( $_[0], \@name );
913             }
914             }
915              
916             {
917             my $validate = _make_validator( qw{ Int UInt|Undef } );
918              
919             sub __year_day_to_rata_die {
920 801     801   4515 my ( $year, $day ) = $validate->( @_ );
921 801         923 --$year;
922 801   100     1936 $day ||= 1;
923 801         3638 return $year * 365 + POSIX::floor( $year / 4 ) -
924             POSIX::floor( $year / 100 ) + POSIX::floor( $year / 400 ) +
925             $day;
926             }
927             }
928              
929 21     21   39532 use constant FORMAT_DATE_ERROR => 'Date must be object or hash';
  21         32  
  21         1536  
930 21     21   95 use constant DATE_CLASS => join '::', __PACKAGE__, 'Date';
  21         29  
  21         25611  
931              
932             sub _make_date_object {
933 282     282   395 my ( $date ) = @_;
934              
935 282 50       663 my $ref = ref $date
936             or Carp::croak( FORMAT_DATE_ERROR );
937              
938 282 100       594 HASH_REF eq $ref
939             or return __valid_date_class( $date );
940              
941 207         267 my %hash = %{ $date };
  207         903  
942 207   50     520 $hash{day} ||= 1;
943 207 50 66     679 $hash{month} ||= $hash{day} < 7 ? 0 : 1;
944 207   100     1564 $hash{$_} ||= 0 for qw{
945             hour minute second nanosecond epoch
946             };
947             defined $hash{zone_name}
948 207 100       463 or $hash{zone_name} = '';
949 207         578 return bless \%hash, DATE_CLASS;
950             }
951              
952             {
953             my %checked;
954              
955             sub __valid_date_class {
956 75     75   105 my ( $obj ) = @_;
957 75   33     142 my $pkg = ref $obj || $obj;
958              
959 75         101 local $" = ', ';
960 75 50 66     93 @{ $checked{$pkg} ||= do {
  75         183  
961 1 50       2 unless ( ref $obj ) {
962 0         0 ( my $fn = $pkg ) =~ s{ :: }{/}smxg;
963 0         0 $fn .= '.pm';
964 0 0       0 $INC{$fn}
965             or require $fn;
966             }
967 1         2 my @missing;
968 1         1 foreach my $method ( qw{
969             __fmt_shire_year
970             __fmt_shire_month
971             __fmt_shire_day
972             __fmt_shire_hour
973             __fmt_shire_minute
974             __fmt_shire_second
975             __fmt_shire_day_of_week
976             __fmt_shire_nanosecond
977             __fmt_shire_epoch
978             __fmt_shire_zone_offset
979             __fmt_shire_zone_name
980             __fmt_shire_accented
981             __fmt_shire_traditional
982             } ) {
983 13 50       34 $pkg->can( $method )
984             or push @missing, $method;
985             }
986 1         3 \@missing;
987             } }
988             and Carp::croak(
989 0         0 "$pkg lacks methods: @{ $checked{$pkg} }" );
990 75         153 return $obj;
991             }
992             }
993              
994             # The arguments are multiple array references. The hash is set up so
995             # that all unique abbreviations of elements 0 return 0, and so on. The
996             # respective elements at the same index do not conflict with each other,
997             # so that (to take a not-so-random example) if two arrays are passed in,
998             # and the respective element 3s are (after normalization) 'midyearsday'
999             # and 'myd', and no other entries start with 'm', then key 'm' will
1000             # exist and return value 3.
1001             sub _make_lookup_hash {
1002 42     42   94 my @sources = @_;
1003 42         88 my %conflict;
1004             my %merged;
1005 42         0 my $source_count;
1006 42         72 foreach ( @sources ) {
1007 84         130 my @source = _normalize_for_lookup( @{ $_ } );
  84         226  
1008 84         113 my %value;
1009 84         180 foreach my $inx ( 0 .. $#source ) {
1010 840         1343 $value{ $source[$inx] } = $inx;
1011             }
1012 84         227 my %hash = Text::Abbrev::abbrev( @source );
1013 84         17554 delete $hash{''};
1014 84         272 foreach ( values %hash ) {
1015 3297         3927 $_ = $value{$_};
1016             }
1017             # Would use keys %merged here, but not sure how performant that
1018             # is under older Perls.
1019 84 100       170 if ( $source_count++ ) {
1020 42         164 foreach my $key ( keys %hash ) {
1021 903 50       1285 if ( $conflict{$key} ) {
    100          
1022             # ignore it
1023             } elsif ( $merged{$key} ) {
1024 651 50       1116 if ( $merged{$key} != $hash{$key} ) {
1025 0         0 delete $merged{$key};
1026 0         0 $conflict{$key} = 1;
1027             }
1028             } else {
1029 252         428 $merged{$key} = $hash{$key};
1030             }
1031             }
1032             } else {
1033 42         1006 %merged = %hash;
1034             }
1035             }
1036 42 50       135 return wantarray ? %merged : \%merged;
1037             }
1038              
1039             # I want this module to be light weight, but I also want to limit the
1040             # arguments so I can add or change them with confidence that I don't
1041             # break anything. So this is poor man's validation.
1042             {
1043             my %type_def;
1044              
1045             BEGIN {
1046             # Type definitions expect the value begin validated to be in $_.
1047             # They return false if the value passes the validation, and a
1048             # brief description of what was expected (which must be a true
1049             # value as far as Perl is concerned) if the value fails
1050             # validation. They must not throw exceptions, because an
1051             # individual validator may be part of an alternation.
1052             #
1053             # We need the BEGIN block because we are manufacturing
1054             # validators in-line, above, and %type_def needs to be populated
1055             # before that happens.
1056             %type_def = (
1057             # An array reference
1058             Array => sub {
1059 180 50       685 ARRAY_REF eq ref $_ ? 0 : 'an ARRAY reference'
1060             },
1061             # A hash reference
1062 282 100       1459 Hash => sub { HASH_REF eq ref $_ ? 0 : 'a HASH reference' },
1063             # An integer, optionally signed
1064             Int => sub {
1065 1201 50 33     8560 ( defined $_ && m/ \A [-+]? [0-9]+ \z /smx ) ? 0 :
1066             'an integer';
1067             },
1068             # An object (i.e. a blessed reference). I am not using
1069             # Scalar::Util::blessed() here because of the desire to run
1070             # under versions of Perl before this was released to core.
1071             Object => sub {
1072 75         106 local $@ = undef;
1073 75 50 33     176 ( ref $_ && eval { $_->can( 'isa' ) } ) ? ## no critic (RequireCheckingReturnValueOfEval)
1074             0 : 'an object' },
1075             # A defined scalar (i.e. not a reference)
1076 304 50 33     1689 Scalar => sub { ( defined $_ && ! ref $_ ) ? 0 :
1077             'a non-reference' },
1078             # An unsigned integer
1079             UInt => sub {
1080 11203 100 66     72331 ( defined $_ && m/ \A [0-9]+ \z /smx ) ? 0 :
1081             'an unsigned integer';
1082             },
1083             # Undefined. Necessary because all the other types reject an
1084             # undefined value.
1085 484 50       1449 Undef => sub { defined $_ ? 'undefined' : 0 },
1086 21     21   15349 );
1087             }
1088              
1089             # Take as arguments the type specifications of all arguments of the
1090             # subroutine to be validated, and return a reference to code that
1091             # checks its arguments against those specs. Type specifications must
1092             # appear in the above table, or be an alternation of items in the
1093             # above table (i.e. joined by '|', e.g. 'Scalar|Undef').
1094             #
1095             # There is currently no way to do slurpy arguments.
1096             sub _make_validator {
1097 378     378   711 my ( @spec ) = @_;
1098 378         618 foreach my $inx ( 0 .. $#spec ) {
1099 588         1986 foreach my $type ( split qr{ [|] }smx, $spec[$inx] ) {
1100 735 50       1522 $type_def{$type}
1101             or Carp::confess(
1102             "Programming error - Argument $inx type '$spec[$inx]' is unknown" );
1103             }
1104             }
1105             return sub {
1106 7782     7782   18044 my @args = @_;
1107 7782 50       17709 @args > @spec
1108             and Carp::croak( 'Too many arguments' );
1109             ARGUMENT_LOOP:
1110 7782         19115 foreach my $inx ( 0 .. $#spec ) {
1111 13170         17523 my @fail;
1112 13170         21082 local $_ = $args[$inx];
1113 13170         58577 foreach my $type ( split qr{ [|] }smx, $spec[$inx] ) {
1114 13729 100       27000 my $error = $type_def{$type}->()
1115             or next ARGUMENT_LOOP;
1116 559         1113 push @fail, $error;
1117             }
1118 0         0 local $" = ' or ';
1119 0         0 Carp::croak( "Argument $inx ('$_') must be @fail" );
1120             }
1121 7782         20642 return @args;
1122 378         7300 };
1123             }
1124             }
1125              
1126             sub _normalize_for_lookup {
1127 106     106   247 my @data = @_;
1128 106         147 foreach ( @data ) {
1129 862 50       1950 defined $_
1130             and ( $_ = lc $_ ) =~ s/ [\s[:punct:]]+ //smxg;
1131             }
1132 106         358 return @data;
1133             }
1134              
1135             # Create methods for the hash wrapper
1136              
1137             {
1138             my %calc = (
1139             day_of_week => sub {
1140             return __day_of_week( $_[0]->__fmt_shire_month(), $_[0]->__fmt_shire_day() );
1141             },
1142             ## quarter => sub {
1143             ## return __quarter( $_[0]->__fmt_shire_month(), $_[0]->__fmt_shire_day() );
1144             ## },
1145             );
1146              
1147             foreach my $field ( qw{
1148             year month day
1149             hour minute second nanosecond epoch
1150             zone_offset zone_name
1151             accented traditional
1152             }, keys %calc ) {
1153             my $fqn = join '::', __PACKAGE__, 'Date', "__fmt_shire_$field";
1154             if ( my $code = $calc{$field} ) {
1155 21     21   144 no strict qw{ refs };
  21         29  
  21         2219  
1156             *$fqn = sub {
1157             defined $_[0]->{$field}
1158 36 50   36   105 or $_[0]->{$field} = $code->( $_[0] );
1159 36         84 return $_[0]->{$field};
1160             };
1161             } else {
1162 21     21   97 no strict qw{ refs };
  21         28  
  21         2294  
1163 477     477   1159 *$fqn = sub { $_[0]->{$field} };
1164             }
1165             }
1166             }
1167              
1168             {
1169             my $validate;
1170              
1171             BEGIN {
1172 21     21   74 $validate = _make_validator( qw{ UInt|Undef Array } );
1173             }
1174              
1175             sub _lookup {
1176 180     180   384 my ( $inx, $tbl ) = $validate->( @_ );
1177 180 100       685 defined $inx
1178             and return $tbl->[ $inx ];
1179 42 50       99 __PACKAGE__ eq caller
1180             or Carp::croak( 'Index not defined' );
1181 42         108 return $tbl;
1182             }
1183             }
1184              
1185             1;
1186              
1187             __END__