File Coverage

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


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