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   470594 use 5.010;
  3         23  
4              
5 3     3   21 use strict;
  3         7  
  3         62  
6 3     3   15 use warnings;
  3         17  
  3         94  
7              
8 3     3   848 use Astro::Coord::ECI 0.119; # For clone() to work.
  3         25715  
  3         98  
9 3     3   19 use Astro::Coord::ECI::Utils 0.119 qw{ TWOPI };
  3         41  
  3         151  
10 3     3   19 use Carp;
  3         14  
  3         143  
11 3     3   2537 use Date::Manip::Date;
  3         240976  
  3         109  
12 3     3   1421 use Module::Load ();
  3         2878  
  3         58  
13 3     3   18 use Scalar::Util ();
  3         5  
  3         38  
14 3     3   1250 use Text::ParseWords ();
  3         3505  
  3         110  
15              
16             our $VERSION = '0.002_01';
17              
18 3     3   18 use constant DEFAULT_TWILIGHT => 'civil';
  3         7  
  3         203  
19 3     3   15 use constant REF_ARRAY => ref [];
  3         10  
  3         123  
20 3     3   14 use constant REF_HASH => ref {};
  3         7  
  3         110  
21 3     3   14 use constant METERS_PER_KILOMETER => 1000;
  3         6  
  3         11380  
22              
23             sub new {
24 6     6 1 4447 my ( $class, @args ) = @_;
25 6         25 return $class->_new( new => @args );
26             }
27              
28             sub _new {
29 7     7   17 my ( $class, $new_method, @args ) = @_;
30              
31 7         10 my @config;
32 7 100 66     39 if ( @args && REF_ARRAY eq ref $args[-1] ) {
33 1         2 @config = @{ pop @args };
  1         3  
34 1         4 state $method_map = {
35             new => 'new_config',
36             };
37 1   33     4 $new_method = $method_map->{$new_method} // $new_method;
38             }
39              
40 7         14 my ( $dmd, $from );
41 7 100       33 if ( ref $class ) {
    50          
42 4         7 $from = $class;
43 4         10 $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         32 $dmd = Date::Manip::Date->$new_method();
51             }
52              
53 7   66     31373 my $self = bless {
54             dmd => $dmd,
55             }, ref $class || $class;
56              
57 7         28 $self->_init_almanac( $from );
58              
59             @config
60 7 100       23 and $self->config( @config );
61              
62 7 100       22 $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       16 and $self->parse( @args );
70              
71 7         25 return $self;
72             }
73              
74             sub new_config {
75 1     1 1 378 my ( $class, @args ) = @_;
76 1         4 return $class->_new( new_config => @args );
77             }
78              
79             sub new_date {
80 1     1 1 438 my ( $class, @args ) = @_;
81             # return $class->_new( new_date => @args );
82 1         3 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 8016 my ( $self, @arg ) = @_;
102              
103 30         53 delete $self->{err};
104              
105 30         61 while ( @arg ) {
106 45         91 my ( $name, $val ) = splice @arg, 0, 2;
107              
108 45         93 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       111 if ( my $code = $config->{ lc $name } ) {
122 43         106 $code->( $self, $name, $val );
123             } else {
124 2         6 $self->dmd()->config( $name, $val );
125             }
126             }
127              
128 30         26202 return;
129             }
130              
131             sub dmd {
132 76     76 1 111 my ( $self ) = @_;
133 76         283 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 5867 my ( $self, @arg ) = @_;
143 51         78 delete $self->{err};
144 51         68 my @rslt;
145              
146 51         84 foreach my $name ( @arg ) {
147 63         142 state $mine = { map { $_ => 1 } qw{
  21         43  
148             elevation latitude location longitude name sky twilight } };
149 63 100       125 if ( $mine->{$name} ) {
150             my $code = $self->can( "_get_config_$name" ) || sub {
151 47   100 39   302 $_[0]{config}{$name} };
  39         141  
152 47         105 push @rslt, scalar $code->( $self );
153             } else {
154 16         34 push @rslt, $self->dmd()->get_config( $name );
155             }
156             }
157              
158 51 100       457 return 1 == @rslt ? $rslt[0] : @rslt;
159             }
160              
161             sub input {
162 3     3 0 32746 my ( $self ) = @_;
163 3         13 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 20 my ( $self, $string ) = @_;
176 11         37 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   5 my ( $self, undef, $fn ) = @_;
189             open my $fh, '<:encoding(utf-8)', $fn ## no critic (RequireBriefOpen)
190 2 50   2   62 or do {
  2         12  
  2         3  
  2         10  
191 0         0 warn "ERROR: [almanac_config_file] unable to open file $fn: $!";
192 0         0 return 1;
193             };
194 2         2016 my $config_file_processed;
195 2         4 local $_ = undef; # while (<>) ... does not localize $_.
196 2         62 while ( <$fh> ) {
197 18 100       88 m/ \S /smx
198             or next;
199 16 50       39 m/ \A \s* [#] /smx
200             and next;
201 16         39 s/ \A \s+ //smx;
202 16         73 s/ \s+ \z //smx;
203 16         140 my ( $name, $val ) = split qr< \s* = \s* >smx, $_, 2;
204 16 50       51 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       45 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         35 close $fh;
223 2         15 return;
224             }
225              
226             sub _config_almanac_default {
227 1     1   4 my ( $self, $name, $val ) = @_;
228 1         1 %{ $self->{config} } = ();
  1         12  
229 1         4 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         4 return $rslt;
235             }
236              
237             sub _config_almanac_default_sky {
238 3     3   8 my ( $self ) = @_;
239 3         14 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   15 my ( $self, $name, $val ) = @_;
248 8         11 my $rslt;
249 8 50       16 $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         55135 return $self->_update_language();
258             }
259              
260             sub _update_language {
261 9     9   7967 my ( $self ) = @_;
262 9         28 my $lang = lc $self->get_config( 'language' );
263              
264             exists $self->{lang}
265             and $lang eq $self->{lang}
266 9 50 66     44 and return 0;
267              
268 9 50       23 my $mod = __load_language( $lang )
269             or return 1;
270              
271 9         24 $self->{lang}{lang} = $lang;
272 9         17 $self->{lang}{mod} = $mod;
273 9         15 delete $self->{lang}{obj};
274              
275 9         26 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   14 my ( $lang ) = @_;
282              
283 9         25 my $module = "Date::ManipX::Almanac::Lang::\L$lang";
284 9         15 local $@ = undef;
285 9 50       14 eval {
286 9         31 Module::Load::load( $module );
287 9         466 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   19 my ( $self, $name, $val ) = @_;
295              
296 9         12 my $set_val;
297 9 50       18 if ( defined $val ) {
298 9 50       36 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       20 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         17 $self->{config}{_twilight} = $set_val;
309             $self->{config}{location}
310 9 100       23 and $self->{config}{location}->set( $name => $set_val );
311              
312 9         73 return;
313             }
314              
315             sub _config_var_is_eci {
316 35     35   95 my ( undef, undef, $val ) = @_;
317 35 50 66     201 ref $val
      66        
318             and Scalar::Util::blessed( $val )
319             and $val->isa( 'Astro::Coord::ECI' )
320             or return;
321 23         78 return $val;
322             }
323              
324             # This ought to be in Astro::Coord::ECI::Utils
325             sub _hms2rad {
326 2     2   176 my ( $hms ) = @_;
327 2         26 my ( $hr, $min, $sec ) = split qr < : >smx, $hms;
328 2   50     16 $_ ||= 0 for $sec, $min, $hr;
329 2         20 return TWOPI * ( ( ( $sec / 60 ) + $min ) / 60 + $hr ) / 24;
330             }
331              
332             sub _config_var_is_eci_class {
333 23     23   46 my ( $self, $name, $val ) = @_;
334 23         45 my $rslt;
335 23 100       40 $rslt = $self->_config_var_is_eci( $name, $val )
336             and return $rslt;
337 12 50       23 if ( ! ref $val ) {
338 12         36 my ( $class, @arg ) = Text::ParseWords::shellwords( $val );
339 12         1520 Module::Load::load( $class );
340             state $factory = {
341             'Astro::Coord::ECI::Star' => sub {
342 2     2   10 my ( $name, $ra, $decl, $rng ) = @_;
343 2         20 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         18494 };
352 12   100 10   80 my $code = $factory->{$class} || sub { $class->new() };
  10         52  
353 12         27 my $obj = $code->( @arg );
354 12 50       3869 if ( $rslt = $self->_config_var_is_eci( $name, $obj ) ) {
355 12         56 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     27 if ( defined $val &&
366             Astro::Coord::ECI::Utils::looks_like_number( $val ) ) {
367 3         8 $self->{config}{$name} = $val;
368 3         5 delete $self->{config}{location};
369 3         8 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   7 my ( $self, $name, $val ) = @_;
377 3 50 33     46 if ( defined $val &&
      33        
      33        
378             Astro::Coord::ECI::Utils::looks_like_number( $val ) &&
379             $val >= -90 && $val <= 90 ) {
380 3         10 $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   10 my ( $self, $name, $val ) = @_;
391 6         9 my $loc;
392 6 100       14 if ( ! defined $val ) {
393 4         7 $loc = undef;
394 4         11 delete @{ $self->{config} }{
395 4         6 qw{ elevation latitude longitude name } };
396             } else {
397 2 50       5 $loc = $self->_config_var_is_eci_class( $name, $val )
398             or return 1;
399 2         9 my ( $lat, $lon, $ele ) = $loc->geodetic();
400 2         67 $self->{config}{elevation} = $ele * METERS_PER_KILOMETER;
401 2         7 $self->{config}{latitude} = Astro::Coord::ECI::Utils::rad2deg( $lat );
402 2         12 $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       61 $_->set( station => $loc ) for @{ $self->{config}{sky} || [] };
  6         29  
410 6         375 $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         17 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         14 $self->{config}{$name} = $val;
428 3         9 delete $self->{config}{location};
429 3         8 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       7 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         7 return;
445             }
446              
447             sub _config_almanac_var_sky {
448 15     15   29 my ( $self, $name, $values ) = @_;
449              
450 15         20 my @sky;
451 15 100       34 unless ( ref $values ) {
452 7 100 66     26 if ( defined( $values ) && $values ne '' ) {
453 6         31 $values = [ $values ];
454 6 100       9 @sky = @{ $self->{config}{sky} || [] };
  6         28  
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         27  
462 21 50       47 my $body = $self->_config_var_is_eci_class( $name, $val )
463             or return 1;
464 21         42 push @sky, $body;
465 21 100       43 if ( my $loc = $self->_get_config_location() ) {
466 6         17 $sky[-1]->set( station => $loc );
467             }
468             }
469              
470 15         366 @{ $self->{config}{sky} } = @sky;
  15         43  
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         44 return;
477             }
478              
479             sub _get_config_location {
480 40     40   72 my ( $self ) = @_;
481             my $cfg = $self->{config}
482 40 100       89 or return;
483             $cfg->{location}
484 38 100       95 and return $cfg->{location};
485             defined $cfg->{latitude}
486             and defined $cfg->{longitude}
487 20 100 66     68 or return;
488 3         12 my $loc = Astro::Coord::ECI->new();
489             defined $cfg->{name}
490 3 50       172 and $loc->set( name => $cfg->{name} );
491             defined $cfg->{_twilight}
492 3 100       80 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     51 ( $cfg->{elevation} || 0 ) / METERS_PER_KILOMETER,
497             );
498 3 100       601 $_->set( station => $loc ) for @{ $self->{config}{sky} || [] };
  3         24  
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         108 delete $self->{lang}{obj};
506              
507 3         10 return( $cfg->{location} = $loc );
508             }
509              
510             sub _get_twilight_qual {
511 12     12   59 my ( undef, $qual ) = @_; # Invocant not used
512 12 50       40 defined $qual
513             or return $qual;
514 12         27 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         73 return $twi_name->{ lc $qual };
520             }
521              
522             sub _init_almanac {
523 7     7   15 my ( $self, $from ) = @_;
524 7 100 66     46 if ( Scalar::Util::blessed( $from ) && $from->isa( __PACKAGE__ ) ) {
525 4         10 state $cfg_var = [ qw{ language location sky twilight } ];
526 4         8 my %cfg;
527 4         7 @cfg{ @{ $cfg_var } } = $from->get_config( @{ $cfg_var } );
  4         13  
  4         10  
528             # We clone because these objects have state.
529             # TODO this requires at least 0.118_01.
530 4         8 @{ $cfg{sky} } = map { $_->clone() } @{ $cfg{sky} };
  4         135  
  9         48  
  4         10  
531 4         16 $self->config( %cfg );
532             } else {
533 3         11 $self->_init_almanac_language( 1 );
534 3 50       10 if ( my $lang = $self->get_config( 'language' ) ) {
535 3         11 $self->_config_almanac_var_language( language => $lang );
536             }
537 3         6 %{ $self->{config} } = ();
  3         11  
538             }
539 7         15 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         11 $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   9058 my ( $self, $string ) = @_;
562             wantarray
563 14 50       33 or confess 'Bug - __parse_pre() must be called in list context';
564 14         21 delete $self->{err};
565 14         25 $self->{input} = $string;
566 14 50       18 @{ $self->{config}{sky} || [] }
  14 50       44  
567             or return $string;
568              
569             $self->{lang}{obj} ||= $self->{lang}{mod}->__new(
570             sky => $self->{config}{sky},
571 14   66     67 );
572 14         53 return $self->{lang}{obj}->__parse_pre( $string );
573             }
574              
575             sub __parse_post {
576 11     11   57545 my ( $self, $body, $event, undef ) = @_;
577 11 50 33     38 defined $body
578             and defined $event
579             or return;
580              
581 11 50       31 $self->_get_config_location()
582             or return $self->_set_err( "[parse] Location not configured" );
583              
584 11 50       54 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         38 $body->universal( $self->secs_since_1970_GMT() );
589              
590 11         9276 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   8 my ( $self, $body, undef, $detail ) = @_;
602              
603 3         10 my $almanac_horizon = $body->get( 'station' )->get(
604             'almanac_horizon' );
605              
606 3         96 my ( $time, $which );
607 3         53 while ( 1 ) {
608 5         26 ( $time, $which ) = $body->next_elevation( $almanac_horizon, 1 );
609 5 100       160436 $which == $detail
610             and last;
611             }
612              
613 3         23 $self->secs_since_1970_GMT( $time );
614              
615 3         1900 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         4 while ( 1 ) {
623 3         14 ( $time, $which ) = $body->next_meridian();
624 3 100       34850 $which == $detail
625             and last;
626             }
627              
628 2         8 $self->secs_since_1970_GMT( $time );
629              
630 2         970 return;
631             }
632              
633             sub __parse_post__quarter {
634 3     3   8 my ( $self, $body, undef, $detail ) = @_;
635              
636 3         17 my $time = $body->next_quarter( $detail );
637              
638 3         34403 $self->secs_since_1970_GMT( $time );
639              
640 3         1463 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     71 my $twilight = $station->get( 'almanac_horizon' ) + (
648             $self->_get_twilight_qual( $qual ) // $station->get( 'twilight' ) );
649              
650 3         28 my ( $time, $which );
651 3         13 while ( 1 ) {
652 4         10 ( $time, $which ) = $body->next_elevation( $twilight, 0 );
653 4 100       69415 $which == $detail
654             and last;
655             }
656              
657 3         10 $self->secs_since_1970_GMT( $time );
658              
659 3         1563 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   21 no strict qw{ refs };
  3         8  
  3         300  
721             *$method = _my_set_subname( $method => sub {
722 34     34 0 968 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         70 return $self->dmd()->$method( @arg );
724             },
725             );
726             }
727              
728             1;
729              
730             __END__