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   45537 use 5.006002;
  20         168  
4              
5 20     20   100 use strict;
  20         38  
  20         489  
6 20     20   98 use warnings;
  20         34  
  20         641  
7              
8 20     20   10103 use charnames qw{ :full };
  20         622031  
  20         122  
9              
10 20     20   4650 use Carp ();
  20         45  
  20         275  
11 20     20   10983 use POSIX ();
  20         106410  
  20         578  
12 20     20   13468 use Text::Abbrev();
  20         1145  
  20         515  
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   122 use Exporter ();
  20         39  
  20         3996  
18             our @ISA = qw{ Exporter };
19              
20             our $VERSION = '0.008';
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   138 use constant ARRAY_REF => ref [];
  20         52  
  20         2408  
57 20     20   137 use constant CODE_REF => ref sub {};
  20         40  
  20         1017  
58 20     20   102 use constant HASH_REF => ref {};
  20         37  
  20         841  
59              
60 20     20   99 use constant DAY_OF_YEAR_MIDYEARS_DAY => 183;
  20         40  
  20         842  
61 20     20   106 use constant DAY_OF_YEAR_OVERLITHE => 184;
  20         32  
  20         963  
62              
63 20     20   125 use constant HOLIDAY_2_YULE => 1;
  20         38  
  20         913  
64 20     20   111 use constant HOLIDAY_1_LITHE => 2;
  20         57  
  20         913  
65 20     20   148 use constant HOLIDAY_MIDYEARS_DAY => 3;
  20         75  
  20         967  
66 20     20   129 use constant HOLIDAY_OVERLITHE => 4;
  20         38  
  20         829  
67 20     20   109 use constant HOLIDAY_2_LITHE => 5;
  20         37  
  20         877  
68 20     20   107 use constant HOLIDAY_1_YULE => 6;
  20         39  
  20         1058  
69              
70             # See the documentation below for where the value came from.
71              
72 20     20   114 use constant GREGORIAN_RATA_DIE_TO_SHIRE => 1995694;
  20         41  
  20         110709  
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   53 my ( $hour ) = $validate->( @_ );
81 21 50       68 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   191751 my ( $month, $day ) = $validate->( @_ );
94 411 100       1068 $month
95             or return $holiday[$day];
96 376         1137 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   770247 my ( $year, $month, $day ) = $validate_d2doy->( @_ );
109              
110 734 100       2176 my $yd = $month ? $month_zero[$month] + $day :
111             $holiday_day[$day];
112              
113 734 100       2067 unless ( __is_leap_year( $year ) ) {
114 368 50 66     1060 not $month
115             and HOLIDAY_OVERLITHE == $day
116             and Carp::croak( 'Overlithe only occurs in a leap year' );
117 368 100       834 $yd >= DAY_OF_YEAR_OVERLITHE
118             and --$yd;
119             }
120 734         1701 return $yd;
121             }
122              
123             my $validate_doy2d = _make_validator( qw{ UInt UInt } );
124              
125             sub __day_of_year_to_date {
126 731     731   385592 my ( $year, $yd ) = $validate_doy2d->( @_ );
127              
128 731 100       1886 unless ( __is_leap_year( $year ) ) {
129 365 100       934 $yd >= DAY_OF_YEAR_OVERLITHE
130             and $yd++;
131             }
132 731 50 33     2816 $yd > 0
133             and $yd <= 366
134             or Carp::croak( "Invalid year day $yd" );
135              
136 731         2140 for ( my $day = 1; $day < @holiday_day; $day++ ) {
137 4358 100       10299 $yd == $holiday_day[$day]
138             and return ( 0, $day );
139             }
140              
141 720         1340 $yd -= 2;
142 720 100       1665 $yd > 180
143             and $yd -= 4;
144 720         1344 my $day = $yd % 30;
145 720         1591 my $month = ( $yd - $day ) / 30;
146 720         2407 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   1301 my ( $date, $tplt ) = $validate->( @_ );
156              
157 264         527 $date = _make_date_object( $date );
158              
159 264         513 my $ctx = {
160             prefix_new_line_unless_empty => 0,
161             };
162              
163 264         1567 $tplt =~ s/ % (?: [{] ( \w+ ) [}] # method ($1)
164             | [{]{2} ( .*? ) [}]{2} # condition ($2)
165             | ( [-_0^#]* ) ( [0-9]* ) ( [EO]? . ) # conv spec ($3,$4,$5)
166             ) /
167 358 50       1273 $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         1272 return $tplt;
173             }
174             }
175              
176             sub _fmt_cond {
177 24     24   59 my ( $date, $tplt ) = @_;
178 24         100 my @cond = split qr< [|]{2} >smx, $tplt;
179 24         50 foreach my $inx ( 1, 2 ) {
180 48 100 100     145 defined $cond[$inx]
181             and '' ne $cond[$inx]
182             or $cond[$inx] = undef;
183             }
184              
185 24         30 my $inx = 0;
186 24 100 100     58 defined $cond[1]
187             and not $date->__fmt_shire_month()
188             and $inx = 1;
189 24 100 100     52 defined $cond[2]
190             and not __day_of_week( $date->__fmt_shire_month(), $date->__fmt_shire_day() )
191             and $inx = 2;
192              
193 24         48 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   1008 my ( $date, $conv, $mod, $wid, $ctx ) = @_;
354 331 50       612 defined $mod
355             or $mod = '';
356             $wid
357 331 100       534 and $ctx->{wid} = $wid;
358 331         368 my $code;
359 331         1227 foreach my $char ( split qr{}, $mod ) {
360 26 50       80 $code = $modifier_map{$char}
361             and $code->( $ctx );
362             }
363 331 100       668 if ( $wid ) {
364 4         6 $ctx->{wid} = $wid;
365             defined $ctx->{pad}
366             and '' eq $ctx->{pad}
367 4 100 100     14 and $ctx->{pad} = ' ';
368             }
369 331         414 my $rslt;
370 331 100 33     620 if ( $code = $spec{$conv} ) {
    50          
371 328         633 $rslt = $code->( $date, $ctx );
372             } elsif ( 1 < length $conv and $code = $spec{ substr $conv, 1 } ) {
373 3         7 $rslt = $code->( $date, $ctx );
374             } else {
375 0         0 $rslt = "%$mod$wid$conv";
376             }
377 331 100       579 defined $rslt
378             or $rslt = '';
379 331 100 66     645 if ( delete $ctx->{change_case} and $code = $case_change{$conv} ) {
380 6         9 delete $ctx->{uc};
381 6         12 $rslt = $code->( $rslt );
382             }
383             delete $ctx->{uc}
384 331 100       514 and $rslt = uc $rslt;
385 331         394 my $need;
386             $ctx->{wid}
387             and '' ne $ctx->{pad}
388             and ( $need = $ctx->{wid} - length $rslt ) > 0
389 331 100 100     903 and $rslt = ( $ctx->{pad} x $need ) . $rslt;
      100        
390 331         420 delete @{ $ctx }{ qw{ pad wid } };
  331         522  
391 331         1042 return $rslt;
392             }
393             }
394              
395             sub _fmt_number {
396 114     114   184 my ( undef, $ctx, $val ) = @_; # Invocant unused
397             defined $ctx->{pad}
398 114 100       264 or $ctx->{pad} = '0';
399             defined $ctx->{wid}
400 114 100       222 or $ctx->{wid} = 2;
401 114 50       282 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   70 or $_[1]{pad} = ' ';
409 14         46 goto &_fmt_number;
410             }
411              
412             sub _fmt_offset {
413 3     3   7 my ( $offset ) = @_;
414 3 100 66     14 defined $offset
415             and $offset =~ m/ \A [+-]? [0-9]+ \z /smx
416             or return '';
417 1 50       5 my $sign = $offset < 0 ? '-' : '+';
418 1         2 $offset = abs $offset;
419 1         2 my $sec = $offset % 60;
420 1         8 $offset = POSIX::floor( ( $offset - $sec ) / 60 );
421 1         3 my $min = $offset % 60;
422 1         4 my $hr = POSIX::floor( ( $offset - $min ) / 60 );
423 1 50       6 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         37 my $pfx = "\n" x $ctx->{prefix_new_line_unless_empty};
431 24         40 $ctx->{prefix_new_line_unless_empty} = 0;
432 24         42 my $month = $date->__fmt_shire_month();
433 24         39 my $day = $date->__fmt_shire_day();
434 24 100       40 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         40 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   1077 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   8148 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   808 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       34 $holiday =~ m/ \A [0-9]+ \z /smx
489             and return $holiday;
490 8   100     54 return $lookup->{$holiday} || 0;
491             }
492             }
493              
494             {
495             my $validate = _make_validator( qw{ UInt } );
496              
497             sub __is_leap_year {
498 1871     1871   324381 my ( $year ) = $validate->( @_ );
499 1871 100       6860 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   288798 my ( $month ) = $validate->( @_ );
514 758 100       1583 defined $month
515             or return [ @name ];
516 738         3037 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   877 my ( $month ) = $validate->( @_ );
529 42 100       222 defined $month
530             or return [ @name ];
531 22   100     100 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   38 my ( $month ) = _normalize_for_lookup(
545             $validate->( @_ ) );
546              
547 14 50       40 $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   1413 my ( $month, $day ) = $validate->( @_ );
701 387 50       803 defined $day
702             or ( $month, $day ) = ( 0, $month );
703 387         1723 return $on_date[$month][$day];
704             }
705              
706             my @on_date_accented;
707              
708             sub __on_date_accented {
709 387     387   1312 my ( $month, $day ) = $validate->( @_ );
710 387 50       761 defined $day
711             or ( $month, $day ) = ( 0, $month );
712              
713 387 100       703 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         6 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         5 my $u_circ = "\N{LATIN SMALL LETTER U WITH CIRCUMFLEX}";
722              
723 2         15 foreach my $month ( @on_date ) {
724 26         43 push @on_date_accented, [];
725 26         32 foreach my $day ( @{ $month } ) {
  26         44  
726 498 100       766 if ( $day ) {
727 142         226 $day =~ s/ \b Anorien \b /An${o_acute}rien/smxgo;
728 142         193 $day =~ s/ \b Annun \b /Ann${u_circ}n/smxgo;
729 142         196 $day =~ s/ \b Barad-dur \b /Barad-d${u_circ}r/smxgo;
730 142         203 $day =~ s/ \b Dunedain \b /D${u_acute}nedain/smxgo;
731 142         227 $day =~ s/ \b Eomer \b /${E_acute}omer/smxgo;
732 142         188 $day =~ s/ \b Eowyn \b /${E_acute}owyn/smxgo;
733 142         203 $day =~ s/ \b Khazad-dum \b /Khazad-d${u_circ}m/smxgo;
734 142         246 $day =~ s/ \b Lorien \b /L${o_acute}rien/smxgo;
735 142         198 $day =~ s/ \b Nazgul \b /Nazg${u_circ}l/smxgo;
736 142         243 $day =~ s/ \b Theoden \b /Th${e_acute}oden/smxgo;
737 142         228 $day =~ s/ \b Theodred \b /Th${e_acute}odred/smxgo;
738 142         206 $day =~ s/ \b Udun \b /Ud${u_circ}n/smxgo;
739             }
740 498         527 push @{ $on_date_accented[-1] }, $day;
  498         988  
741             }
742             }
743             }
744              
745 387         1379 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   198040 my ( $month, $day ) = $validate->( @_ );
756 366 50       928 defined $day
757             or ( $month, $day ) = ( 0, $month );
758 366 100       2302 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   2695 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   19 my ( $quarter ) = $validate->( @_ );
783 5         27 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   162548 my ( $rata_die ) = $validate->( @_ );
792              
793 400         621 --$rata_die; # The algorithm is simpler with zero-based days.
794 400         1219 my $cycle = POSIX::floor( $rata_die / 146097 );
795 400         874 my $day_of_cycle = $rata_die - $cycle * 146097;
796 400         1516 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         949 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   811 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   1499 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   828 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   201492 my ( $month, $day ) = $validate->( @_ );
841 375 100       849 $month
842             or return $holiday[$day];
843             return int( (
844 363         1553 ( $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   793 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   809 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   757 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   4325 my ( $year, $day ) = $validate->( @_ );
879 801         1151 --$year;
880 801   100     2038 $day ||= 1;
881 801         3586 return $year * 365 + POSIX::floor( $year / 4 ) -
882             POSIX::floor( $year / 100 ) + POSIX::floor( $year / 400 ) +
883             $day;
884             }
885             }
886              
887 20     20   266134 use constant FORMAT_DATE_ERROR => 'Date must be object or hash';
  20         52  
  20         1815  
888 20     20   128 use constant DATE_CLASS => join '::', __PACKAGE__, 'Date';
  20         255  
  20         24257  
889              
890             sub _make_date_object {
891 264     264   360 my ( $date ) = @_;
892              
893 264 50       581 my $ref = ref $date
894             or Carp::croak( FORMAT_DATE_ERROR );
895              
896 264 100       482 HASH_REF eq $ref
897             or return __valid_date_class( $date );
898              
899 201         251 my %hash = %{ $date };
  201         767  
900 201   50     410 $hash{day} ||= 1;
901 201 50 66     625 $hash{month} ||= $hash{day} < 7 ? 0 : 1;
902 201   100     1473 $hash{$_} ||= 0 for qw{
903             hour minute second nanosecond epoch
904             };
905             defined $hash{zone_name}
906 201 100       434 or $hash{zone_name} = '';
907 201         450 return bless \%hash, DATE_CLASS;
908             }
909              
910             {
911             my %checked;
912              
913             sub __valid_date_class {
914 63     63   77 my ( $obj ) = @_;
915 63   33     109 my $pkg = ref $obj || $obj;
916              
917 63         83 local $" = ', ';
918 63 50 66     65 @{ $checked{$pkg} ||= do {
  63         202  
919 1 50       3 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         2 my @missing;
926 1         3 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       40 $pkg->can( $method )
942             or push @missing, $method;
943             }
944 1         10 \@missing;
945             } }
946             and Carp::croak(
947 0         0 "$pkg lacks methods: @{ $checked{$pkg} }" );
948 63         115 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   109 my @sources = @_;
961 40         135 my %conflict;
962             my %merged;
963 40         0 my $source_count;
964 40         98 foreach ( @sources ) {
965 80         151 my @source = _normalize_for_lookup( @{ $_ } );
  80         246  
966 80         144 my %value;
967 80         175 foreach my $inx ( 0 .. $#source ) {
968 800         1383 $value{ $source[$inx] } = $inx;
969             }
970 80         280 my %hash = Text::Abbrev::abbrev( @source );
971 80         19977 delete $hash{''};
972 80         302 foreach ( values %hash ) {
973 3140         4434 $_ = $value{$_};
974             }
975             # Would use keys %merged here, but not sure how performant that
976             # is under older Perls.
977 80 100       217 if ( $source_count++ ) {
978 40         151 foreach my $key ( keys %hash ) {
979 860 50       1451 if ( $conflict{$key} ) {
    100          
980             # ignore it
981             } elsif ( $merged{$key} ) {
982 620 50       1225 if ( $merged{$key} != $hash{$key} ) {
983 0         0 delete $merged{$key};
984 0         0 $conflict{$key} = 1;
985             }
986             } else {
987 240         430 $merged{$key} = $hash{$key};
988             }
989             }
990             } else {
991 40         1063 %merged = %hash;
992             }
993             }
994 40 50       136 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       775 ARRAY_REF eq ref $_ ? 0 : 'an ARRAY reference'
1018             },
1019             # A hash reference
1020 264 100       1067 Hash => sub { HASH_REF eq ref $_ ? 0 : 'a HASH reference' },
1021             # An integer, optionally signed
1022             Int => sub {
1023 1201 50 33     7902 ( 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         73 local $@ = undef;
1031 63 50 33     144 ( ref $_ && eval { $_->can( 'isa' ) } ) ? ## no critic (RequireCheckingReturnValueOfEval)
1032             0 : 'an object' },
1033             # A defined scalar (i.e. not a reference)
1034 286 50 33     1401 Scalar => sub { ( defined $_ && ! ref $_ ) ? 0 :
1035             'a non-reference' },
1036             # An unsigned integer
1037             UInt => sub {
1038 11193 100 66     72891 ( 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       1504 Undef => sub { defined $_ ? 'undefined' : 0 },
1044 20     20   14473 );
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   790 my ( @spec ) = @_;
1056 360         742 foreach my $inx ( 0 .. $#spec ) {
1057 560         2228 foreach my $type ( split qr{ [|] }smx, $spec[$inx] ) {
1058 700 50       1887 $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   16474 my @args = @_;
1065 7754 50       18080 @args > @spec
1066             and Carp::croak( 'Too many arguments' );
1067             ARGUMENT_LOOP:
1068 7754         17660 foreach my $inx ( 0 .. $#spec ) {
1069 13118         18536 my @fail;
1070 13118         22554 local $_ = $args[$inx];
1071 13118         54065 foreach my $type ( split qr{ [|] }smx, $spec[$inx] ) {
1072 13661 100       27231 my $error = $type_def{$type}->()
1073             or next ARGUMENT_LOOP;
1074 543         1102 push @fail, $error;
1075             }
1076 0         0 local $" = ' or ';
1077 0         0 Carp::croak( "Argument $inx ('$_') must be @fail" );
1078             }
1079 7754         21349 return @args;
1080 360         7801 };
1081             }
1082             }
1083              
1084             sub _normalize_for_lookup {
1085 102     102   273 my @data = @_;
1086 102         180 foreach ( @data ) {
1087 822 50       2159 defined $_
1088             and ( $_ = lc $_ ) =~ s/ [\s[:punct:]]+ //smxg;
1089             }
1090 102         376 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   190 no strict qw{ refs };
  20         46  
  20         2719  
1114             *$fqn = sub {
1115             defined $_[0]->{$field}
1116 36 50   36   97 or $_[0]->{$field} = $code->( $_[0] );
1117 36         83 return $_[0]->{$field};
1118             };
1119             } else {
1120 20     20   146 no strict qw{ refs };
  20         51  
  20         3033  
1121 459     459   1059 *$fqn = sub { $_[0]->{$field} };
1122             }
1123             }
1124             }
1125              
1126             {
1127             my $validate;
1128              
1129             BEGIN {
1130 20     20   101 $validate = _make_validator( qw{ UInt|Undef Array } );
1131             }
1132              
1133             sub _lookup {
1134 174     174   404 my ( $inx, $tbl ) = $validate->( @_ );
1135 174 100       732 defined $inx
1136             and return $tbl->[ $inx ];
1137 40 50       159 __PACKAGE__ eq caller
1138             or Carp::croak( 'Index not defined' );
1139 40         162 return $tbl;
1140             }
1141             }
1142              
1143             1;
1144              
1145             __END__