File Coverage

blib/lib/Date/ManipX/Almanac/Date.pm
Criterion Covered Total %
statement 312 352 88.6
branch 86 130 66.1
condition 41 89 46.0
subroutine 80 87 91.9
pod 11 38 28.9
total 530 696 76.1


line stmt bran cond sub pod time code
1             package Date::ManipX::Almanac::Date;
2              
3 3     3   442066 use 5.010;
  3         22  
4              
5 3     3   25 use strict;
  3         6  
  3         71  
6 3     3   13 use warnings;
  3         6  
  3         95  
7              
8 3     3   933 use Astro::Coord::ECI 0.119; # For clone() to work.
  3         27125  
  3         108  
9 3     3   23 use Astro::Coord::ECI::Utils 0.119 qw{ TWOPI };
  3         41  
  3         154  
10 3     3   16 use Carp;
  3         6  
  3         137  
11 3     3   2747 use Date::Manip::Date;
  3         238003  
  3         125  
12 3     3   1570 use Module::Load ();
  3         2943  
  3         57  
13 3     3   19 use Scalar::Util ();
  3         5  
  3         39  
14 3     3   1276 use Text::ParseWords ();
  3         3430  
  3         117  
15              
16             our $VERSION = '0.003';
17              
18 3     3   21 use constant DEFAULT_TWILIGHT => 'civil';
  3         7  
  3         203  
19 3     3   16 use constant REF_ARRAY => ref [];
  3         6  
  3         123  
20 3     3   15 use constant REF_HASH => ref {};
  3         8  
  3         113  
21 3     3   16 use constant METERS_PER_KILOMETER => 1000;
  3         6  
  3         11627  
22              
23             sub new {
24 6     6 1 3165 my ( $class, @args ) = @_;
25 6         23 return $class->_new( new => @args );
26             }
27              
28             sub _new {
29 7     7   20 my ( $class, $new_method, @args ) = @_;
30              
31 7         14 my @config;
32 7 100 66     32 if ( @args && REF_ARRAY eq ref $args[-1] ) {
33 1         2 @config = @{ pop @args };
  1         3  
34 1         3 state $method_map = {
35             new => 'new_config',
36             };
37 1   33     4 $new_method = $method_map->{$new_method} // $new_method;
38             }
39              
40 7         17 my ( $dmd, $from );
41 7 100       37 if ( ref $class ) {
    50          
42 4         8 $from = $class;
43 4         13 $dmd = $class->dmd()->$new_method();
44             } elsif ( Scalar::Util::blessed( $args[0] ) ) {
45 0         0 $from = shift @args;
46 0 0       0 $dmd = Date::Manip::Date->$new_method(
47             $from->isa( __PACKAGE__ ) ? $from->dmd() : $from
48             );
49             } else {
50 3         34 $dmd = Date::Manip::Date->$new_method();
51             }
52              
53 7   66     33061 my $self = bless {
54             dmd => $dmd,
55             }, ref $class || $class;
56              
57 7         30 $self->_init_almanac( $from );
58              
59             @config
60 7 100       20 and $self->config( @config );
61              
62 7 100       27 $self->get_config( 'sky' )
63             or $self->_config_almanac_default_sky();
64 7 100       24 defined $self->get_config( 'twilight' )
65             or $self->_config_almanac_var_twilight(
66             twilight => DEFAULT_TWILIGHT );
67              
68             @args
69 7 50       17 and $self->parse( @args );
70              
71 7         38 return $self;
72             }
73              
74             sub new_config {
75 1     1 1 586 my ( $class, @args ) = @_;
76 1         5 return $class->_new( new_config => @args );
77             }
78              
79             sub new_date {
80 1     1 1 606 my ( $class, @args ) = @_;
81             # return $class->_new( new_date => @args );
82 1         5 return $class->new( @args );
83             }
84              
85             sub calc {
86 0     0 1 0 my ( $self, $obj, @args ) = @_;
87 0 0 0     0 Scalar::Util::blessed( $obj )
88             and $obj->isa( __PACKAGE__ )
89             and $obj = $obj->dmd();
90 0         0 return $self->dmd()->calc( $obj, @args );
91             }
92              
93             sub cmp : method { ## no critic (ProhibitBuiltinHomonyms)
94 0     0 1 0 my ( $self, $date ) = @_;
95 0 0       0 $date->isa( __PACKAGE__ )
96             and $date = $date->dmd();
97 0         0 return $self->dmd()->cmp( $date );
98             }
99              
100             sub config {
101 30     30 1 8262 my ( $self, @arg ) = @_;
102              
103 30         51 delete $self->{err};
104              
105 30         62 while ( @arg ) {
106 45         102 my ( $name, $val ) = splice @arg, 0, 2;
107              
108 45         89 state $config = {
109             almanacconfigfile => \&_config_almanac_config_file,
110             defaults => \&_config_almanac_default,
111             elevation => \&_config_almanac_var_elevation,
112             language => \&_config_almanac_var_language,
113             latitude => \&_config_almanac_var_latitude,
114             location => \&_config_almanac_var_location,
115             longitude => \&_config_almanac_var_longitude,
116             name => \&_config_almanac_var_name,
117             sky => \&_config_almanac_var_sky,
118             twilight => \&_config_almanac_var_twilight,
119             };
120              
121 45 100       123 if ( my $code = $config->{ lc $name } ) {
122 43         98 $code->( $self, $name, $val );
123             } else {
124 2         7 $self->dmd()->config( $name, $val );
125             }
126             }
127              
128 30         25867 return;
129             }
130              
131             sub dmd {
132 76     76 1 133 my ( $self ) = @_;
133 76         305 return $self->{dmd};
134             }
135              
136             sub err {
137 0     0 1 0 my ( $self ) = @_;
138 0   0     0 return $self->{err} // $self->dmd()->err();
139             }
140              
141             sub get_config {
142 51     51 1 5764 my ( $self, @arg ) = @_;
143 51         81 delete $self->{err};
144 51         70 my @rslt;
145              
146 51         87 foreach my $name ( @arg ) {
147 63         160 state $mine = { map { $_ => 1 } qw{
  21         52  
148             elevation latitude location longitude name sky twilight } };
149 63 100       188 if ( $mine->{$name} ) {
150             my $code = $self->can( "_get_config_$name" ) || sub {
151 47   100 39   330 $_[0]{config}{$name} };
  39         142  
152 47         104 push @rslt, scalar $code->( $self );
153             } else {
154 16         46 push @rslt, $self->dmd()->get_config( $name );
155             }
156             }
157              
158 51 100       494 return 1 == @rslt ? $rslt[0] : @rslt;
159             }
160              
161             sub input {
162 3     3 0 31335 my ( $self ) = @_;
163 3         10 return $self->{input};
164             }
165              
166             sub list_events {
167 0     0 0 0 my ( $self, @args ) = @_;
168 0 0 0     0 Scalar::Util::blessed( $args[0] )
169             and $args[0]->isa( __PACKAGE__ )
170             and $args[0] = $args[0]->dmd();
171 0         0 return $self->dmd()->list_events( @args );
172             }
173              
174             sub parse {
175 11     11 1 23 my ( $self, $string ) = @_;
176 11         30 my ( $idate, @event ) = $self->__parse_pre( $string );
177 11   33     29 return $self->dmd()->parse( $idate ) || $self->__parse_post( @event );
178             }
179              
180             sub parse_time {
181 0     0 1 0 my ( $self, $string ) = @_;
182 0         0 my ( $idate, @event ) = $self->__parse_pre( $string );
183 0   0     0 return $self->dmd()->parse_time( $idate ) || $self->__parse_post( @event );
184             }
185              
186             sub _config_almanac_config_file {
187             # my ( $self, $name, $fn ) = @_;
188 2     2   8 my ( $self, undef, $fn ) = @_;
189             open my $fh, '<:encoding(utf-8)', $fn ## no critic (RequireBriefOpen)
190 2 50   2   61 or do {
  2         14  
  2         4  
  2         12  
191 0         0 warn "ERROR: [almanac_config_file] unable to open file $fn: $!";
192 0         0 return 1;
193             };
194 2         2050 my $config_file_processed;
195 2         7 local $_ = undef; # while (<>) ... does not localize $_.
196 2         58 while ( <$fh> ) {
197 18 100       91 m/ \S /smx
198             or next;
199 16 50       35 m/ \A \s* [#] /smx
200             and next;
201 16         37 s/ \A \s+ //smx;
202 16         75 s/ \s+ \z //smx;
203 16         122 my ( $name, $val ) = split qr< \s* = \s* >smx, $_, 2;
204 16 50       49 if ( m/ \A [*] ( .* ) /smx ) {
205             # TODO retire exception for *almanac once I'm fully to new
206             # config file structure.
207 0         0 state $allow = { map { $_ => 1 } qw{ almanac } };
  0         0  
208 0 0       0 unless ( $allow->{ lc $1 } ) {
209 0         0 warn "WARNING: [almanac_config_file] section '$_' ",
210             "not allowed in AlmanacConfigFile $fn line $.\n";
211 0         0 last;
212             }
213             } else {
214 16 50       48 if ( $name =~ m/ \A ConfigFile \z /smxi ) {
    50          
215 0         0 $config_file_processed = 1;
216             } elsif ( $config_file_processed ) {
217 0         0 warn "Config item '$name' after ConfigFile in $fn line $.\n";
218             }
219 16         38 $self->config( $name, $val );
220             }
221             }
222 2         36 close $fh;
223 2         13 return;
224             }
225              
226             sub _config_almanac_default {
227 1     1   3 my ( $self, $name, $val ) = @_;
228 1         1 %{ $self->{config} } = ();
  1         11  
229 1         3 delete $self->{lang};
230 1   33     4 my $rslt = $self->dmd()->config( $name, $val ) ||
231             $self->_update_language() ||
232             $self->_config_almanac_default_sky() ||
233             $self->_config_almanac_var_twilight( twilight => DEFAULT_TWILIGHT );
234 1         5 return $rslt;
235             }
236              
237             sub _config_almanac_default_sky {
238 3     3   7 my ( $self ) = @_;
239 3         12 return $self->_config_almanac_var_sky( sky => [ qw{
240             Astro::Coord::ECI::Sun
241             Astro::Coord::ECI::Moon
242             } ],
243             );
244             }
245              
246             sub _config_almanac_var_language {
247 8     8   20 my ( $self, $name, $val ) = @_;
248 8         19 my $rslt;
249 8 50       21 $rslt = $self->dmd()->config( $name, $val )
250             and return $rslt;
251              
252             # FIXME Doing ourselves after the embedded DMD object can result in
253             # an inconsistency if DMD supports a language but we do not. But I
254             # see no way to avoid this in all cases, because the embedded object
255             # may have been configured in some way (such as a configuration
256             # file) that we can't intercept.
257 8         56348 return $self->_update_language();
258             }
259              
260             sub _update_language {
261 9     9   7836 my ( $self ) = @_;
262 9         38 my $lang = lc $self->get_config( 'language' );
263              
264             exists $self->{lang}
265             and $lang eq $self->{lang}
266 9 50 66     53 and return 0;
267              
268 9 50       28 my $mod = __load_language( $lang )
269             or return 1;
270              
271 9         26 $self->{lang}{lang} = $lang;
272 9         19 $self->{lang}{mod} = $mod;
273 9         16 delete $self->{lang}{obj};
274              
275 9         93 return 0;
276             }
277              
278             # We isolate this so we can hook it to something different during
279             # testing if need be.
280             sub __load_language {
281 9     9   22 my ( $lang ) = @_;
282              
283 9         27 my $module = "Date::ManipX::Almanac::Lang::\L$lang";
284 9         21 local $@ = undef;
285 9 50       21 eval {
286 9         42 Module::Load::load( $module );
287 9         647 1;
288             } and return $module;
289 0         0 warn "ERROR: [language] invalid: $lang\n";
290 0         0 return 0;
291             }
292              
293             sub _config_almanac_var_twilight {
294 9     9   28 my ( $self, $name, $val ) = @_;
295              
296 9         14 my $set_val;
297 9 50       21 if ( defined $val ) {
298 9 50       49 if ( Astro::Coord::ECI::Utils::looks_like_number( $val ) ) {
299 0         0 $set_val = - Astro::Coord::ECI::Utils::deg2rad( abs $val );
300             } else {
301 9 50       28 defined( $set_val = $self->_get_twilight_qual( $val ) )
302             or return $self->_my_config_err(
303             "Do not recognize '$val' twilight" );
304             }
305             }
306              
307 9         25 $self->{config}{twilight} = $val;
308 9         19 $self->{config}{_twilight} = $set_val;
309             $self->{config}{location}
310 9 100       26 and $self->{config}{location}->set( $name => $set_val );
311              
312 9         74 return;
313             }
314              
315             sub _config_var_is_eci {
316 35     35   62 my ( undef, undef, $val ) = @_;
317 35 50 66     219 ref $val
      66        
318             and Scalar::Util::blessed( $val )
319             and $val->isa( 'Astro::Coord::ECI' )
320             or return;
321 23         73 return $val;
322             }
323              
324             # This ought to be in Astro::Coord::ECI::Utils
325             sub _hms2rad {
326 2     2   172 my ( $hms ) = @_;
327 2         27 my ( $hr, $min, $sec ) = split qr < : >smx, $hms;
328 2   50     16 $_ ||= 0 for $sec, $min, $hr;
329 2         18 return TWOPI * ( ( ( $sec / 60 ) + $min ) / 60 + $hr ) / 24;
330             }
331              
332             sub _config_var_is_eci_class {
333 23     23   50 my ( $self, $name, $val ) = @_;
334 23         36 my $rslt;
335 23 100       51 $rslt = $self->_config_var_is_eci( $name, $val )
336             and return $rslt;
337 12 50       26 if ( ! ref $val ) {
338 12         41 my ( $class, @arg ) = Text::ParseWords::shellwords( $val );
339 12         1145 Module::Load::load( $class );
340             state $factory = {
341             'Astro::Coord::ECI::Star' => sub {
342 2     2   8 my ( $name, $ra, $decl, $rng ) = @_;
343 2         18 return Astro::Coord::ECI::Star->new(
344             name => $name,
345             )->position(
346             _hms2rad( $ra ),
347             Astro::Coord::ECI::Utils::deg2rad( $decl ),
348             $rng,
349             );
350             },
351 12         19636 };
352 12   100 10   83 my $code = $factory->{$class} || sub { $class->new() };
  10         54  
353 12         28 my $obj = $code->( @arg );
354 12 50       4260 if ( $rslt = $self->_config_var_is_eci( $name, $obj ) ) {
355 12         58 return $rslt;
356             }
357             }
358             $self->_my_config_err(
359 0         0 "$val must be an Astro::Coord::ECI object or class" );
360 0         0 return;
361             }
362              
363             sub _config_almanac_var_elevation {
364 3     3   6 my ( $self, $name, $val ) = @_;
365 3 50 33     23 if ( defined $val &&
366             Astro::Coord::ECI::Utils::looks_like_number( $val ) ) {
367 3         8 $self->{config}{$name} = $val;
368 3         7 delete $self->{config}{location};
369 3         7 return;
370             } else {
371 0         0 return $self->_my_config_err( "\u$name must be a number" );
372             }
373             }
374              
375             sub _config_almanac_var_latitude {
376 3     3   8 my ( $self, $name, $val ) = @_;
377 3 50 33     38 if ( defined $val &&
      33        
      33        
378             Astro::Coord::ECI::Utils::looks_like_number( $val ) &&
379             $val >= -90 && $val <= 90 ) {
380 3         13 $self->{config}{$name} = $val;
381 3         8 delete $self->{config}{location};
382 3         9 return;
383             } else {
384 0         0 return $self->_my_config_err(
385             "\u$name must be a number between -90 and 90 degrees" );
386             }
387             }
388              
389             sub _config_almanac_var_location {
390 6     6   13 my ( $self, $name, $val ) = @_;
391 6         10 my $loc;
392 6 100       22 if ( ! defined $val ) {
393 4         7 $loc = undef;
394 4         15 delete @{ $self->{config} }{
395 4         7 qw{ elevation latitude longitude name } };
396             } else {
397 2 50       6 $loc = $self->_config_var_is_eci_class( $name, $val )
398             or return 1;
399 2         8 my ( $lat, $lon, $ele ) = $loc->geodetic();
400 2         66 $self->{config}{elevation} = $ele * METERS_PER_KILOMETER;
401 2         7 $self->{config}{latitude} = Astro::Coord::ECI::Utils::rad2deg( $lat );
402 2         11 $self->{config}{longitude} = Astro::Coord::ECI::Utils::rad2deg( $lon );
403 2         10 $self->{config}{name} = $loc->get( 'name' );
404             }
405              
406             defined $self->{config}{_twilight}
407             and defined $loc
408 6 100 100     67 and $loc->set( twilight => $self->{config}{_twilight} );
409 6 100       64 $_->set( station => $loc ) for @{ $self->{config}{sky} || [] };
  6         34  
410 6         303 $self->{config}{location} = $loc;
411              
412             # NOTE we do this because when the Lang object initializes itself it
413             # consults the first sky object's station attribute (set above) to
414             # figure out whether it is in the Northern or Southern hemisphere.
415             # The object will be re-created when we actually try to perform a
416             # parse.
417 6         16 delete $self->{lang}{obj};
418              
419 6         18 return;
420             }
421              
422             sub _config_almanac_var_longitude {
423 3     3   6 my ( $self, $name, $val ) = @_;
424 3 50 33     29 if ( defined $val &&
      33        
      33        
425             Astro::Coord::ECI::Utils::looks_like_number( $val ) &&
426             $val >= -180 && $val <= 180 ) {
427 3         8 $self->{config}{$name} = $val;
428 3         5 delete $self->{config}{location};
429 3         19 return;
430             } else {
431 0         0 return $self->_my_config_err(
432             "\u$name must be a number between -180 and 180 degrees" );
433             }
434             }
435              
436             sub _config_almanac_var_name {
437 3     3   8 my ( $self, $name, $val ) = @_;
438 3 50       16 if ( defined $val ) {
439 3         10 $self->{config}{$name} = $val;
440             } else {
441 0         0 delete $self->{config}{$name};
442             }
443 3         5 delete $self->{config}{location};
444 3         8 return;
445             }
446              
447             sub _config_almanac_var_sky {
448 15     15   29 my ( $self, $name, $values ) = @_;
449              
450 15         21 my @sky;
451 15 100       35 unless ( ref $values ) {
452 7 100 66     28 if ( defined( $values ) && $values ne '' ) {
453 6         31 $values = [ $values ];
454 6 100       9 @sky = @{ $self->{config}{sky} || [] };
  6         33  
455             } else {
456 1         2 $values = [];
457 1         3 @{ $self->{config}{sky} } = ();
  1         16  
458             }
459             }
460              
461 15         24 foreach my $val ( @{ $values } ) {
  15         31  
462 21 50       151 my $body = $self->_config_var_is_eci_class( $name, $val )
463             or return 1;
464 21         60 push @sky, $body;
465 21 100       46 if ( my $loc = $self->_get_config_location() ) {
466 9         28 $sky[-1]->set( station => $loc );
467             }
468             }
469              
470 15         397 @{ $self->{config}{sky} } = @sky;
  15         49  
471              
472             # NOTE we do this to force re-creation of the Lang object, which
473             # then picks up the new sky.
474 15         27 delete $self->{lang}{obj};
475              
476 15         47 return;
477             }
478              
479             sub _get_config_location {
480 40     40   64 my ( $self ) = @_;
481             my $cfg = $self->{config}
482 40 100       103 or return;
483             $cfg->{location}
484 36 100       97 and return $cfg->{location};
485             defined $cfg->{latitude}
486             and defined $cfg->{longitude}
487 15 100 66     61 or return;
488 3         21 my $loc = Astro::Coord::ECI->new();
489             defined $cfg->{name}
490 3 50       159 and $loc->set( name => $cfg->{name} );
491             defined $cfg->{_twilight}
492 3 100       76 and $loc->set( twilight => $cfg->{_twilight} );
493             $loc->geodetic(
494             Astro::Coord::ECI::Utils::deg2rad( $cfg->{latitude} ),
495             Astro::Coord::ECI::Utils::deg2rad( $cfg->{longitude} ),
496 3   50     58 ( $cfg->{elevation} || 0 ) / METERS_PER_KILOMETER,
497             );
498 3 100       569 $_->set( station => $loc ) for @{ $self->{config}{sky} || [] };
  3         22  
499              
500             # NOTE we do this because when the Lang object initializes itself it
501             # consults the first sky object's station attribute (set above) to
502             # figure out whether it is in the Northern or Southern hemisphere.
503             # The object will be re-created when we actually try to perform a
504             # parse.
505 3         109 delete $self->{lang}{obj};
506              
507 3         10 return( $cfg->{location} = $loc );
508             }
509              
510             sub _get_twilight_qual {
511 12     12   64 my ( undef, $qual ) = @_; # Invocant not used
512 12 50       46 defined $qual
513             or return $qual;
514 12         28 state $twi_name = {
515             civil => Astro::Coord::ECI::Utils::deg2rad( -6 ),
516             nautical => Astro::Coord::ECI::Utils::deg2rad( -12 ),
517             astronomical => Astro::Coord::ECI::Utils::deg2rad( -18 ),
518             };
519 12         81 return $twi_name->{ lc $qual };
520             }
521              
522             sub _init_almanac {
523 7     7   16 my ( $self, $from ) = @_;
524 7 100 66     53 if ( Scalar::Util::blessed( $from ) && $from->isa( __PACKAGE__ ) ) {
525 4         11 state $cfg_var = [ qw{ language location sky twilight } ];
526 4         9 my %cfg;
527 4         7 @cfg{ @{ $cfg_var } } = $from->get_config( @{ $cfg_var } );
  4         13  
  4         13  
528             # We clone because these objects have state.
529             # TODO this requires at least 0.118_01.
530 4         10 @{ $cfg{sky} } = map { $_->clone() } @{ $cfg{sky} };
  4         180  
  9         59  
  4         11  
531 4         19 $self->config( %cfg );
532             } else {
533 3         13 $self->_init_almanac_language( 1 );
534 3 50       10 if ( my $lang = $self->get_config( 'language' ) ) {
535 3         12 $self->_config_almanac_var_language( language => $lang );
536             }
537 3         6 %{ $self->{config} } = ();
  3         11  
538             }
539 7         18 return;
540             }
541              
542             sub _init_almanac_language {
543 3     3   7 my ( $self, $force ) = @_;
544              
545             not $force
546             and exists $self->{lang}
547 3 0 33     9 and return;
548              
549 3         14 $self->{lang} = {};
550              
551 3         6 return;
552             }
553              
554             sub _my_config_err {
555 0     0   0 my ( undef, $err ) = @_;
556 0         0 warn "ERROR: [config_var] $err\n";
557 0         0 return 1;
558             }
559              
560             sub __parse_pre {
561 14     14   8715 my ( $self, $string ) = @_;
562             wantarray
563 14 50       36 or confess 'Bug - __parse_pre() must be called in list context';
564 14         22 delete $self->{err};
565 14         25 $self->{input} = $string;
566 14 50       19 @{ $self->{config}{sky} || [] }
  14 50       46  
567             or return $string;
568              
569             $self->{lang}{obj} ||= $self->{lang}{mod}->__new(
570             sky => $self->{config}{sky},
571 14   66     44 );
572 14         47 return $self->{lang}{obj}->__parse_pre( $string );
573             }
574              
575             sub __parse_post {
576 11     11   56074 my ( $self, $body, $event, undef ) = @_;
577 11 50 33     48 defined $body
578             and defined $event
579             or return;
580              
581 11 50       28 $self->_get_config_location()
582             or return $self->_set_err( "[parse] Location not configured" );
583              
584 11 50       56 my $code = $self->can( "__parse_post__$event" )
585             or confess "Bug - event $event not implemented";
586              
587             # TODO support for systems that do not use this epoch.
588 11         31 $body->universal( $self->secs_since_1970_GMT() );
589              
590 11         9066 goto $code;
591             }
592              
593             sub _set_err {
594 0     0   0 my ( $self, $err ) = @_;
595              
596 0         0 $self->{err} = $err;
597 0         0 return 1;
598             }
599              
600             sub __parse_post__horizon {
601 3     3   10 my ( $self, $body, undef, $detail ) = @_;
602              
603 3         10 my $almanac_horizon = $body->get( 'station' )->get(
604             'almanac_horizon' );
605              
606 3         95 my ( $time, $which );
607 3         39 while ( 1 ) {
608 5         24 ( $time, $which ) = $body->next_elevation( $almanac_horizon, 1 );
609 5 100       158383 $which == $detail
610             and last;
611             }
612              
613 3         16 $self->secs_since_1970_GMT( $time );
614              
615 3         1752 return;
616             }
617              
618             sub __parse_post__meridian {
619 2     2   6 my ( $self, $body, undef, $detail ) = @_;
620              
621 2         4 my ( $time, $which );
622 2         2 while ( 1 ) {
623 3         15 ( $time, $which ) = $body->next_meridian();
624 3 100       34723 $which == $detail
625             and last;
626             }
627              
628 2         8 $self->secs_since_1970_GMT( $time );
629              
630 2         1074 return;
631             }
632              
633             sub __parse_post__quarter {
634 3     3   9 my ( $self, $body, undef, $detail ) = @_;
635              
636 3         12 my $time = $body->next_quarter( $detail );
637              
638 3         33681 $self->secs_since_1970_GMT( $time );
639              
640 3         1448 return;
641             }
642              
643             sub __parse_post__twilight {
644 3     3   9 my ( $self, $body, undef, $detail, $qual ) = @_;
645              
646 3         10 my $station = $body->get( 'station' );
647 3   66     64 my $twilight = $station->get( 'almanac_horizon' ) + (
648             $self->_get_twilight_qual( $qual ) // $station->get( 'twilight' ) );
649              
650 3         28 my ( $time, $which );
651 3         5 while ( 1 ) {
652 4         13 ( $time, $which ) = $body->next_elevation( $twilight, 0 );
653 4 100       68593 $which == $detail
654             and last;
655             }
656              
657 3         13 $self->secs_since_1970_GMT( $time );
658              
659 3         1689 return;
660             }
661              
662             # Implemented as a subroutine so I can authortest for changes. This was
663             # the list as of Date::Manip::Date version 6.85. The list is generated
664             # by tools/dmd_public_interface.
665             sub __date_manip_date_public_interface {
666 3     3   20 return ( qw{
667             base
668             calc
669             cmp
670             complete
671             config
672             convert
673             err
674             get_config
675             holiday
676             input
677             is_business_day
678             is_date
679             is_delta
680             is_recur
681             list_events
682             list_holidays
683             nearest_business_day
684             new
685             new_config
686             new_date
687             new_delta
688             new_recur
689             next
690             next_business_day
691             parse
692             parse_date
693             parse_format
694             parse_time
695             prev
696             prev_business_day
697             printf
698             secs_since_1970_GMT
699             set
700             tz
701             value
702             version
703             week_of_year
704             } );
705             }
706              
707             {
708             local $@ = undef;
709             *_my_set_subname = eval {
710             require Sub::Util;
711             Sub::Util->can( 'set_subname' );
712             } || sub { $_[1] };
713             }
714              
715             foreach my $method ( __date_manip_date_public_interface() ) {
716             __PACKAGE__->can( $method )
717             and next;
718             Date::Manip::Date->can( $method )
719             or next;
720 3     3   33 no strict qw{ refs };
  3         5  
  3         290  
721             *$method = _my_set_subname( $method => sub {
722 34     34 0 929 my ( $self, @arg ) = @_;
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
        34 0    
723 34         80 return $self->dmd()->$method( @arg );
724             },
725             );
726             }
727              
728             1;
729              
730             __END__