File Coverage

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


line stmt bran cond sub pod time code
1             package Date::ManipX::Almanac::Date;
2              
3 3     3   348772 use 5.010;
  3         9  
4              
5 3     3   11 use strict;
  3         5  
  3         80  
6 3     3   10 use warnings;
  3         10  
  3         152  
7              
8 3     3   1123 use Astro::Coord::ECI 0.119; # For clone() to work.
  3         28966  
  3         156  
9 3     3   17 use Astro::Coord::ECI::Utils 0.119 qw{ TWOPI };
  3         60  
  3         154  
10 3     3   14 use Carp;
  3         4  
  3         153  
11 3     3   2918 use Date::Manip::Date;
  3         258944  
  3         117  
12 3     3   1611 use Module::Load ();
  3         3281  
  3         66  
13 3     3   17 use Scalar::Util ();
  3         6  
  3         30  
14 3     3   1050 use Text::ParseWords ();
  3         3875  
  3         137  
15              
16             our $VERSION = '0.004';
17              
18 3     3   19 use constant DEFAULT_TWILIGHT => 'civil';
  3         5  
  3         285  
19 3     3   13 use constant REF_ARRAY => ref [];
  3         5  
  3         112  
20 3     3   11 use constant REF_HASH => ref {};
  3         3  
  3         123  
21 3     3   12 use constant METERS_PER_KILOMETER => 1000;
  3         4  
  3         11365  
22              
23             sub new {
24 6     6 1 307876 my ( $class, @args ) = @_;
25 6         35 return $class->_new( new => @args );
26             }
27              
28             sub _new {
29 7     7   26 my ( $class, $new_method, @args ) = @_;
30              
31 7         16 my @config;
32 7 100 66     36 if ( @args && REF_ARRAY eq ref $args[-1] ) {
33 1         2 @config = @{ pop @args };
  1         2  
34 1         3 state $method_map = {
35             new => 'new_config',
36             };
37 1   33     3 $new_method = $method_map->{$new_method} // $new_method;
38             }
39              
40 7         11 my ( $dmd, $from );
41 7 100       28 if ( ref $class ) {
    50          
42 4         8 $from = $class;
43 4         14 $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         36 $dmd = Date::Manip::Date->$new_method();
51             }
52              
53 7   66     48890 my $self = bless {
54             dmd => $dmd,
55             }, ref $class || $class;
56              
57 7         78 $self->_init_almanac( $from );
58              
59             @config
60 7 100       20 and $self->config( @config );
61              
62 7 100       20 $self->get_config( 'sky' )
63             or $self->_config_almanac_default_sky();
64 7 100       21 defined $self->get_config( 'twilight' )
65             or $self->_config_almanac_var_twilight(
66             twilight => DEFAULT_TWILIGHT );
67              
68             @args
69 7 50       13 and $self->parse( @args );
70              
71 7         40 return $self;
72             }
73              
74             sub new_config {
75 1     1 1 460 my ( $class, @args ) = @_;
76 1         4 return $class->_new( new_config => @args );
77             }
78              
79             sub new_date {
80 1     1 1 742 my ( $class, @args ) = @_;
81             # return $class->_new( new_date => @args );
82 1         9 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 13958 my ( $self, @arg ) = @_;
102              
103 30         54 delete $self->{err};
104              
105 30         56 while ( @arg ) {
106 45         104 my ( $name, $val ) = splice @arg, 0, 2;
107              
108 45         108 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       117 if ( my $code = $config->{ lc $name } ) {
122 43         100 $code->( $self, $name, $val );
123             } else {
124 2         25 $self->dmd()->config( $name, $val );
125             }
126             }
127              
128 30         25903 return;
129             }
130              
131             sub dmd {
132 76     76 1 148 my ( $self ) = @_;
133 76         429 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 6890 my ( $self, @arg ) = @_;
143 51         96 delete $self->{err};
144 51         66 my @rslt;
145              
146 51         86 foreach my $name ( @arg ) {
147 63         194 state $mine = { map { $_ => 1 } qw{
  21         115  
148             elevation latitude location longitude name sky twilight } };
149 63 100       128 if ( $mine->{$name} ) {
150             my $code = $self->can( "_get_config_$name" ) || sub {
151 47   66 39   389 $_[0]{config}{$name} };
  39         178  
152 47         125 push @rslt, scalar $code->( $self );
153             } else {
154 16         43 push @rslt, $self->dmd()->get_config( $name );
155             }
156             }
157              
158 51 100       470 return 1 == @rslt ? $rslt[0] : @rslt;
159             }
160              
161             sub input {
162 3     3 0 25777 my ( $self ) = @_;
163 3         27 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 22 my ( $self, $string ) = @_;
176 11         32 my ( $idate, @event ) = $self->__parse_pre( $string );
177 11   33     32 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   7 my ( $self, undef, $fn ) = @_;
189             open my $fh, '<:encoding(utf-8)', $fn ## no critic (RequireBriefOpen)
190 2 50   2   101 or do {
  2         1416  
  2         28  
  2         8  
191 0         0 warn "ERROR: [almanac_config_file] unable to open file $fn: $!";
192 0         0 return 1;
193             };
194 2         2605 my $config_file_processed;
195 2         5 local $_ = undef; # while (<>) ... does not localize $_.
196 2         74 while ( <$fh> ) {
197 18 100       87 m/ \S /smx
198             or next;
199 16 50       37 m/ \A \s* [#] /smx
200             and next;
201 16         33 s/ \A \s+ //smx;
202 16         79 s/ \s+ \z //smx;
203 16         120 my ( $name, $val ) = split qr< \s* = \s* >smx, $_, 2;
204 16 50       36 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       47 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         34 $self->config( $name, $val );
220             }
221             }
222 2         35 close $fh;
223 2         33 return;
224             }
225              
226             sub _config_almanac_default {
227 1     1   4 my ( $self, $name, $val ) = @_;
228 1         3 %{ $self->{config} } = ();
  1         18  
229 1         6 delete $self->{lang};
230 1   33     5 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         4 return $rslt;
235             }
236              
237             sub _config_almanac_default_sky {
238 3     3   6 my ( $self ) = @_;
239 3         23 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         15 my $rslt;
249 8 50       20 $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         48230 return $self->_update_language();
258             }
259              
260             sub _update_language {
261 9     9   6738 my ( $self ) = @_;
262 9         22 my $lang = lc $self->get_config( 'language' );
263              
264             exists $self->{lang}
265             and $lang eq $self->{lang}
266 9 50 66     78 and return 0;
267              
268 9 50       32 my $mod = __load_language( $lang )
269             or return 1;
270              
271 9         30 $self->{lang}{lang} = $lang;
272 9         33 $self->{lang}{mod} = $mod;
273 9         16 delete $self->{lang}{obj};
274              
275 9         33 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   18 my ( $lang ) = @_;
282              
283 9         64 my $module = "Date::ManipX::Almanac::Lang::\L$lang";
284 9         17 local $@ = undef;
285 9 50       14 eval {
286 9         62 Module::Load::load( $module );
287 9         610 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   22 my ( $self, $name, $val ) = @_;
295              
296 9         13 my $set_val;
297 9 50       24 if ( defined $val ) {
298 9 50       39 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       34 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         22 $self->{config}{twilight} = $val;
308 9         35 $self->{config}{_twilight} = $set_val;
309             $self->{config}{location}
310 9 100       44 and $self->{config}{location}->set( $name => $set_val );
311              
312 9         100 return;
313             }
314              
315             sub _config_var_is_eci {
316 35     35   63 my ( undef, undef, $val ) = @_;
317 35 50 66     183 ref $val
      66        
318             and Scalar::Util::blessed( $val )
319             and $val->isa( 'Astro::Coord::ECI' )
320             or return;
321 23         61 return $val;
322             }
323              
324             # This ought to be in Astro::Coord::ECI::Utils
325             sub _hms2rad {
326 2     2   170 my ( $hms ) = @_;
327 2         17 my ( $hr, $min, $sec ) = split qr < : >smx, $hms;
328 2   50     11 $_ ||= 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   38 my ( $self, $name, $val ) = @_;
334 23         23 my $rslt;
335 23 100       46 $rslt = $self->_config_var_is_eci( $name, $val )
336             and return $rslt;
337 12 50       5028 if ( ! ref $val ) {
338 12         50 my ( $class, @arg ) = Text::ParseWords::shellwords( $val );
339 12         1278 Module::Load::load( $class );
340             state $factory = {
341             'Astro::Coord::ECI::Star' => sub {
342 2     2   6 my ( $name, $ra, $decl, $rng ) = @_;
343 2         29 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         19569 };
352 12   66 10   74 my $code = $factory->{$class} || sub { $class->new() };
  10         72  
353 12         46 my $obj = $code->( @arg );
354 12 50       3798 if ( $rslt = $self->_config_var_is_eci( $name, $obj ) ) {
355 12         69 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   8 my ( $self, $name, $val ) = @_;
365 3 50 33     20 if ( defined $val &&
366             Astro::Coord::ECI::Utils::looks_like_number( $val ) ) {
367 3         7 $self->{config}{$name} = $val;
368 3         3 delete $self->{config}{location};
369 3         6 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     54 if ( defined $val &&
      33        
      33        
378             Astro::Coord::ECI::Utils::looks_like_number( $val ) &&
379             $val >= -90 && $val <= 90 ) {
380 3         8 $self->{config}{$name} = $val;
381 3         6 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   15 my ( $self, $name, $val ) = @_;
391 6         10 my $loc;
392 6 100       14 if ( ! defined $val ) {
393 4         7 $loc = undef;
394 4         19 delete @{ $self->{config} }{
395 4         44 qw{ elevation latitude longitude name } };
396             } else {
397 2 50       3 $loc = $self->_config_var_is_eci_class( $name, $val )
398             or return 1;
399 2         17 my ( $lat, $lon, $ele ) = $loc->geodetic();
400 2         74 $self->{config}{elevation} = $ele * METERS_PER_KILOMETER;
401 2         10 $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     54 and $loc->set( twilight => $self->{config}{_twilight} );
409 6 100       36 $_->set( station => $loc ) for @{ $self->{config}{sky} || [] };
  6         45  
410 6         342 $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         11 delete $self->{lang}{obj};
418              
419 6         19 return;
420             }
421              
422             sub _config_almanac_var_longitude {
423 3     3   7 my ( $self, $name, $val ) = @_;
424 3 50 33     45 if ( defined $val &&
      33        
      33        
425             Astro::Coord::ECI::Utils::looks_like_number( $val ) &&
426             $val >= -180 && $val <= 180 ) {
427 3         6 $self->{config}{$name} = $val;
428 3         6 delete $self->{config}{location};
429 3         15 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   7 my ( $self, $name, $val ) = @_;
438 3 50       6 if ( defined $val ) {
439 3         7 $self->{config}{$name} = $val;
440             } else {
441 0         0 delete $self->{config}{$name};
442             }
443 3         6 delete $self->{config}{location};
444 3         6 return;
445             }
446              
447             sub _config_almanac_var_sky {
448 15     15   29 my ( $self, $name, $values ) = @_;
449              
450 15         19 my @sky;
451 15 100       31 unless ( ref $values ) {
452 7 100 66     27 if ( defined( $values ) && $values ne '' ) {
453 6         12 $values = [ $values ];
454 6 100       8 @sky = @{ $self->{config}{sky} || [] };
  6         19  
455             } else {
456 1         4 $values = [];
457 1         2 @{ $self->{config}{sky} } = ();
  1         30  
458             }
459             }
460              
461 15         22 foreach my $val ( @{ $values } ) {
  15         35  
462 21 50       57 my $body = $self->_config_var_is_eci_class( $name, $val )
463             or return 1;
464 21         32 push @sky, $body;
465 21 100       39 if ( my $loc = $self->_get_config_location() ) {
466 6         15 $sky[-1]->set( station => $loc );
467             }
468             }
469              
470 15         341 @{ $self->{config}{sky} } = @sky;
  15         52  
471              
472             # NOTE we do this to force re-creation of the Lang object, which
473             # then picks up the new sky.
474 15         25 delete $self->{lang}{obj};
475              
476 15         44 return;
477             }
478              
479             sub _get_config_location {
480 40     40   63 my ( $self ) = @_;
481             my $cfg = $self->{config}
482 40 100       89 or return;
483             $cfg->{location}
484 37 100       105 and return $cfg->{location};
485             defined $cfg->{latitude}
486             and defined $cfg->{longitude}
487 19 100 66     67 or return;
488 3         11 my $loc = Astro::Coord::ECI->new();
489             defined $cfg->{name}
490 3 50       148 and $loc->set( name => $cfg->{name} );
491             defined $cfg->{_twilight}
492 3 100       58 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     50 ( $cfg->{elevation} || 0 ) / METERS_PER_KILOMETER,
497             );
498 3 100       581 $_->set( station => $loc ) for @{ $self->{config}{sky} || [] };
  3         16  
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         132 delete $self->{lang}{obj};
506              
507 3         10 return( $cfg->{location} = $loc );
508             }
509              
510             sub _get_twilight_qual {
511 12     12   51 my ( undef, $qual ) = @_; # Invocant not used
512 12 50       41 defined $qual
513             or return $qual;
514 12         23 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         76 return $twi_name->{ lc $qual };
520             }
521              
522             sub _init_almanac {
523 7     7   22 my ( $self, $from ) = @_;
524 7 100 66     75 if ( Scalar::Util::blessed( $from ) && $from->isa( __PACKAGE__ ) ) {
525 4         14 state $cfg_var = [ qw{ language location sky twilight } ];
526 4         7 my %cfg;
527 4         7 @cfg{ @{ $cfg_var } } = $from->get_config( @{ $cfg_var } );
  4         44  
  4         71  
528             # We clone because these objects have state.
529             # TODO this requires at least 0.118_01.
530 4         7 @{ $cfg{sky} } = map { $_->clone() } @{ $cfg{sky} };
  4         117  
  9         76  
  4         15  
531 4         29 $self->config( %cfg );
532             } else {
533 3         44 $self->_init_almanac_language( 1 );
534 3 50       36 if ( my $lang = $self->get_config( 'language' ) ) {
535 3         29 $self->_config_almanac_var_language( language => $lang );
536             }
537 3         7 %{ $self->{config} } = ();
  3         11  
538             }
539 7         19 return;
540             }
541              
542             sub _init_almanac_language {
543 3     3   12 my ( $self, $force ) = @_;
544              
545             not $force
546             and exists $self->{lang}
547 3 0 33     31 and return;
548              
549 3         25 $self->{lang} = {};
550              
551 3         15 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   8560 my ( $self, $string ) = @_;
562             wantarray
563 14 50       60 or confess 'Bug - __parse_pre() must be called in list context';
564 14         25 delete $self->{err};
565 14         20 $self->{input} = $string;
566 14 50       19 @{ $self->{config}{sky} || [] }
  14 50       51  
567             or return $string;
568              
569             $self->{lang}{obj} ||= $self->{lang}{mod}->__new(
570             sky => $self->{config}{sky},
571 14   66     55 );
572 14         56 return $self->{lang}{obj}->__parse_pre( $string );
573             }
574              
575             sub __parse_post {
576 11     11   55268 my ( $self, $body, $event, undef ) = @_;
577 11 50 33     65 defined $body
578             and defined $event
579             or return;
580              
581 11 50       34 $self->_get_config_location()
582             or return $self->_set_err( "[parse] Location not configured" );
583              
584 11 50       81 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         37 $body->universal( $self->secs_since_1970_GMT() );
589              
590 11         8695 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   7 my ( $self, $body, undef, $detail ) = @_;
602              
603 3         12 my $almanac_horizon = $body->get( 'station' )->get(
604             'almanac_horizon' );
605              
606 3         85 my ( $time, $which );
607 3         3 while ( 1 ) {
608 5         30 ( $time, $which ) = $body->next_elevation( $almanac_horizon, 1 );
609 5 100       136922 $which == $detail
610             and last;
611             }
612              
613 3         17 $self->secs_since_1970_GMT( $time );
614              
615 3         1748 return;
616             }
617              
618             sub __parse_post__meridian {
619 2     2   5 my ( $self, $body, undef, $detail ) = @_;
620              
621 2         3 my ( $time, $which );
622 2         3 while ( 1 ) {
623 3         14 ( $time, $which ) = $body->next_meridian();
624 3 100       29233 $which == $detail
625             and last;
626             }
627              
628 2         8 $self->secs_since_1970_GMT( $time );
629              
630 2         1068 return;
631             }
632              
633             sub __parse_post__quarter {
634 3     3   8 my ( $self, $body, undef, $detail ) = @_;
635              
636 3         22 my $time = $body->next_quarter( $detail );
637              
638 3         29522 $self->secs_since_1970_GMT( $time );
639              
640 3         2600 return;
641             }
642              
643             sub __parse_post__twilight {
644 3     3   10 my ( $self, $body, undef, $detail, $qual ) = @_;
645              
646 3         8 my $station = $body->get( 'station' );
647 3   66     57 my $twilight = $station->get( 'almanac_horizon' ) + (
648             $self->_get_twilight_qual( $qual ) // $station->get( 'twilight' ) );
649              
650 3         38 my ( $time, $which );
651 3         4 while ( 1 ) {
652 4         9 ( $time, $which ) = $body->next_elevation( $twilight, 0 );
653 4 100       66453 $which == $detail
654             and last;
655             }
656              
657 3         14 $self->secs_since_1970_GMT( $time );
658              
659 3         1744 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   28 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   35 no strict qw{ refs };
  3         9  
  3         332  
721             *$method = _my_set_subname( $method => sub {
722 34     34 0 905 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__