File Coverage

blib/lib/DateTime/Event/Sunrise.pm
Criterion Covered Total %
statement 252 262 96.1
branch 71 78 91.0
condition 8 12 66.6
subroutine 41 44 93.1
pod 9 24 37.5
total 381 420 90.7


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 Ron Hill and Jean Forget
5             #
6             # See the license in the embedded documentation below.
7             #
8             package DateTime::Event::Sunrise;
9              
10 7     7   1860913 use strict;
  7         19  
  7         274  
11 7     7   42 use warnings;
  7         16  
  7         287  
12             require Exporter;
13 7     7   1085 use POSIX qw(floor);
  7         8871  
  7         65  
14 7     7   9993 use Math::Trig;
  7         159480  
  7         1614  
15 7     7   97 use Carp;
  7         17  
  7         478  
16 7     7   1452 use DateTime;
  7         171195  
  7         172  
17 7     7   7913 use DateTime::Set;
  7         356740  
  7         239  
18 7     7   80 use Params::Validate qw(:all);
  7         16  
  7         1857  
19 7     7   48 use Set::Infinite qw(inf $inf);
  7         22  
  7         809  
20 7     7   117 use vars qw( $VERSION $RADEG $DEGRAD @ISA );
  7         16  
  7         46121  
21             @ISA = qw( Exporter );
22             $VERSION = '0.0504';
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 716     716 1 2499054 my $class = shift;
33              
34 716 100       3928 if (@_ % 2 != 0) {
35 1         27 croak "Odd number of parameters";
36             }
37 715         4112 my %args = @_;
38 715 100 66     3189 if (exists $args{iteration} && exists $args{precise}) {
39 1         13 croak "Parameter 'iteration' is deprecated, use only 'precise'";
40             }
41              
42 714         30577 %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 714 50       25963 unless (exists $args{precise}) {
60 0         0 $args{precise} = $args{iteration};
61             }
62             # TODO : get rid of the old parameters after this point
63 714         1809 $args{iteration} = $args{precise};
64              
65 714         4167 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 119     119 1 619622 my $class = shift;
87 119         554 my $self = $class->new(@_);
88             return DateTime::Set->from_recurrence(
89             next => sub {
90 242 100   242   78663 return $_[0] if $_[0]->is_infinite;
91 123         860 $self->_following_sunrise( $_[0] );
92             },
93             previous => sub {
94 126 100   126   10238 return $_[0] if $_[0]->is_infinite;
95 7         48 $self->_previous_sunrise( $_[0] );
96 119         1348 } );
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 125     125 1 26613 my $class = shift;
118 125         454 my $self = $class->new(@_);
119             return DateTime::Set->from_recurrence(
120             next => sub {
121 254 100   254   41848 return $_[0] if $_[0]->is_infinite;
122 129         923 $self->_following_sunset( $_[0] );
123             },
124             previous => sub {
125 132 100   132   20679 return $_[0] if $_[0]->is_infinite;
126 7         58 $self->_previous_sunset( $_[0] );
127 125         1013 } );
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 2410 my $self = shift;
152 469         766 my $dt = shift;
153 469         1026 my $class = ref($dt);
154              
155 469 100       1951 if ( ! $dt->isa('DateTime') ) {
156 1         12 croak("Dates need to be DateTime objects");
157             }
158 468         1045 my ( undef, $tmp_set ) = _sunrise( $self, $dt );
159 468         2763 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 104378 my $self = shift;
184 469         1082 my $dt = shift;
185 469         1181 my $class = ref($dt);
186              
187 469 100       2465 if ( ! $dt->isa('DateTime') ) {
188 1         12 croak("Dates need to be DateTime objects");
189             }
190 468         2161 my ( $tmp_rise, undef ) = _sunrise( $self, $dt );
191 468         2724 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 1198 my $self = shift;
216 2         5 my $dt = shift;
217 2         6 my $class = ref($dt);
218              
219 2 100       18 if ( ! $dt->isa('DateTime') ) {
220 1         12 croak("Dates need to be DateTime objects");
221             }
222 1         4 my ( $tmp_rise, $tmp_set ) = _sunrise( $self, $dt );
223              
224 1         14 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 382101 my $self = shift;
250 181         357 my $dt = shift;
251 181         397 my $class = ref($dt);
252              
253 181 100       1026 if ( ! $dt->isa('DateTime') ) {
254 1         10 croak("Dates need to be DateTime objects");
255             }
256 180         670 my ( undef, undef, $rise_season, $set_season ) = _sunrise( $self, $dt, 1 );
257 180   66     3085 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 1647 my $self = shift;
280 181         291 my $dt = shift;
281 181         464 my $class = ref($dt);
282              
283 181 100       1120 if ( ! $dt->isa('DateTime') ) {
284 1         10 croak("Dates need to be DateTime objects");
285             }
286 180         537 my ( undef, undef, $rise_season, $set_season ) = _sunrise( $self, $dt, 1 );
287 180   66     2458 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 1386 my $self = shift;
310 181         301 my $dt = shift;
311 181         389 my $class = ref($dt);
312              
313 181 100       1281 if ( ! $dt->isa('DateTime') ) {
314 1         10 croak("Dates need to be DateTime objects");
315             }
316 180         614 my ( undef, undef, $rise_season, $set_season ) = _sunrise( $self, $dt, 1 );
317 180   66     2265 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 124     124   1204 my $self = shift;
343 124         253 my $dt = shift;
344 124 100       707 croak( "Dates need to be DateTime objects (" . ref($dt) . ")" )
345             unless ( $dt->isa('DateTime') );
346 123         1401 my ( $tmp_rise, undef ) = _sunrise( $self, $dt );
347 123 100       836 return $tmp_rise if $tmp_rise > $dt;
348 4         307 my $d = DateTime::Duration->new(
349             days => 1,
350             );
351 4         284 my $new_dt = $dt + $d;
352 4         3118 ( $tmp_rise, undef ) = _sunrise( $self, $new_dt );
353 4 100       32 return $tmp_rise if $tmp_rise > $dt;
354 1         84 $new_dt = $new_dt + $d;
355 1         1653 ( $tmp_rise, undef ) = _sunrise( $self, $new_dt );
356 1         19 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 8     8   1030 my $self = shift;
380 8         19 my $dt = shift;
381 8 100       55 croak( "Dates need to be DateTime objects (" . ref($dt) . ")" )
382             unless ( $dt->isa('DateTime') );
383 7         14 my ( $tmp_rise, undef ) = _sunrise( $self, $dt );
384 7 100       42 return $tmp_rise if $tmp_rise < $dt;
385 6         401 my $d = DateTime::Duration->new(
386             days => 1,
387             );
388 6         384 my $new_dt = $dt - $d;
389 6         5268 ( $tmp_rise, undef ) = _sunrise( $self, $new_dt );
390 6 50       48 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 130     130   1286 my $self = shift;
417 130         255 my $dt = shift;
418 130 100       468 croak( "Dates need to be DateTime objects (" . ref($dt) . ")" )
419             unless ( ref($dt) eq 'DateTime' );
420 129         299 my ( undef, $tmp_set ) = _sunrise( $self, $dt );
421 129 100       813 return $tmp_set if $tmp_set > $dt;
422 4         305 my $d = DateTime::Duration->new(
423             days => 1,
424             );
425 4         269 my $new_dt = $dt + $d;
426 4         3176 ( undef, $tmp_set ) = _sunrise( $self, $new_dt );
427 4 50       37 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 8     8   1010 my $self = shift;
454 8         15 my $dt = shift;
455 8 100       57 croak( "Dates need to be DateTime objects (" . ref($dt) . ")" )
456             unless ( $dt->isa('DateTime') );
457 7         20 my ( undef, $tmp_set ) = _sunrise( $self, $dt );
458 7 100       49 return $tmp_set if $tmp_set < $dt;
459 6         471 my $d = DateTime::Duration->new(
460             days => 1,
461             );
462 6         420 my $new_dt = $dt - $d;
463 6         5451 ( undef, $tmp_set ) = _sunrise( $self, $new_dt );
464 6 100       50 return $tmp_set if $tmp_set < $dt;
465 1         78 $new_dt = $new_dt - $d;
466 1         552 ( undef, $tmp_set ) = _sunrise( $self, $new_dt );
467 1         20 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 1765     1765   3164 my ($self, $dt, $silent) = @_;
497 1765         5853 my $cloned_dt = $dt->clone;
498 1765         30783 my $altit = $self->{altitude};
499 1765 50       5910 my $precise = defined( $self->{precise} ) ? $self->{precise} : 0;
500 1765 100       4673 unless (defined $silent) {
501 1225 50       4353 $silent = defined( $self->{silent} ) ? $self->{silent} : 0;
502             }
503 1765         14288 $cloned_dt->set_time_zone('floating');
504              
505 1765 100       71031 if ($precise) {
506              
507             # This is the initial start
508              
509 17         41 my $d = days_since_2000_Jan_0($cloned_dt) + 0.5 - $self->{longitude} / 360.0;
510 17         292 my ($tmp_rise_1, $tmp_set_1, $rise_season) = _sunrise_sunset( $d, $self->{longitude}, $self->{latitude}, $altit,
511             15.04107, $self->{upper_limb}, $silent);
512 17         25 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 17         18 my $tmp_rise_2 = 9;
518 17         20 my $tmp_rise_3 = 0;
519 17         46 until ( equal( $tmp_rise_2, $tmp_rise_3, 8 ) ) {
520              
521 51         94 my $d_sunrise_1 = $d + $tmp_rise_1 / 24.0;
522 51         134 ($tmp_rise_2, undef, undef) = _sunrise_sunset($d_sunrise_1, $self->{longitude}, $self->{latitude},
523             $altit, 15.04107, $self->{upper_limb}, $silent);
524 51         80 $tmp_rise_1 = $tmp_rise_3;
525 51         68 my $d_sunrise_2 = $d + $tmp_rise_2 / 24.0;
526 51         140 ($tmp_rise_3, undef, $rise_season) = _sunrise_sunset($d_sunrise_2, $self->{longitude}, $self->{latitude},
527             $altit, 15.04107, $self->{upper_limb}, $silent);
528             }
529              
530 17         28 my $tmp_set_2 = 9;
531 17         24 my $tmp_set_3 = 0;
532              
533 17         31 until ( equal( $tmp_set_2, $tmp_set_3, 8 ) ) {
534              
535 51         99 my $d_sunset_1 = $d + $tmp_set_1 / 24.0;
536 51         147 (undef, $tmp_set_2, undef) = _sunrise_sunset( $d_sunset_1, $self->{longitude}, $self->{latitude},
537             $altit, 15.04107, $self->{upper_limb}, $silent);
538 51         77 $tmp_set_1 = $tmp_set_3;
539 51         78 my $d_sunset_2 = $d + $tmp_set_2 / 24.0;
540 51         172 (undef, $tmp_set_3, $set_season) = _sunrise_sunset( $d_sunset_2, $self->{longitude}, $self->{latitude},
541             $altit, 15.04107, $self->{upper_limb}, $silent);
542              
543             }
544              
545 17         47 my ( $second_rise, $second_set ) = convert_hour( $tmp_rise_3, $tmp_set_3 );
546              
547             # This is to fix the datetime object to use a duration
548             # instead of blindly setting the hour/min
549 17         96 my $rise_dur = DateTime::Duration->new( seconds => $second_rise );
550 17         1975 my $set_dur = DateTime::Duration->new( seconds => $second_set );
551              
552 17         1254 my $tmp_dt1 = DateTime->new(
553             year => $dt->year,
554             month => $dt->month,
555             day => $dt->day,
556             hour => 0,
557             minute => 0,
558             time_zone => 'UTC'
559             );
560              
561 17         5316 my $rise_time = $tmp_dt1 + $rise_dur;
562 17         9769 my $set_time = $tmp_dt1 + $set_dur;
563 17         8319 my $tz = $dt->time_zone;
564 17 50       958 $rise_time->set_time_zone($tz) unless $tz->is_floating;
565 17 50       219 $set_time->set_time_zone($tz) unless $tz->is_floating;
566 17         342 return ( $rise_time, $set_time, $rise_season, $set_season );
567             }
568             else {
569 1748         12098 my $d = days_since_2000_Jan_0($cloned_dt) + 0.5 - $self->{longitude} / 360.0;
570 1748         31161 my ( $h1, $h2, $season ) = _sunrise_sunset( $d, $self->{longitude}, $self->{latitude}, $altit, 15.0, $self->{upper_limb}, $silent);
571 1748         4908 my ( $seconds_rise, $seconds_set ) = convert_hour( $h1, $h2 );
572 1748         13668 my $rise_dur = DateTime::Duration->new( seconds => $seconds_rise );
573 1748         159885 my $set_dur = DateTime::Duration->new( seconds => $seconds_set );
574 1748         129999 my $tmp_dt1 = DateTime->new(
575             year => $dt->year,
576             month => $dt->month,
577             day => $dt->day,
578             hour => 0,
579             minute => 0,
580             time_zone => 'UTC'
581             );
582              
583 1748         472870 my $rise_time = $tmp_dt1 + $rise_dur;
584 1748         927603 my $set_time = $tmp_dt1 + $set_dur;
585 1748         915807 my $tz = $dt->time_zone;
586 1748 100       13387 $rise_time->set_time_zone($tz) unless $tz->is_floating;
587 1748 100       41878 $set_time->set_time_zone($tz) unless $tz->is_floating;
588 1748         54778 return ( $rise_time, $set_time, $season, $season );
589             }
590              
591             }
592              
593             #
594             #
595             # FUNCTIONAL SEQUENCE for _sunrise_sunset
596             #
597             # _GIVEN
598             #
599             # days since Jan 0 2000, longitude, latitude, reference sun height $h and the "upper limb" and "silent" flags
600             # _THEN
601             #
602             # Compute the sunrise/sunset times for that day
603             #
604             # _RETURN
605             #
606             # sunrise and sunset times as hours (GMT Time)
607             # season flag: -1 for polar night, +1 for midnight sun, 0 for day and night
608             #
609             sub _sunrise_sunset {
610              
611 1969     1969   5192 my ( $d, $lon, $lat, $altit, $h, $upper_limb, $silent ) = @_;
612              
613             # Compute local sidereal time of this moment
614 1969         4854 my $sidtime = revolution(GMST0($d) + 180.0 + $lon);
615              
616             # Compute Sun's RA + Decl + distance at this moment
617 1969         4896 my ($sRA, $sdec, $sr) = sun_RA_dec($d);
618              
619             # Compute time when Sun is at south - in hours UT
620 1969         4907 my $tsouth = 12.0 - rev180( $sidtime - $sRA ) / $h;
621              
622             # Compute the Sun's apparent radius, degrees
623 1969         2951 my $sradius = 0.2666 / $sr;
624              
625             # Do correction to upper limb, if necessary
626 1969 100       4186 if ($upper_limb) {
627 775         1968 $altit -= $sradius;
628             }
629              
630             # Compute the diurnal arc that the Sun traverses to reach
631             # the specified height altit:
632              
633 1969         3385 my $cost = (sind($altit) - sind($lat) * sind($sdec))
634             / (cosd($lat) * cosd($sdec));
635              
636 1969         2791 my $t;
637 1969         2871 my $season = 0;
638 1969 100       6312 if ( $cost >= 1.0 ) {
    100          
639 238 100       469 unless ($silent) {
640 8         1271 carp "Sun never rises!!\n";
641             }
642 238         1446 $t = 0.0; # Sun always below altit
643 238         321 $season = -1;
644             }
645             elsif ( $cost <= -1.0 ) {
646 536 100       1436 unless ($silent) {
647 16         12794 carp "Sun never sets!!\n";
648             }
649 536         10503 $t = 12.0; # Sun always above altit
650 536         851 $season = +1;
651             }
652             else {
653 1195         2604 $t = acosd($cost) / 15.0; # The diurnal arc, hours
654             }
655              
656             # Store rise and set times - in hours UT
657              
658 1969         11920 my $hour_rise_ut = $tsouth - $t;
659 1969         2964 my $hour_set_ut = $tsouth + $t;
660 1969         5646 return ( $hour_rise_ut, $hour_set_ut, $season );
661              
662             }
663              
664             #
665             #
666             # FUNCTIONAL SEQUENCE for GMST0
667             #
668             # _GIVEN
669             # Day number
670             #
671             # _THEN
672             #
673             # computes GMST0, the Greenwich Mean Sidereal Time
674             # at 0h UT (i.e. the sidereal time at the Greenwhich meridian at
675             # 0h UT). GMST is then the sidereal time at Greenwich at any
676             # time of the day.
677             #
678             #
679             # _RETURN
680             #
681             # Sidtime
682             #
683             sub GMST0 {
684 1969     1969 0 2984 my ($d) = @_;
685 1969         5592 my $sidtim0 = revolution( ( 180.0 + 356.0470 + 282.9404 ) + ( 0.9856002585 + 4.70935E-5 ) * $d );
686 1969         5984 return $sidtim0;
687             }
688              
689             #
690             #
691             # FUNCTIONAL SEQUENCE for sunpos
692             #
693             # _GIVEN
694             # day number
695             #
696             # _THEN
697             #
698             # Computes the Sun's ecliptic longitude and distance
699             # at an instant given in d, number of days since
700             # 2000 Jan 0.0.
701             #
702             #
703             # _RETURN
704             #
705             # ecliptic longitude and distance
706             # ie. $True_solar_longitude, $Solar_distance
707             #
708             sub sunpos {
709              
710 1969     1969 0 2616 my ($d) = @_;
711              
712             # Mean anomaly of the Sun
713             # Mean longitude of perihelion
714             # Note: Sun's mean longitude = M + w
715             # Eccentricity of Earth's orbit
716             # Eccentric anomaly
717             # x, y coordinates in orbit
718             # True anomaly
719              
720             # Compute mean elements
721 1969         4537 my $Mean_anomaly_of_sun = revolution( 356.0470 + 0.9856002585 * $d );
722 1969         4546 my $Mean_longitude_of_perihelion = 282.9404 + 4.70935E-5 * $d;
723 1969         2864 my $Eccentricity_of_Earth_orbit = 0.016709 - 1.151E-9 * $d;
724              
725             # Compute true longitude and radius vector
726 1969         4734 my $Eccentric_anomaly = $Mean_anomaly_of_sun
727             + $Eccentricity_of_Earth_orbit * $RADEG
728             * sind($Mean_anomaly_of_sun)
729             * ( 1.0 + $Eccentricity_of_Earth_orbit * cosd($Mean_anomaly_of_sun) );
730              
731 1969         3775 my $x = cosd($Eccentric_anomaly) - $Eccentricity_of_Earth_orbit;
732              
733 1969         4729 my $y = sqrt( 1.0 - $Eccentricity_of_Earth_orbit * $Eccentricity_of_Earth_orbit )
734             * sind($Eccentric_anomaly);
735              
736 1969         3695 my $Solar_distance = sqrt( $x * $x + $y * $y ); # Solar distance
737 1969         4500 my $True_anomaly = atan2d( $y, $x ); # True anomaly
738              
739 1969         2774 my $True_solar_longitude =
740             $True_anomaly + $Mean_longitude_of_perihelion; # True solar longitude
741              
742 1969 100       5574 if ( $True_solar_longitude >= 360.0 ) {
743 985         1613 $True_solar_longitude -= 360.0; # Make it 0..360 degrees
744             }
745              
746 1969         5154 return ( $Solar_distance, $True_solar_longitude );
747             }
748              
749             #
750             #
751             # FUNCTIONAL SEQUENCE for sun_RA_dec
752             #
753             # _GIVEN
754             # day number, $r and $lon (from sunpos)
755             #
756             # _THEN
757             #
758             # compute RA and dec
759             #
760             #
761             # _RETURN
762             #
763             # Sun's Right Ascension (RA), Declination (dec) and distance (r)
764             #
765             #
766             sub sun_RA_dec {
767              
768 1969     1969 0 2946 my ($d) = @_;
769              
770             # Compute Sun's ecliptical coordinates
771 1969         4317 my ( $r, $lon ) = sunpos($d);
772              
773             # Compute ecliptic rectangular coordinates (z=0)
774 1969         3767 my $x = $r * cosd($lon);
775 1969         4087 my $y = $r * sind($lon);
776              
777             # Compute obliquity of ecliptic (inclination of Earth's axis)
778 1969         3321 my $obl_ecl = 23.4393 - 3.563E-7 * $d;
779              
780             # Convert to equatorial rectangular coordinates - x is unchanged
781 1969         3434 my $z = $y * sind($obl_ecl);
782 1969         3919 $y = $y * cosd($obl_ecl);
783              
784             # Convert to spherical coordinates
785 1969         4662 my $RA = atan2d( $y, $x );
786 1969         5238 my $dec = atan2d( $z, sqrt( $x * $x + $y * $y ) );
787              
788 1969         5273 return ( $RA, $dec, $r );
789              
790             } # sun_RA_dec
791              
792             #
793             #
794             # FUNCTIONAL SEQUENCE for days_since_2000_Jan_0
795             #
796             # _GIVEN
797             # A Datetime object
798             #
799             # _THEN
800             #
801             # process the DateTime object for number of days
802             # since Jan,1 2000 (counted in days)
803             # Day 0.0 is at Jan 1 2000 0.0 UT
804             #
805             # _RETURN
806             #
807             # day number
808             #
809             sub days_since_2000_Jan_0 {
810 1765     1765 0 2789 my ($dt) = @_;
811 1765         6217 return int($dt->jd - $jd_2000_Jan_0);
812             }
813              
814             sub sind {
815 13783     13783 0 41806 sin( ( $_[0] ) * $DEGRAD );
816             }
817              
818             sub cosd {
819 11814     11814 0 27982 cos( ( $_[0] ) * $DEGRAD );
820             }
821              
822             sub tand {
823 0     0 0 0 tan( ( $_[0] ) * $DEGRAD );
824             }
825              
826             sub atand {
827 0     0 0 0 ( $RADEG * atan( $_[0] ) );
828             }
829              
830             sub asind {
831 0     0 0 0 ( $RADEG * asin( $_[0] ) );
832             }
833              
834             sub acosd {
835 1195     1195 0 4555 ( $RADEG * acos( $_[0] ) );
836             }
837              
838             sub atan2d {
839 5907     5907 0 26828 ( $RADEG * atan2( $_[0], $_[1] ) );
840             }
841              
842             #
843             #
844             # FUNCTIONAL SEQUENCE for revolution
845             #
846             # _GIVEN
847             # any angle in degrees
848             #
849             # _THEN
850             #
851             # reduces any angle to within the first revolution
852             # by subtracting or adding even multiples of 360.0
853             #
854             #
855             # _RETURN
856             #
857             # the value of the input is >= 0.0 and < 360.0
858             #
859             sub revolution {
860              
861 5907     5907 0 8222 my $x = $_[0];
862 5907         19729 return ( $x - 360.0 * floor( $x * $INV360 ) );
863             }
864              
865             #
866             #
867             # FUNCTIONAL SEQUENCE for rev180
868             #
869             # _GIVEN
870             #
871             # any angle in degrees
872             #
873             # _THEN
874             #
875             # Reduce input to within +180..+180 degrees
876             #
877             #
878             # _RETURN
879             #
880             # angle that was reduced
881             #
882             sub rev180 {
883              
884 1969     1969 0 2836 my ($x) = @_;
885              
886 1969         8126 return ( $x - 360.0 * floor( $x * $INV360 + 0.5 ) );
887             }
888              
889             #
890             #
891             # FUNCTIONAL SEQUENCE for equal
892             #
893             # _GIVEN
894             #
895             # Two floating point numbers and Accuracy
896             #
897             # _THEN
898             #
899             # Use sprintf to format the numbers to Accuracy
900             # number of decimal places
901             #
902             # _RETURN
903             #
904             # True if the numbers are equal
905             #
906             sub equal {
907              
908 136     136 0 195 my ( $A, $B, $dp ) = @_;
909              
910 136         1138 return sprintf( "%.${dp}g", $A ) eq sprintf( "%.${dp}g", $B );
911             }
912              
913             #
914             #
915             # FUNCTIONAL SEQUENCE for convert_hour
916             #
917             # _GIVEN
918             # Hour_rise, Hour_set
919             # hours are in UT
920             #
921             # _THEN
922             #
923             # split out the hours and minutes
924             # Oct 20 2003
925             # will convert hours to seconds and return this
926             # let DateTime handle the conversion
927             #
928             # _RETURN
929             #
930             # number of seconds
931             sub convert_hour {
932              
933 1765     1765 0 2371 my ( $hour_rise_ut, $hour_set_ut ) = @_;
934 1765         5428 my $seconds_rise = floor( $hour_rise_ut * 60 * 60 );
935 1765         4394 my $seconds_set = floor( $hour_set_ut * 60 * 60 );
936              
937 1765         3756 return ( $seconds_rise, $seconds_set );
938             }
939              
940             1962; # Hint: sung by RZ, better known as BD
941              
942             =encoding utf8
943              
944             =head1 NAME
945              
946             DateTime::Event::Sunrise - Perl DateTime extension for computing the sunrise/sunset on a given day
947              
948             =head1 SYNOPSIS
949              
950             use DateTime;
951             use DateTime::Event::Sunrise;
952              
953             # generating DateTime objects from a DateTime::Event::Sunrise object
954             my $sun_Kyiv = DateTime::Event::Sunrise->new(longitude => +30.85, # 30°51'E
955             latitude => +50.45); # 50°27'N
956             for (12, 13, 14) {
957             my $dt_yapc_eu = DateTime->new(year => 2013,
958             month => 8,
959             day => $_,
960             time_zone => 'Europe/Kiev');
961             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,
962             " and sunset occurs at ", $sun_Kyiv->sunset_datetime ($dt_yapc_eu)->hms;
963             }
964              
965             # generating DateTime objects from DateTime::Set objects
966             my $sunrise_Austin = DateTime::Event::Sunrise->sunrise(longitude => -94.73, # 97°44'W
967             latitude => +30.3); # 30°18'N
968             my $sunset_Austin = DateTime::Event::Sunrise->sunset (longitude => -94.73,
969             latitude => +30.3);
970             my $dt_yapc_na_rise = DateTime->new(year => 2013,
971             month => 6,
972             day => 3,
973             time_zone => 'America/Chicago');
974             my $dt_yapc_na_set = $dt_yapc_na_rise->clone;
975             say "In Austin (30°18'N, 97°44'W), sunrises and sunsets are";
976             for (1..3) {
977             $dt_yapc_na_rise = $sunrise_Austin->next($dt_yapc_na_rise);
978             $dt_yapc_na_set = $sunset_Austin ->next($dt_yapc_na_set);
979             say $dt_yapc_na_rise, ' ', $dt_yapc_na_set;
980             }
981              
982             # If you deal with a polar location
983             my $sun_in_Halley = DateTime::Event::Sunrise->new(
984             longitude => -26.65, # 26°39'W
985             latitude => -75.58, # 75°35'S
986             precise => 1,
987             );
988             my $Alex_arrival = DateTime->new(year => 2006, # approximate date, not necessarily the exact one
989             month => 1,
990             day => 15,
991             time_zone => 'Antarctica/Rothera');
992             say $Alex_arrival->strftime("Alex Gough (a Perl programmer) arrived at Halley Base on %Y-%m-%d.");
993             if ($sun_in_Halley->is_polar_day($Alex_arrival)) {
994             say "It would be days, maybe weeks, before the sun would set.";
995             }
996             elsif ($sun_in_Halley->is_polar_night($Alex_arrival)) {
997             say "It would be days, maybe weeks, before the sun would rise.";
998             }
999             else {
1000             my $sunset = $sun_in_Halley->sunset_datetime($Alex_arrival);
1001             say $sunset->strftime("And he saw his first antarctic sunset at %H:%M:%S.");
1002             }
1003              
1004             =head1 DESCRIPTION
1005              
1006             This module will computes the time of sunrise and sunset for a given date
1007             and a given location. The computation uses Paul Schlyter's algorithm.
1008              
1009             Actually, the module creates a DateTime::Event::Sunrise object or a
1010             DateTime::Set object, which are used to generate the sunrise or the sunset
1011             times for a given location and for any date.
1012              
1013             =head1 METHODS
1014              
1015             =head2 new
1016              
1017             This is the DateTime::Event::Sunrise constructor. It takes keyword
1018             parameters, which are:
1019              
1020             =over 4
1021              
1022             =item longitude
1023              
1024             This is the longitude of the location where the sunrises and sunsets are observed.
1025             It is given as decimal degrees: no minutes, no seconds, but tenths and hundredths of degrees.
1026             Another break with the normal usage is that Eastern longitude are positive, Western longitudes
1027             are negative.
1028              
1029             Default value is 0, that is Greenwich or any location on the eponymous meridian.
1030              
1031             =item latitude
1032              
1033             This is the latitude of the location where the sunrises and sunsets are observed.
1034             As for the longitude, it is given as decimal degrees. Northern latitudes are positive
1035             numbers, Southern latitudes are negative numbers.
1036              
1037             Default value is 0, that is any location on the equator.
1038              
1039             =item altitude
1040              
1041             This is the height of the Sun at sunrise or sunset. In astronomical context, the altitude or
1042             height is the angle between the Sun and the local horizon. It is expressed as degrees, usually
1043             with a negative number, since the Sun is I the horizon.
1044              
1045             Default value is -0.833, that is when the sun's upper limb touches the horizon, while
1046             taking in account the light refraction.
1047              
1048             Positive altitude are allowed, in case the location is near a mountain range
1049             behind which the sun rises or sets.
1050              
1051             =item precise
1052              
1053             Boolean to control which algorithm is used. A false value gives a simple algorithm, but
1054             which can lead to inaccurate sunrise times and sunset times. A true value gives
1055             a more elaborate algorithm, with a loop to refine the sunrise and sunset times
1056             and obtain a better precision.
1057              
1058             Default value is 0, to choose the simple algorithm.
1059              
1060             This parameter replaces the C deprecated parameter.
1061              
1062             =item upper_limb
1063              
1064             Boolean to choose between checking the Sun's upper limb or its center.
1065             A true value selects the upper limb, a false value selects the center.
1066              
1067             This parameter is significant only when the altitude does not already deal with the sun radius.
1068             When the altitude takes into account the sun radius, this parameter should be false.
1069              
1070             Default value is 0, since the upper limb correction is already
1071             taken in account with the default -0.833 altitude.
1072              
1073             =item silent
1074              
1075             Boolean to control the output of some warning messages.
1076             With polar locations and dates near the winter solstice or the summer solstice,
1077             it may happen that the sun never rises above the horizon or never sets below.
1078             If this parameter is set to false, the module will send warnings for these
1079             conditions. If this parameter is set to true, the module will not pollute
1080             your F stream.
1081              
1082             Default value is 0, for backward compatibility.
1083              
1084             =back
1085              
1086             =head2 sunrise, sunset
1087              
1088             Although they come from the DateTime::Event::Sunrise module, these methods
1089             are C constructors. They use the same parameters as the C
1090             constructor, but they give objects from a different class.
1091              
1092             =head2 sunrise_datetime, sunset_datetime
1093              
1094             These two methods apply to C objects (that is, created
1095             with C, not C or C). They receive one parameter in addition
1096             to C<$self>, a C object. They return another C object,
1097             for the same day, but with the time of the sunrise or sunset, respectively.
1098              
1099             =head2 sunrise_sunset_span
1100              
1101             This method applies to C objects. It accepts a
1102             C object as the second parameter. It returns a C
1103             object, beginning at sunrise and ending at sunset.
1104              
1105             =head2 is_polar_night, is_polar_day, is_day_and_night
1106              
1107             These methods apply to C objects. They accept a
1108             C object as the second parameter. They return a boolean indicating
1109             the following condutions:
1110              
1111             =over 4
1112              
1113             =item * is_polar_night is true when the sun stays under the horizon. Or rather
1114             under the altitude parameter used when the C object was created.
1115              
1116             =item * is_polar_day is true when the sun stays above the horizon,
1117             resulting in a "Midnight sun". Or rather when it stays above the
1118             altitude parameter used when the C object was created.
1119              
1120             =item * is_day_and_night is true when neither is_polar_day, nor is_polar_night
1121             are true.
1122              
1123             =back
1124              
1125             =head2 next current previous contains as_list iterator
1126              
1127             See DateTime::Set.
1128              
1129             =head1 EXTENDED EXAMPLES
1130              
1131             my $dt = DateTime->new( year => 2000,
1132             month => 6,
1133             day => 20,
1134             );
1135              
1136             my $sunrise = DateTime::Event::Sunrise ->sunrise (
1137             longitude =>'-118',
1138             latitude =>'33',
1139             altitude => '-0.833',
1140             precise => '1'
1141             );
1142              
1143             my $sunset = DateTime::Event::Sunrise ->sunset (
1144             longitude =>'-118',
1145             latitude =>'33',
1146             altitude => '-0.833',
1147             precise => '1'
1148             );
1149              
1150             my $tmp_rise = $sunrise->next( $dt );
1151            
1152             my $dt2 = DateTime->new( year => 2000,
1153             month => 12,
1154             day => 31,
1155             );
1156            
1157             # iterator
1158             my $dt_span = DateTime::Span->new( start =>$dt, end=>$dt2 );
1159             my $set = $sunrise->intersection($dt_span);
1160             my $iter = $set->iterator;
1161             while ( my $dt = $iter->next ) {
1162             print ' ',$dt->datetime;
1163             }
1164              
1165             # is it day or night?
1166             my $day_set = DateTime::SpanSet->from_sets(
1167             start_set => $sunrise, end_set => $sunset );
1168             print $day_set->contains( $dt ) ? 'day' : 'night';
1169              
1170             my $dt = DateTime->new( year => 2000,
1171             month => 6,
1172             day => 20,
1173             time_zone => 'America/Los_Angeles',
1174             );
1175              
1176             my $sunrise = DateTime::Event::Sunrise ->new(
1177             longitude =>'-118' ,
1178             latitude => '33',
1179             altitude => '-0.833',
1180             precise => '1'
1181              
1182             );
1183              
1184             my $tmp = $sunrise->sunrise_sunset_span($dt);
1185             print "Sunrise is:" , $tmp->start->datetime , "\n";
1186             print "Sunset is:" , $tmp->end->datetime;
1187              
1188             =head1 NOTES
1189              
1190             =head2 Longitude Signs
1191              
1192             Remember, contrary to the usual convention,
1193              
1194             EASTERN longitudes are POSITIVE,
1195              
1196             WESTERN longitudes are NEGATIVE.
1197              
1198             On the other hand, the latitude signs follow the usual convention:
1199              
1200             Northen latitudes are positive,
1201              
1202             Southern latitudes are negative.
1203            
1204             =head2 Sun Height
1205              
1206             There are a number of sun heights to choose from. The default is
1207             -0.833 because this is what most countries use. Feel free to
1208             specify it if you need to. Here is the list of values to specify
1209             the sun height with:
1210              
1211             =over 4
1212              
1213             =item * B<0> degrees
1214              
1215             Center of Sun's disk touches a mathematical horizon
1216              
1217             =item * B<-0.25> degrees
1218              
1219             Sun's upper limb touches a mathematical horizon
1220              
1221             =item * B<-0.583> degrees
1222              
1223             Center of Sun's disk touches the horizon; atmospheric refraction accounted for
1224              
1225             =item * B<-0.833> degrees
1226              
1227             Sun's supper limb touches the horizon; atmospheric refraction accounted for
1228              
1229             =item * B<-6> degrees
1230              
1231             Civil twilight (one can no longer read outside without artificial illumination)
1232              
1233             =item * B<-12> degrees
1234              
1235             Nautical twilight (navigation using a sea horizon no longer possible)
1236              
1237             =item * B<-15> degrees
1238              
1239             Amateur astronomical twilight (the sky is dark enough for most astronomical observations)
1240              
1241             =item * B<-18> degrees
1242              
1243             Astronomical twilight (the sky is completely dark)
1244              
1245             =back
1246              
1247             =head2 Notes on the Precise Algorithm
1248              
1249             The original method only gives an approximate value of the Sun's rise/set times.
1250             The error rarely exceeds one or two minutes, but at high latitudes, when the Midnight Sun
1251             soon will start or just has ended, the errors may be much larger. If you want higher accuracy,
1252             you must then select the precise variant of the algorithm. This feature is new as of version 0.7. Here is
1253             what I have tried to accomplish with this.
1254              
1255              
1256             =over 4
1257              
1258             =item a)
1259              
1260             Compute sunrise or sunset as always, with one exception: to convert LHA from degrees to hours,
1261             divide by 15.04107 instead of 15.0 (this accounts for the difference between the solar day
1262             and the sidereal day.
1263              
1264             =item b)
1265              
1266             Re-do the computation but compute the Sun's RA and Decl, and also GMST0, for the moment
1267             of sunrise or sunset last computed.
1268              
1269             =item c)
1270              
1271             Iterate b) until the computed sunrise or sunset no longer changes significantly.
1272             Usually 2 iterations are enough, in rare cases 3 or 4 iterations may be needed.
1273              
1274             =back
1275              
1276             =head2 Notes on polar locations
1277              
1278             If the location is beyond either polar circle, and if the date is
1279             near either solstice, there can be midnight sun or polar night.
1280             In this case, there is neither sunrise nor sunset, and
1281             the module Cs that the sun never rises or never sets.
1282             Then, it returns the time at which the sun is at its highest
1283             or lowest point.
1284              
1285             =head1 DEPENDENCIES
1286              
1287             This module requires:
1288              
1289             =over 4
1290              
1291             =item *
1292              
1293             DateTime
1294              
1295             =item *
1296              
1297             DateTime::Set
1298              
1299             =item *
1300              
1301             DateTime::Span
1302              
1303             =item *
1304              
1305             Params::Validate
1306              
1307             =item *
1308              
1309             Set::Infinite
1310              
1311             =item *
1312              
1313             POSIX
1314              
1315             =item *
1316              
1317             Math::Trig
1318              
1319             =back
1320              
1321             =head1 BUGS AND CAVEATS
1322              
1323             Using a latitude of 90 degrees (North Pole or South Pole) gives curious results.
1324             I guess that it is linked with a ambiguous value resulting from a 0/0 computation.
1325              
1326             =head1 AUTHORS
1327              
1328             Ron Hill
1329              
1330             Co-maintainer: Jean Forget
1331              
1332             =head1 SPECIAL THANKS
1333              
1334             =over 4
1335              
1336             =item Robert Creager [Astro-Sunrise@LogicalChaos.org]
1337              
1338             for providing help with converting Paul's C code to perl.
1339              
1340             =item Flávio S. Glock [fglock@pucrs.br]
1341              
1342             for providing the the interface to the DateTime::Set
1343             module.
1344              
1345             =back
1346              
1347             =head1 CREDITS
1348              
1349             =over 4
1350              
1351             =item Paul Schlyter, Stockholm, Sweden
1352              
1353             for his excellent web page on the subject.
1354              
1355             =item Rich Bowen (rbowen@rbowen.com)
1356              
1357             for suggestions.
1358              
1359             =back
1360              
1361             =head1 COPYRIGHT and LICENSE
1362              
1363             =head2 Perl Module
1364              
1365             This program is distributed under the same terms as Perl 5.16.3:
1366             GNU Public License version 1 or later and Perl Artistic License
1367              
1368             You can find the text of the licenses in the F file or at
1369             L
1370             and L.
1371              
1372             Here is the summary of GPL:
1373              
1374             This program is free software; you can redistribute it and/or modify
1375             it under the terms of the GNU General Public License as published by
1376             the Free Software Foundation; either version 1, or (at your option)
1377             any later version.
1378              
1379             This program is distributed in the hope that it will be useful,
1380             but WITHOUT ANY WARRANTY; without even the implied warranty of
1381             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1382             GNU General Public License for more details.
1383              
1384             You should have received a copy of the GNU General Public License
1385             along with this program; if not, write to the Free Software Foundation,
1386             Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
1387              
1388             =head2 Original C program
1389              
1390             Here is the copyright information provided by Paul Schlyter
1391             for the original C program:
1392              
1393             Written as DAYLEN.C, 1989-08-16
1394              
1395             Modified to SUNRISET.C, 1992-12-01
1396              
1397             (c) Paul Schlyter, 1989, 1992
1398              
1399             Released to the public domain by Paul Schlyter, December 1992
1400              
1401             Permission is hereby granted, free of charge, to any person obtaining a
1402             copy of this software and associated documentation files (the "Software"),
1403             to deal in the Software without restriction, including without limitation
1404             the rights to use, copy, modify, merge, publish, distribute, sublicense,
1405             and/or sell copies of the Software, and to permit persons to whom the
1406             Software is furnished to do so, subject to the following conditions:
1407              
1408             The above copyright notice and this permission notice shall be included
1409             in all copies or substantial portions of the Software.
1410              
1411             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1412             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1413             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1414             THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
1415             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
1416             OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1417             THE SOFTWARE.
1418              
1419             =head1 SEE ALSO
1420              
1421             perl(1).
1422              
1423             DateTime Web page at http://datetime.perl.org/
1424              
1425             DateTime::Set
1426              
1427             DateTime::SpanSet
1428              
1429             Astro::Sunrise
1430              
1431             DateTime::Event::Jewish::Sunrise
1432              
1433             Paul Schlyter's homepage at http://stjarnhimlen.se/english.html
1434              
1435             =cut
1436