File Coverage

blib/lib/DateTime/Event/Sunrise.pm
Criterion Covered Total %
statement 256 266 96.2
branch 75 82 91.4
condition 8 12 66.6
subroutine 41 44 93.1
pod 9 24 37.5
total 389 428 90.8


line stmt bran cond sub pod time code
1             # -*- encoding: utf-8; indent-tabs-mode: nil -*-
2             #
3             # Perl DateTime extension for computing the sunrise/sunset on a given day
4             # Copyright (C) 1999-2004, 2013-2014 Ron Hill and Jean Forget
5             #
6             # See the license in the embedded documentation below.
7             #
8             package DateTime::Event::Sunrise;
9              
10 8     8   1089007 use strict;
  8         17  
  8         248  
11 8     8   41 use warnings;
  8         16  
  8         286  
12             require Exporter;
13 8     8   832 use POSIX qw(floor);
  8         7931  
  8         53  
14 8     8   12050 use Math::Trig;
  8         136614  
  8         1679  
15 8     8   70 use Carp;
  8         16  
  8         453  
16 8     8   1348 use DateTime;
  8         106313  
  8         173  
17 8     8   5639 use DateTime::Set;
  8         286740  
  8         245  
18 8     8   68 use Params::Validate qw(:all);
  8         17  
  8         1762  
19 8     8   49 use Set::Infinite qw(inf $inf);
  8         17  
  8         728  
20 8     8   114 use vars qw( $VERSION $RADEG $DEGRAD @ISA );
  8         25  
  8         29277  
21             @ISA = qw( Exporter );
22             $VERSION = '0.0505';
23             $RADEG = ( 180 / pi );
24             $DEGRAD = ( pi / 180 );
25             my $INV360 = ( 1.0 / 360.0 );
26              
27             # Julian day number for the 0th January 2000 (that is, 31st December 1999)
28             my $jd_2000_Jan_0 = DateTime->new(year => 1999, month => 12, day => 31, time_zone => 'UTC')->jd;
29              
30              
31             sub new {
32 718     718 1 1954453 my $class = shift;
33              
34 718 100       2202 if (@_ % 2 != 0) {
35 1         20 croak "Odd number of parameters";
36             }
37 717         3011 my %args = @_;
38 717 50 66     1841 if (exists $args{iteration} && exists $args{precise}) {
39 1         9 croak "Parameter 'iteration' is deprecated, use only 'precise'";
40             }
41              
42 716         24023 %args = validate(
43             @_, {
44             longitude => { type => SCALAR, optional => 1, default => 0 },
45             latitude => { type => SCALAR, optional => 1, default => 0 },
46             altitude => {
47             type => SCALAR,
48             default => '-0.833',
49             regex => qr/^(-?\d+(?:\.\d+)?)$/
50             },
51             iteration => { type => SCALAR, default => '0' },
52             precise => { type => SCALAR, default => '0' },
53             upper_limb => { type => SCALAR, default => '0' },
54             silent => { type => SCALAR, default => '0' },
55             }
56             );
57              
58             # Making old and new parameters synonymous
59 716 50       20911 unless (exists $args{precise}) {
60 0         0 $args{precise} = $args{iteration};
61             }
62             # TODO : get rid of the old parameters after this point
63 716         1359 $args{iteration} = $args{precise};
64              
65 716         2735 return bless \%args, $class;
66             }
67              
68             #
69             #
70             # FUNCTIONAL SEQUENCE for sunrise
71             #
72             # _GIVEN
73             # A sunrise object that was created by the new method
74             #
75             # _THEN
76             #
77             # setup subs for following/previous sunrise times
78             #
79             #
80             # _RETURN
81             #
82             # A new DateTime::Set recurrence object
83             #
84             sub sunrise {
85              
86 120     120 1 393057 my $class = shift;
87 120         426 my $self = $class->new(@_);
88             return DateTime::Set->from_recurrence(
89             next => sub {
90 244 100   244   60367 return $_[0] if $_[0]->is_infinite;
91 124         741 $self->_following_sunrise( $_[0] );
92             },
93             previous => sub {
94 128 100   128   8136 return $_[0] if $_[0]->is_infinite;
95 8         62 $self->_previous_sunrise( $_[0] );
96 120         886 } );
97             }
98              
99             #
100             #
101             # FUNCTIONAL SEQUENCE for sunset
102             #
103             # _GIVEN
104             #
105             # A sunrise object that was created by the new method
106             # _THEN
107             #
108             # Setup subs for following/previous sunset times
109             #
110             #
111             # _RETURN
112             #
113             # A new DateTime::Set recurrence object
114             #
115             sub sunset {
116              
117 126     126 1 18933 my $class = shift;
118 126         371 my $self = $class->new(@_);
119             return DateTime::Set->from_recurrence(
120             next => sub {
121 256 100   256   33551 return $_[0] if $_[0]->is_infinite;
122 130         733 $self->_following_sunset( $_[0] );
123             },
124             previous => sub {
125 134 100   134   7101 return $_[0] if $_[0]->is_infinite;
126 8         51 $self->_previous_sunset( $_[0] );
127 126         790 } );
128             }
129              
130             #
131             #
132             # FUNCTIONAL SEQUENCE for sunset_datetime
133             #
134             # _GIVEN
135             #
136             # A sunrise object
137             # A DateTime object
138             #
139             # _THEN
140             #
141             # Validate the DateTime object is valid
142             # Compute sunrise and sunset
143             #
144             #
145             # _RETURN
146             #
147             # DateTime object that contains the sunset time
148             #
149             sub sunset_datetime {
150              
151 469     469 1 1689 my $self = shift;
152 469         684 my $dt = shift;
153 469         767 my $class = ref($dt);
154              
155 469 100       1635 if ( ! $dt->isa('DateTime') ) {
156 1         9 croak("Dates need to be DateTime objects");
157             }
158 468         909 my ( undef, $tmp_set ) = _sunrise( $self, $dt );
159 468         2281 return $tmp_set;
160             }
161              
162             #
163             #
164             # FUNCTIONAL SEQUENCE for sunrise_datetime
165             #
166             # _GIVEN
167             #
168             # A sunrise object
169             # A DateTime object
170             #
171             # _THEN
172             #
173             # Validate the DateTime object is valid
174             # Compute sunrise and sunset
175             #
176             #
177             # _RETURN
178             #
179             # DateTime object that contains the sunrise times
180             #
181             sub sunrise_datetime {
182              
183 469     469 1 90734 my $self = shift;
184 469         788 my $dt = shift;
185 469         893 my $class = ref($dt);
186              
187 469 100       1795 if ( ! $dt->isa('DateTime') ) {
188 1         9 croak("Dates need to be DateTime objects");
189             }
190 468         1095 my ( $tmp_rise, undef ) = _sunrise( $self, $dt );
191 468         2301 return $tmp_rise;
192             }
193              
194             #
195             #
196             # FUNCTIONAL SEQUENCE for sunrise_sunset_span
197             #
198             # _GIVEN
199             #
200             # A sunrise object
201             # A DateTime object
202             #
203             # _THEN
204             #
205             # Validate the DateTime object is valid
206             # Compute sunrise and sunset
207             #
208             #
209             # _RETURN
210             #
211             # DateTime Span object that contains the sunrise/sunset times
212             #
213             sub sunrise_sunset_span {
214              
215 2     2 1 762 my $self = shift;
216 2         5 my $dt = shift;
217 2         5 my $class = ref($dt);
218              
219 2 100       16 if ( ! $dt->isa('DateTime') ) {
220 1         10 croak("Dates need to be DateTime objects");
221             }
222 1         4 my ( $tmp_rise, $tmp_set ) = _sunrise( $self, $dt );
223              
224 1         12 return DateTime::Span->from_datetimes(
225             start => $tmp_rise,
226             end => $tmp_set
227             );
228             }
229              
230             #
231             # FUNCTIONAL SEQUENCE for is_polar_night
232             #
233             # _GIVEN
234             #
235             # A sunrise object
236             # A DateTime object
237             #
238             # _THEN
239             #
240             # Validate the DateTime object is valid
241             # Compute sunrise and sunset
242             #
243             # _RETURN
244             #
245             # A boolean flag telling whether the sun will stay under the horizon or not
246             #
247             sub is_polar_night {
248              
249 181     181 1 244815 my $self = shift;
250 181         248 my $dt = shift;
251 181         296 my $class = ref($dt);
252              
253 181 100       775 if ( ! $dt->isa('DateTime') ) {
254 1         10 croak("Dates need to be DateTime objects");
255             }
256 180         452 my ( undef, undef, $rise_season, $set_season ) = _sunrise( $self, $dt, 1 );
257 180   66     2057 return ($rise_season < 0 || $set_season < 0);
258             }
259              
260             #
261             # FUNCTIONAL SEQUENCE for is_polar_day
262             #
263             # _GIVEN
264             #
265             # A sunrise object
266             # A DateTime object
267             #
268             # _THEN
269             #
270             # Validate the DateTime object is valid
271             # Compute sunrise and sunset
272             #
273             # _RETURN
274             #
275             # A boolean flag telling whether the sun will stay above the horizon or not
276             #
277             sub is_polar_day {
278              
279 181     181 1 1046 my $self = shift;
280 181         266 my $dt = shift;
281 181         311 my $class = ref($dt);
282              
283 181 100       727 if ( ! $dt->isa('DateTime') ) {
284 1         9 croak("Dates need to be DateTime objects");
285             }
286 180         424 my ( undef, undef, $rise_season, $set_season ) = _sunrise( $self, $dt, 1 );
287 180   66     1896 return ($rise_season > 0 || $set_season > 0);
288             }
289              
290             #
291             # FUNCTIONAL SEQUENCE for is_day_and_night
292             #
293             # _GIVEN
294             #
295             # A sunrise object
296             # A DateTime object
297             #
298             # _THEN
299             #
300             # Validate the DateTime object is valid
301             # Compute sunrise and sunset
302             #
303             # _RETURN
304             #
305             # A boolean flag telling whether the sun will rise and set or not
306             #
307             sub is_day_and_night {
308              
309 181     181 1 1103 my $self = shift;
310 181         233 my $dt = shift;
311 181         309 my $class = ref($dt);
312              
313 181 100       751 if ( ! $dt->isa('DateTime') ) {
314 1         9 croak("Dates need to be DateTime objects");
315             }
316 180         414 my ( undef, undef, $rise_season, $set_season ) = _sunrise( $self, $dt, 1 );
317 180   66     1908 return ($rise_season == 0 && $set_season == 0);
318             }
319              
320             #
321             #
322             # FUNCTIONAL SEQUENCE for _following_sunrise
323             #
324             # _GIVEN
325             #
326             # A sunrise object
327             # A DateTime object
328             #
329             # _THEN
330             #
331             # Validate the DateTime object is valid
332             # Compute sunrise and return if it is greater
333             # than the original if not add one day and recompute
334             #
335             #
336             # _RETURN
337             #
338             # A new DateTime object that contains the sunrise time
339             #
340             sub _following_sunrise {
341              
342 125     125   932 my $self = shift;
343 125         229 my $dt = shift;
344 125 100       601 croak( "Dates need to be DateTime objects (" . ref($dt) . ")" )
345             unless ( $dt->isa('DateTime') );
346 124         306 my ( $tmp_rise, undef ) = _sunrise( $self, $dt );
347 124 100       781 return $tmp_rise if $tmp_rise > $dt;
348 4         284 my $d = DateTime::Duration->new(
349             days => 1,
350             );
351 4         265 my $new_dt = $dt + $d;
352 4         2817 ( $tmp_rise, undef ) = _sunrise( $self, $new_dt );
353 4 100       31 return $tmp_rise if $tmp_rise > $dt;
354 1         67 $new_dt = $new_dt + $d;
355 1         407 ( $tmp_rise, undef ) = _sunrise( $self, $new_dt );
356 1         14 return $tmp_rise;
357             }
358              
359             #
360             #
361             # FUNCTIONAL SEQUENCE for _previous_sunrise
362             #
363             # _GIVEN
364             # A sunrise object
365             # A DateTime object
366             #
367             # _THEN
368             #
369             # Validate the DateTime Object
370             # Compute sunrise and return if it is less than
371             # the original object if not subtract one day and recompute
372             #
373             # _RETURN
374             #
375             # A new DateTime Object that contains the sunrise time
376             #
377             sub _previous_sunrise {
378              
379 9     9   762 my $self = shift;
380 9         16 my $dt = shift;
381 9 100       62 croak( "Dates need to be DateTime objects (" . ref($dt) . ")" )
382             unless ( $dt->isa('DateTime') );
383 8         22 my ( $tmp_rise, undef ) = _sunrise( $self, $dt );
384 8 100       49 return $tmp_rise if $tmp_rise < $dt;
385 7         588 my $d = DateTime::Duration->new(
386             days => 1,
387             );
388 7         464 my $new_dt = $dt - $d;
389 7         6177 ( $tmp_rise, undef ) = _sunrise( $self, $new_dt );
390 7 50       61 return $tmp_rise if $tmp_rise < $dt;
391 0         0 $new_dt = $new_dt - $d;
392 0         0 ( $tmp_rise, undef ) = _sunrise( $self, $new_dt );
393 0         0 return $tmp_rise;
394             }
395              
396             #
397             #
398             # FUNCTIONAL SEQUENCE for _following_sunset
399             #
400             # _GIVEN
401             # A sunrise object
402             # A DateTime object
403             #
404             # _THEN
405             #
406             # Validate the DateTime object is valid
407             # Compute sunset and return if it is greater
408             # than the original if not add one day and recompute
409             #
410             # _RETURN
411             #
412             # A DateTime object with sunset time
413             #
414             sub _following_sunset {
415              
416 131     131   914 my $self = shift;
417 131         228 my $dt = shift;
418 131 100       362 croak( "Dates need to be DateTime objects (" . ref($dt) . ")" )
419             unless ( ref($dt) eq 'DateTime' );
420 130         283 my ( undef, $tmp_set ) = _sunrise( $self, $dt );
421 130 100       778 return $tmp_set if $tmp_set > $dt;
422 4         278 my $d = DateTime::Duration->new(
423             days => 1,
424             );
425 4         230 my $new_dt = $dt + $d;
426 4         2841 ( undef, $tmp_set ) = _sunrise( $self, $new_dt );
427 4 50       33 return $tmp_set if $tmp_set > $dt;
428 0         0 $new_dt = $new_dt + $d;
429 0         0 ( undef, $tmp_set ) = _sunrise( $self, $new_dt );
430 0         0 return $tmp_set;
431             }
432              
433             #
434             #
435             # FUNCTIONAL SEQUENCE for _previous_sunset
436             #
437             # _GIVEN
438             # A sunrise object
439             # A DateTime object
440             #
441             # _THEN
442             #
443             # Validate the DateTime Object
444             # Compute sunset and return if it is less than
445             # the original object if not subtract one day and recompute
446             #
447             # _RETURN
448             #
449             # A DateTime object with sunset time
450             #
451             sub _previous_sunset {
452              
453 9     9   785 my $self = shift;
454 9         17 my $dt = shift;
455 9 100       51 croak( "Dates need to be DateTime objects (" . ref($dt) . ")" )
456             unless ( $dt->isa('DateTime') );
457 8         20 my ( undef, $tmp_set ) = _sunrise( $self, $dt );
458 8 100       52 return $tmp_set if $tmp_set < $dt;
459 7         500 my $d = DateTime::Duration->new(
460             days => 1,
461             );
462 7         415 my $new_dt = $dt - $d;
463 7         5529 ( undef, $tmp_set ) = _sunrise( $self, $new_dt );
464 7 100       81 return $tmp_set if $tmp_set < $dt;
465 2         139 $new_dt = $new_dt - $d;
466 2         1039 ( undef, $tmp_set ) = _sunrise( $self, $new_dt );
467 2         64 return $tmp_set;
468             }
469              
470             #
471             #
472             # FUNCTIONAL SEQUENCE for _sunrise
473             #
474             # _GIVEN
475             # A sunrise object and a DateTime object
476             #
477             # _THEN
478             #
479             # Check if precise is set to one if so
480             # initially compute sunrise/sunset (using division
481             # by 15.04107 instead of 15.0) then recompute rise/set time
482             # using exact moment last computed. IF precise is set
483             # to zero devide by 15.0 (only once)
484             #
485             # Bug in this sub, I was blindly setting the hour and min without
486             # checking if it was neg. a neg. value for hours/min is not correct
487             # I changed the routine to use a duration then add the duration.
488             #
489             # _RETURN
490             #
491             # two DateTime objects with the date and time for sunrise and sunset
492             # two season flags for sunrise and sunset respectively
493             #
494             sub _sunrise {
495              
496 1772     1772   2781 my ($self, $dt, $silent) = @_;
497 1772         4908 my $cloned_dt = $dt->clone;
498 1772         19866 my $altit = $self->{altitude};
499 1772 50       4899 my $precise = defined( $self->{precise} ) ? $self->{precise} : 0;
500 1772 100       3983 unless (defined $silent) {
501 1232 50       3189 $silent = defined( $self->{silent} ) ? $self->{silent} : 0;
502             }
503 1772         4972 $cloned_dt->set_time_zone('floating');
504              
505 1772 100       52389 if ($precise) {
506              
507             # This is the initial start
508              
509 24         52 my $d = days_since_2000_Jan_0($cloned_dt) + 0.5 - $self->{longitude} / 360.0;
510             my ($tmp_rise_1, $tmp_set_1, $rise_season) = _sunrise_sunset( $d, $self->{longitude}, $self->{latitude}, $altit,
511 24         329 15.04107, $self->{upper_limb}, $silent);
512 24         40 my $set_season = $rise_season;
513              
514             # Now we have the initial rise/set times next recompute d using the exact moment
515             # recompute sunrise
516              
517 24         28 my $tmp_rise_2 = 9;
518 24         30 my $tmp_rise_3 = 0;
519              
520 24         27 my $counter = 0;
521 24         56 until ( equal( $tmp_rise_2, $tmp_rise_3, 8 ) ) {
522              
523 72         135 my $d_sunrise_1 = $d + $tmp_rise_1 / 24.0;
524             ($tmp_rise_2, undef, undef) = _sunrise_sunset($d_sunrise_1, $self->{longitude}, $self->{latitude},
525 72         177 $altit, 15.04107, $self->{upper_limb}, $silent);
526 72         102 $tmp_rise_1 = $tmp_rise_3;
527 72         125 my $d_sunrise_2 = $d + $tmp_rise_2 / 24.0;
528             ($tmp_rise_3, undef, $rise_season) = _sunrise_sunset($d_sunrise_2, $self->{longitude}, $self->{latitude},
529 72         171 $altit, 15.04107, $self->{upper_limb}, $silent);
530 72 50       229 last if ++$counter > 10;
531             }
532              
533 24         39 my $tmp_set_2 = 9;
534 24         31 my $tmp_set_3 = 0;
535              
536 24         29 $counter = 0;
537 24         43 until ( equal( $tmp_set_2, $tmp_set_3, 8 ) ) {
538              
539 80         142 my $d_sunset_1 = $d + $tmp_set_1 / 24.0;
540             (undef, $tmp_set_2, undef) = _sunrise_sunset( $d_sunset_1, $self->{longitude}, $self->{latitude},
541 80         183 $altit, 15.04107, $self->{upper_limb}, $silent);
542 80         115 $tmp_set_1 = $tmp_set_3;
543 80         117 my $d_sunset_2 = $d + $tmp_set_2 / 24.0;
544             (undef, $tmp_set_3, $set_season) = _sunrise_sunset( $d_sunset_2, $self->{longitude}, $self->{latitude},
545 80         189 $altit, 15.04107, $self->{upper_limb}, $silent);
546 80 100       260 last if ++$counter > 10;
547              
548             }
549              
550 24         58 my ( $second_rise, $second_set ) = convert_hour( $tmp_rise_3, $tmp_set_3 );
551              
552             # This is to fix the datetime object to use a duration
553             # instead of blindly setting the hour/min
554 24         111 my $rise_dur = DateTime::Duration->new( seconds => $second_rise );
555 24         1689 my $set_dur = DateTime::Duration->new( seconds => $second_set );
556              
557 24         1342 my $tmp_dt1 = DateTime->new(
558             year => $dt->year,
559             month => $dt->month,
560             day => $dt->day,
561             hour => 0,
562             minute => 0,
563             time_zone => 'UTC'
564             );
565              
566 24         5358 my $rise_time = $tmp_dt1 + $rise_dur;
567 24         10356 my $set_time = $tmp_dt1 + $set_dur;
568 24         9862 my $tz = $dt->time_zone;
569 24 100       138 $rise_time->set_time_zone($tz) unless $tz->is_floating;
570 24 100       269 $set_time->set_time_zone($tz) unless $tz->is_floating;
571 24         445 return ( $rise_time, $set_time, $rise_season, $set_season );
572             }
573             else {
574 1748         3498 my $d = days_since_2000_Jan_0($cloned_dt) + 0.5 - $self->{longitude} / 360.0;
575 1748         21990 my ( $h1, $h2, $season ) = _sunrise_sunset( $d, $self->{longitude}, $self->{latitude}, $altit, 15.0, $self->{upper_limb}, $silent);
576 1748         3673 my ( $seconds_rise, $seconds_set ) = convert_hour( $h1, $h2 );
577 1748         6357 my $rise_dur = DateTime::Duration->new( seconds => $seconds_rise );
578 1748         112967 my $set_dur = DateTime::Duration->new( seconds => $seconds_set );
579 1748         102926 my $tmp_dt1 = DateTime->new(
580             year => $dt->year,
581             month => $dt->month,
582             day => $dt->day,
583             hour => 0,
584             minute => 0,
585             time_zone => 'UTC'
586             );
587              
588 1748         399807 my $rise_time = $tmp_dt1 + $rise_dur;
589 1748         735214 my $set_time = $tmp_dt1 + $set_dur;
590 1748         725177 my $tz = $dt->time_zone;
591 1748 100       10773 $rise_time->set_time_zone($tz) unless $tz->is_floating;
592 1748 100       38392 $set_time->set_time_zone($tz) unless $tz->is_floating;
593 1748         48136 return ( $rise_time, $set_time, $season, $season );
594             }
595              
596             }
597              
598             #
599             #
600             # FUNCTIONAL SEQUENCE for _sunrise_sunset
601             #
602             # _GIVEN
603             #
604             # days since Jan 0 2000, longitude, latitude, reference sun height $h and the "upper limb" and "silent" flags
605             # _THEN
606             #
607             # Compute the sunrise/sunset times for that day
608             #
609             # _RETURN
610             #
611             # sunrise and sunset times as hours (GMT Time)
612             # season flag: -1 for polar night, +1 for midnight sun, 0 for day and night
613             #
614             sub _sunrise_sunset {
615              
616 2076     2076   4749 my ( $d, $lon, $lat, $altit, $h, $upper_limb, $silent ) = @_;
617              
618             # Compute local sidereal time of this moment
619 2076         4064 my $sidtime = revolution(GMST0($d) + 180.0 + $lon);
620              
621             # Compute Sun's RA + Decl + distance at this moment
622 2076         4370 my ($sRA, $sdec, $sr) = sun_RA_dec($d);
623              
624             # Compute time when Sun is at south - in hours UT
625 2076         4546 my $tsouth = 12.0 - rev180( $sidtime - $sRA ) / $h;
626              
627             # Compute the Sun's apparent radius, degrees
628 2076         3208 my $sradius = 0.2666 / $sr;
629              
630             # Do correction to upper limb, if necessary
631 2076 100       4195 if ($upper_limb) {
632 775         1832 $altit -= $sradius;
633             }
634              
635             # Compute the diurnal arc that the Sun traverses to reach
636             # the specified height altit:
637              
638 2076         3753 my $cost = (sind($altit) - sind($lat) * sind($sdec))
639             / (cosd($lat) * cosd($sdec));
640              
641 2076         2929 my $t;
642 2076         2499 my $season = 0;
643 2076 100       5310 if ( $cost >= 1.0 ) {
    100          
644 238 100       519 unless ($silent) {
645 8         1166 carp "Sun never rises!!\n";
646             }
647 238         1066 $t = 0.0; # Sun always below altit
648 238         326 $season = -1;
649             }
650             elsif ( $cost <= -1.0 ) {
651 536 100       1049 unless ($silent) {
652 16         2148 carp "Sun never sets!!\n";
653             }
654 536         2457 $t = 12.0; # Sun always above altit
655 536         731 $season = +1;
656             }
657             else {
658 1302         2439 $t = acosd($cost) / 15.0; # The diurnal arc, hours
659             }
660              
661             # Store rise and set times - in hours UT
662              
663 2076         10144 my $hour_rise_ut = $tsouth - $t;
664 2076         2609 my $hour_set_ut = $tsouth + $t;
665 2076         5035 return ( $hour_rise_ut, $hour_set_ut, $season );
666              
667             }
668              
669             #
670             #
671             # FUNCTIONAL SEQUENCE for GMST0
672             #
673             # _GIVEN
674             # Day number
675             #
676             # _THEN
677             #
678             # computes GMST0, the Greenwich Mean Sidereal Time
679             # at 0h UT (i.e. the sidereal time at the Greenwhich meridian at
680             # 0h UT). GMST is then the sidereal time at Greenwich at any
681             # time of the day.
682             #
683             #
684             # _RETURN
685             #
686             # Sidtime
687             #
688             sub GMST0 {
689 2076     2076 0 2702 my ($d) = @_;
690 2076         4795 my $sidtim0 = revolution( ( 180.0 + 356.0470 + 282.9404 ) + ( 0.9856002585 + 4.70935E-5 ) * $d );
691 2076         5535 return $sidtim0;
692             }
693              
694             #
695             #
696             # FUNCTIONAL SEQUENCE for sunpos
697             #
698             # _GIVEN
699             # day number
700             #
701             # _THEN
702             #
703             # Computes the Sun's ecliptic longitude and distance
704             # at an instant given in d, number of days since
705             # 2000 Jan 0.0.
706             #
707             #
708             # _RETURN
709             #
710             # ecliptic longitude and distance
711             # ie. $True_solar_longitude, $Solar_distance
712             #
713             sub sunpos {
714              
715 2076     2076 0 2740 my ($d) = @_;
716              
717             # Mean anomaly of the Sun
718             # Mean longitude of perihelion
719             # Note: Sun's mean longitude = M + w
720             # Eccentricity of Earth's orbit
721             # Eccentric anomaly
722             # x, y coordinates in orbit
723             # True anomaly
724              
725             # Compute mean elements
726 2076         4394 my $Mean_anomaly_of_sun = revolution( 356.0470 + 0.9856002585 * $d );
727 2076         3339 my $Mean_longitude_of_perihelion = 282.9404 + 4.70935E-5 * $d;
728 2076         2940 my $Eccentricity_of_Earth_orbit = 0.016709 - 1.151E-9 * $d;
729              
730             # Compute true longitude and radius vector
731 2076         4096 my $Eccentric_anomaly = $Mean_anomaly_of_sun
732             + $Eccentricity_of_Earth_orbit * $RADEG
733             * sind($Mean_anomaly_of_sun)
734             * ( 1.0 + $Eccentricity_of_Earth_orbit * cosd($Mean_anomaly_of_sun) );
735              
736 2076         3845 my $x = cosd($Eccentric_anomaly) - $Eccentricity_of_Earth_orbit;
737              
738 2076         4503 my $y = sqrt( 1.0 - $Eccentricity_of_Earth_orbit * $Eccentricity_of_Earth_orbit )
739             * sind($Eccentric_anomaly);
740              
741 2076         3371 my $Solar_distance = sqrt( $x * $x + $y * $y ); # Solar distance
742 2076         3874 my $True_anomaly = atan2d( $y, $x ); # True anomaly
743              
744 2076         2825 my $True_solar_longitude =
745             $True_anomaly + $Mean_longitude_of_perihelion; # True solar longitude
746              
747 2076 100       4639 if ( $True_solar_longitude >= 360.0 ) {
748 985         1370 $True_solar_longitude -= 360.0; # Make it 0..360 degrees
749             }
750              
751 2076         4340 return ( $Solar_distance, $True_solar_longitude );
752             }
753              
754             #
755             #
756             # FUNCTIONAL SEQUENCE for sun_RA_dec
757             #
758             # _GIVEN
759             # day number, $r and $lon (from sunpos)
760             #
761             # _THEN
762             #
763             # compute RA and dec
764             #
765             #
766             # _RETURN
767             #
768             # Sun's Right Ascension (RA), Declination (dec) and distance (r)
769             #
770             #
771             sub sun_RA_dec {
772              
773 2076     2076 0 2799 my ($d) = @_;
774              
775             # Compute Sun's ecliptical coordinates
776 2076         3837 my ( $r, $lon ) = sunpos($d);
777              
778             # Compute ecliptic rectangular coordinates (z=0)
779 2076         4020 my $x = $r * cosd($lon);
780 2076         3764 my $y = $r * sind($lon);
781              
782             # Compute obliquity of ecliptic (inclination of Earth's axis)
783 2076         3087 my $obl_ecl = 23.4393 - 3.563E-7 * $d;
784              
785             # Convert to equatorial rectangular coordinates - x is unchanged
786 2076         3568 my $z = $y * sind($obl_ecl);
787 2076         3809 $y = $y * cosd($obl_ecl);
788              
789             # Convert to spherical coordinates
790 2076         3810 my $RA = atan2d( $y, $x );
791 2076         4833 my $dec = atan2d( $z, sqrt( $x * $x + $y * $y ) );
792              
793 2076         4326 return ( $RA, $dec, $r );
794              
795             } # sun_RA_dec
796              
797             #
798             #
799             # FUNCTIONAL SEQUENCE for days_since_2000_Jan_0
800             #
801             # _GIVEN
802             # A Datetime object
803             #
804             # _THEN
805             #
806             # process the DateTime object for number of days
807             # since Jan,1 2000 (counted in days)
808             # Day 0.0 is at Jan 1 2000 0.0 UT
809             #
810             # _RETURN
811             #
812             # day number
813             #
814             sub days_since_2000_Jan_0 {
815 1772     1772 0 2582 my ($dt) = @_;
816 1772         4842 return int($dt->jd - $jd_2000_Jan_0);
817             }
818              
819             sub sind {
820 14532     14532 0 38413 sin( ( $_[0] ) * $DEGRAD );
821             }
822              
823             sub cosd {
824 12456     12456 0 23233 cos( ( $_[0] ) * $DEGRAD );
825             }
826              
827             sub tand {
828 0     0 0 0 tan( ( $_[0] ) * $DEGRAD );
829             }
830              
831             sub atand {
832 0     0 0 0 ( $RADEG * atan( $_[0] ) );
833             }
834              
835             sub asind {
836 0     0 0 0 ( $RADEG * asin( $_[0] ) );
837             }
838              
839             sub acosd {
840 1302     1302 0 3620 ( $RADEG * acos( $_[0] ) );
841             }
842              
843             sub atan2d {
844 6228     6228 0 12343 ( $RADEG * atan2( $_[0], $_[1] ) );
845             }
846              
847             #
848             #
849             # FUNCTIONAL SEQUENCE for revolution
850             #
851             # _GIVEN
852             # any angle in degrees
853             #
854             # _THEN
855             #
856             # reduces any angle to within the first revolution
857             # by subtracting or adding even multiples of 360.0
858             #
859             #
860             # _RETURN
861             #
862             # the value of the input is >= 0.0 and < 360.0
863             #
864             sub revolution {
865              
866 6228     6228 0 7722 my $x = $_[0];
867 6228         17077 return ( $x - 360.0 * floor( $x * $INV360 ) );
868             }
869              
870             #
871             #
872             # FUNCTIONAL SEQUENCE for rev180
873             #
874             # _GIVEN
875             #
876             # any angle in degrees
877             #
878             # _THEN
879             #
880             # Reduce input to within +180..+180 degrees
881             #
882             #
883             # _RETURN
884             #
885             # angle that was reduced
886             #
887             sub rev180 {
888              
889 2076     2076 0 2586 my ($x) = @_;
890              
891 2076         6797 return ( $x - 360.0 * floor( $x * $INV360 + 0.5 ) );
892             }
893              
894             #
895             #
896             # FUNCTIONAL SEQUENCE for equal
897             #
898             # _GIVEN
899             #
900             # Two floating point numbers and Accuracy
901             #
902             # _THEN
903             #
904             # Use sprintf to format the numbers to Accuracy
905             # number of decimal places
906             #
907             # _RETURN
908             #
909             # True if the numbers are equal
910             #
911             sub equal {
912              
913 199     199 0 278 my ( $A, $B, $dp ) = @_;
914              
915 199         1387 return sprintf( "%.${dp}g", $A ) eq sprintf( "%.${dp}g", $B );
916             }
917              
918             #
919             #
920             # FUNCTIONAL SEQUENCE for convert_hour
921             #
922             # _GIVEN
923             # Hour_rise, Hour_set
924             # hours are in UT
925             #
926             # _THEN
927             #
928             # split out the hours and minutes
929             # Oct 20 2003
930             # will convert hours to seconds and return this
931             # let DateTime handle the conversion
932             #
933             # _RETURN
934             #
935             # number of seconds
936             sub convert_hour {
937              
938 1772     1772 0 2435 my ( $hour_rise_ut, $hour_set_ut ) = @_;
939 1772         4361 my $seconds_rise = floor( $hour_rise_ut * 60 * 60 );
940 1772         3852 my $seconds_set = floor( $hour_set_ut * 60 * 60 );
941              
942 1772         3214 return ( $seconds_rise, $seconds_set );
943             }
944              
945             1962; # Hint: sung by RZ, better known as BD
946              
947             =encoding utf8
948              
949             =head1 NAME
950              
951             DateTime::Event::Sunrise - Perl DateTime extension for computing the sunrise/sunset on a given day
952              
953             =head1 SYNOPSIS
954              
955             use DateTime;
956             use DateTime::Event::Sunrise;
957              
958             # generating DateTime objects from a DateTime::Event::Sunrise object
959             my $sun_Kyiv = DateTime::Event::Sunrise->new(longitude => +30.85, # 30°51'E
960             latitude => +50.45); # 50°27'N
961             for (12, 13, 14) {
962             my $dt_yapc_eu = DateTime->new(year => 2013,
963             month => 8,
964             day => $_,
965             time_zone => 'Europe/Kiev');
966             say "In Kyiv (50°27'N, 30°51'E) on ", $dt_yapc_eu->ymd, " sunrise occurs at ", $sun_Kyiv->sunrise_datetime($dt_yapc_eu)->hms,
967             " and sunset occurs at ", $sun_Kyiv->sunset_datetime ($dt_yapc_eu)->hms;
968             }
969              
970             # generating DateTime objects from DateTime::Set objects
971             my $sunrise_Austin = DateTime::Event::Sunrise->sunrise(longitude => -94.73, # 97°44'W
972             latitude => +30.3); # 30°18'N
973             my $sunset_Austin = DateTime::Event::Sunrise->sunset (longitude => -94.73,
974             latitude => +30.3);
975             my $dt_yapc_na_rise = DateTime->new(year => 2013,
976             month => 6,
977             day => 3,
978             time_zone => 'America/Chicago');
979             my $dt_yapc_na_set = $dt_yapc_na_rise->clone;
980             say "In Austin (30°18'N, 97°44'W), sunrises and sunsets are";
981             for (1..3) {
982             $dt_yapc_na_rise = $sunrise_Austin->next($dt_yapc_na_rise);
983             $dt_yapc_na_set = $sunset_Austin ->next($dt_yapc_na_set);
984             say $dt_yapc_na_rise, ' ', $dt_yapc_na_set;
985             }
986              
987             # If you deal with a polar location
988             my $sun_in_Halley = DateTime::Event::Sunrise->new(
989             longitude => -26.65, # 26°39'W
990             latitude => -75.58, # 75°35'S
991             precise => 1,
992             );
993             my $Alex_arrival = DateTime->new(year => 2006, # approximate date, not necessarily the exact one
994             month => 1,
995             day => 15,
996             time_zone => 'Antarctica/Rothera');
997             say $Alex_arrival->strftime("Alex Gough (a Perl programmer) arrived at Halley Base on %Y-%m-%d.");
998             if ($sun_in_Halley->is_polar_day($Alex_arrival)) {
999             say "It would be days, maybe weeks, before the sun would set.";
1000             }
1001             elsif ($sun_in_Halley->is_polar_night($Alex_arrival)) {
1002             say "It would be days, maybe weeks, before the sun would rise.";
1003             }
1004             else {
1005             my $sunset = $sun_in_Halley->sunset_datetime($Alex_arrival);
1006             say $sunset->strftime("And he saw his first antarctic sunset at %H:%M:%S.");
1007             }
1008              
1009             =head1 DESCRIPTION
1010              
1011             This module will computes the time of sunrise and sunset for a given date
1012             and a given location. The computation uses Paul Schlyter's algorithm.
1013              
1014             Actually, the module creates a DateTime::Event::Sunrise object or a
1015             DateTime::Set object, which are used to generate the sunrise or the sunset
1016             times for a given location and for any date.
1017              
1018             =head1 METHODS
1019              
1020             =head2 new
1021              
1022             This is the DateTime::Event::Sunrise constructor. It takes keyword
1023             parameters, which are:
1024              
1025             =over 4
1026              
1027             =item longitude
1028              
1029             This is the longitude of the location where the sunrises and sunsets are observed.
1030             It is given as decimal degrees: no minutes, no seconds, but tenths and hundredths of degrees.
1031             Another break with the normal usage is that Eastern longitude are positive, Western longitudes
1032             are negative.
1033              
1034             Default value is 0, that is Greenwich or any location on the eponymous meridian.
1035              
1036             =item latitude
1037              
1038             This is the latitude of the location where the sunrises and sunsets are observed.
1039             As for the longitude, it is given as decimal degrees. Northern latitudes are positive
1040             numbers, Southern latitudes are negative numbers.
1041              
1042             Default value is 0, that is any location on the equator.
1043              
1044             =item altitude
1045              
1046             This is the height of the Sun at sunrise or sunset. In astronomical context, the altitude or
1047             height is the angle between the Sun and the local horizon. It is expressed as degrees, usually
1048             with a negative number, since the Sun is I the horizon.
1049              
1050             Default value is -0.833, that is when the sun's upper limb touches the horizon, while
1051             taking in account the light refraction.
1052              
1053             Positive altitude are allowed, in case the location is near a mountain range
1054             behind which the sun rises or sets.
1055              
1056             =item precise
1057              
1058             Boolean to control which algorithm is used. A false value gives a simple algorithm, but
1059             which can lead to inaccurate sunrise times and sunset times. A true value gives
1060             a more elaborate algorithm, with a loop to refine the sunrise and sunset times
1061             and obtain a better precision.
1062              
1063             Default value is 0, to choose the simple algorithm.
1064              
1065             This parameter replaces the C deprecated parameter.
1066              
1067             =item upper_limb
1068              
1069             Boolean to choose between checking the Sun's upper limb or its center.
1070             A true value selects the upper limb, a false value selects the center.
1071              
1072             This parameter is significant only when the altitude does not already deal with the sun radius.
1073             When the altitude takes into account the sun radius, this parameter should be false.
1074              
1075             Default value is 0, since the upper limb correction is already
1076             taken in account with the default -0.833 altitude.
1077              
1078             =item silent
1079              
1080             Boolean to control the output of some warning messages.
1081             With polar locations and dates near the winter solstice or the summer solstice,
1082             it may happen that the sun never rises above the horizon or never sets below.
1083             If this parameter is set to false, the module will send warnings for these
1084             conditions. If this parameter is set to true, the module will not pollute
1085             your F stream.
1086              
1087             Default value is 0, for backward compatibility.
1088              
1089             =back
1090              
1091             =head2 sunrise, sunset
1092              
1093             Although they come from the DateTime::Event::Sunrise module, these methods
1094             are C constructors. They use the same parameters as the C
1095             constructor, but they give objects from a different class.
1096              
1097             =head2 sunrise_datetime, sunset_datetime
1098              
1099             These two methods apply to C objects (that is, created
1100             with C, not C or C). They receive one parameter in addition
1101             to C<$self>, a C object. They return another C object,
1102             for the same day, but with the time of the sunrise or sunset, respectively.
1103              
1104             =head2 sunrise_sunset_span
1105              
1106             This method applies to C objects. It accepts a
1107             C object as the second parameter. It returns a C
1108             object, beginning at sunrise and ending at sunset.
1109              
1110             =head2 is_polar_night, is_polar_day, is_day_and_night
1111              
1112             These methods apply to C objects. They accept a
1113             C object as the second parameter. They return a boolean indicating
1114             the following condutions:
1115              
1116             =over 4
1117              
1118             =item * is_polar_night is true when the sun stays under the horizon. Or rather
1119             under the altitude parameter used when the C object was created.
1120              
1121             =item * is_polar_day is true when the sun stays above the horizon,
1122             resulting in a "Midnight sun". Or rather when it stays above the
1123             altitude parameter used when the C object was created.
1124              
1125             =item * is_day_and_night is true when neither is_polar_day, nor is_polar_night
1126             are true.
1127              
1128             =back
1129              
1130             =head2 next current previous contains as_list iterator
1131              
1132             See DateTime::Set.
1133              
1134             =head1 EXTENDED EXAMPLES
1135              
1136             my $dt = DateTime->new( year => 2000,
1137             month => 6,
1138             day => 20,
1139             );
1140              
1141             my $sunrise = DateTime::Event::Sunrise ->sunrise (
1142             longitude =>'-118',
1143             latitude =>'33',
1144             altitude => '-0.833',
1145             precise => '1'
1146             );
1147              
1148             my $sunset = DateTime::Event::Sunrise ->sunset (
1149             longitude =>'-118',
1150             latitude =>'33',
1151             altitude => '-0.833',
1152             precise => '1'
1153             );
1154              
1155             my $tmp_rise = $sunrise->next( $dt );
1156            
1157             my $dt2 = DateTime->new( year => 2000,
1158             month => 12,
1159             day => 31,
1160             );
1161            
1162             # iterator
1163             my $dt_span = DateTime::Span->new( start =>$dt, end=>$dt2 );
1164             my $set = $sunrise->intersection($dt_span);
1165             my $iter = $set->iterator;
1166             while ( my $dt = $iter->next ) {
1167             print ' ',$dt->datetime;
1168             }
1169              
1170             # is it day or night?
1171             my $day_set = DateTime::SpanSet->from_sets(
1172             start_set => $sunrise, end_set => $sunset );
1173             print $day_set->contains( $dt ) ? 'day' : 'night';
1174              
1175             my $dt = DateTime->new( year => 2000,
1176             month => 6,
1177             day => 20,
1178             time_zone => 'America/Los_Angeles',
1179             );
1180              
1181             my $sunrise = DateTime::Event::Sunrise ->new(
1182             longitude =>'-118' ,
1183             latitude => '33',
1184             altitude => '-0.833',
1185             precise => '1'
1186              
1187             );
1188              
1189             my $tmp = $sunrise->sunrise_sunset_span($dt);
1190             print "Sunrise is:" , $tmp->start->datetime , "\n";
1191             print "Sunset is:" , $tmp->end->datetime;
1192              
1193             =head1 NOTES
1194              
1195             =head2 Longitude Signs
1196              
1197             Remember, contrary to the usual convention,
1198              
1199             EASTERN longitudes are POSITIVE,
1200              
1201             WESTERN longitudes are NEGATIVE.
1202              
1203             On the other hand, the latitude signs follow the usual convention:
1204              
1205             Northen latitudes are positive,
1206              
1207             Southern latitudes are negative.
1208            
1209             =head2 Sun Height
1210              
1211             There are a number of sun heights to choose from. The default is
1212             -0.833 because this is what most countries use. Feel free to
1213             specify it if you need to. Here is the list of values to specify
1214             the sun height with:
1215              
1216             =over 4
1217              
1218             =item * B<0> degrees
1219              
1220             Center of Sun's disk touches a mathematical horizon
1221              
1222             =item * B<-0.25> degrees
1223              
1224             Sun's upper limb touches a mathematical horizon
1225              
1226             =item * B<-0.583> degrees
1227              
1228             Center of Sun's disk touches the horizon; atmospheric refraction accounted for
1229              
1230             =item * B<-0.833> degrees
1231              
1232             Sun's supper limb touches the horizon; atmospheric refraction accounted for
1233              
1234             =item * B<-6> degrees
1235              
1236             Civil twilight (one can no longer read outside without artificial illumination)
1237              
1238             =item * B<-12> degrees
1239              
1240             Nautical twilight (navigation using a sea horizon no longer possible)
1241              
1242             =item * B<-15> degrees
1243              
1244             Amateur astronomical twilight (the sky is dark enough for most astronomical observations)
1245              
1246             =item * B<-18> degrees
1247              
1248             Astronomical twilight (the sky is completely dark)
1249              
1250             =back
1251              
1252             =head2 Notes on the Precise Algorithm
1253              
1254             The original method only gives an approximate value of the Sun's rise/set times.
1255             The error rarely exceeds one or two minutes, but at high latitudes, when the Midnight Sun
1256             soon will start or just has ended, the errors may be much larger. If you want higher accuracy,
1257             you must then select the precise variant of the algorithm. This feature is new as of version 0.7. Here is
1258             what I have tried to accomplish with this.
1259              
1260              
1261             =over 4
1262              
1263             =item a)
1264              
1265             Compute sunrise or sunset as always, with one exception: to convert LHA from degrees to hours,
1266             divide by 15.04107 instead of 15.0 (this accounts for the difference between the solar day
1267             and the sidereal day.
1268              
1269             =item b)
1270              
1271             Re-do the computation but compute the Sun's RA and Decl, and also GMST0, for the moment
1272             of sunrise or sunset last computed.
1273              
1274             =item c)
1275              
1276             Iterate b) until the computed sunrise or sunset no longer changes significantly.
1277             Usually 2 iterations are enough, in rare cases 3 or 4 iterations may be needed.
1278              
1279             =back
1280              
1281             =head2 Notes on polar locations
1282              
1283             If the location is beyond either polar circle, and if the date is
1284             near either solstice, there can be midnight sun or polar night.
1285             In this case, there is neither sunrise nor sunset, and
1286             the module Cs that the sun never rises or never sets.
1287             Then, it returns the time at which the sun is at its highest
1288             or lowest point.
1289              
1290             =head1 DEPENDENCIES
1291              
1292             This module requires:
1293              
1294             =over 4
1295              
1296             =item *
1297              
1298             DateTime
1299              
1300             =item *
1301              
1302             DateTime::Set
1303              
1304             =item *
1305              
1306             DateTime::Span
1307              
1308             =item *
1309              
1310             Params::Validate
1311              
1312             =item *
1313              
1314             Set::Infinite
1315              
1316             =item *
1317              
1318             POSIX
1319              
1320             =item *
1321              
1322             Math::Trig
1323              
1324             =back
1325              
1326             =head1 BUGS AND CAVEATS
1327              
1328             Using a latitude of 90 degrees (North Pole or South Pole) gives curious results.
1329             I guess that it is linked with a ambiguous value resulting from a 0/0 computation.
1330              
1331             Using a longitude of 177 degrees, or any longitude near the 180 meridian, may also give
1332             curious results, especially with the precise algorithm.
1333              
1334             The precise algorithm should be overhauled.
1335              
1336             =head1 AUTHORS
1337              
1338             Original author: Ron Hill
1339              
1340             Co-maintainer: Jean Forget
1341              
1342             =head1 SPECIAL THANKS
1343              
1344             =over 4
1345              
1346             =item Robert Creager [Astro-Sunrise@LogicalChaos.org]
1347              
1348             for providing help with converting Paul's C code to perl.
1349              
1350             =item Flávio S. Glock [fglock@pucrs.br]
1351              
1352             for providing the the interface to the DateTime::Set
1353             module.
1354              
1355             =back
1356              
1357             =head1 CREDITS
1358              
1359             =over 4
1360              
1361             =item Paul Schlyter, Stockholm, Sweden
1362              
1363             for his excellent web page on the subject.
1364              
1365             =item Rich Bowen (rbowen@rbowen.com)
1366              
1367             for suggestions.
1368              
1369             =item People at L
1370              
1371             for noticing an endless loop condition in L and for fixing it.
1372              
1373             =back
1374              
1375             =head1 COPYRIGHT and LICENSE
1376              
1377             =head2 Perl Module
1378              
1379             This program is distributed under the same terms as Perl 5.16.3:
1380             GNU Public License version 1 or later and Perl Artistic License
1381              
1382             You can find the text of the licenses in the F file or at
1383             L
1384             and L.
1385              
1386             Here is the summary of GPL:
1387              
1388             This program is free software; you can redistribute it and/or modify
1389             it under the terms of the GNU General Public License as published by
1390             the Free Software Foundation; either version 1, or (at your option)
1391             any later version.
1392              
1393             This program is distributed in the hope that it will be useful,
1394             but WITHOUT ANY WARRANTY; without even the implied warranty of
1395             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1396             GNU General Public License for more details.
1397              
1398             You should have received a copy of the GNU General Public License
1399             along with this program; if not, write to the Free Software Foundation,
1400             Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
1401              
1402             =head2 Original C program
1403              
1404             Here is the copyright information provided by Paul Schlyter
1405             for the original C program:
1406              
1407             Written as DAYLEN.C, 1989-08-16
1408              
1409             Modified to SUNRISET.C, 1992-12-01
1410              
1411             (c) Paul Schlyter, 1989, 1992
1412              
1413             Released to the public domain by Paul Schlyter, December 1992
1414              
1415             Permission is hereby granted, free of charge, to any person obtaining a
1416             copy of this software and associated documentation files (the "Software"),
1417             to deal in the Software without restriction, including without limitation
1418             the rights to use, copy, modify, merge, publish, distribute, sublicense,
1419             and/or sell copies of the Software, and to permit persons to whom the
1420             Software is furnished to do so, subject to the following conditions:
1421              
1422             The above copyright notice and this permission notice shall be included
1423             in all copies or substantial portions of the Software.
1424              
1425             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1426             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1427             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1428             THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
1429             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
1430             OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1431             THE SOFTWARE.
1432              
1433             =head1 SEE ALSO
1434              
1435             perl(1).
1436              
1437             DateTime Web page at http://datetime.perl.org/
1438              
1439             DateTime::Set
1440              
1441             DateTime::SpanSet
1442              
1443             Astro::Sunrise
1444              
1445             DateTime::Event::Jewish::Sunrise
1446              
1447             Paul Schlyter's homepage at http://stjarnhimlen.se/english.html
1448              
1449             =cut
1450