File Coverage

blib/lib/Date/Tolkien/Shire/Data.pm
Criterion Covered Total %
statement 303 312 97.1
branch 121 156 77.5
condition 47 66 71.2
subroutine 68 68 100.0
pod n/a
total 539 602 89.5


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