File Coverage

blib/lib/Astro/Sunrise.pm
Criterion Covered Total %
statement 207 209 99.0
branch 58 64 90.6
condition 22 27 81.4
subroutine 40 40 100.0
pod 11 21 52.3
total 338 361 93.6


line stmt bran cond sub pod time code
1             # -*- encoding: utf-8; indent-tabs-mode: nil -*-
2             #
3             # Perl extension for computing the sunrise/sunset on a given day
4             # Copyright (C) 1999-2003, 2013, 2015, 2017 Ron Hill and Jean Forget
5             #
6             # See the license in the embedded documentation below.
7             #
8             package Astro::Sunrise;
9              
10 10     10   774078 use strict;
  10         97  
  10         310  
11 10     10   57 use warnings;
  10         22  
  10         330  
12 10     10   678 use POSIX qw(floor);
  10         13854  
  10         69  
13 10     10   11010 use Math::Trig;
  10         137184  
  10         1513  
14 10     10   110 use Carp;
  10         23  
  10         650  
15 10     10   64 use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $RADEG $DEGRAD );
  10         25  
  10         12419  
16              
17             require Exporter;
18              
19             @ISA = qw( Exporter );
20             @EXPORT = qw( sunrise sun_rise sun_set );
21             @EXPORT_OK = qw( DEFAULT CIVIL NAUTICAL AMATEUR ASTRONOMICAL sind cosd tand asind acosd atand atan2d equal );
22             %EXPORT_TAGS = (
23             constants => [ qw/DEFAULT CIVIL NAUTICAL AMATEUR ASTRONOMICAL/ ],
24             trig => [ qw/sind cosd tand asind acosd atand atan2d equal/ ],
25             );
26              
27             $VERSION = '0.97';
28             $RADEG = ( 180 / pi );
29             $DEGRAD = ( pi / 180 );
30             my $INV360 = ( 1.0 / 360.0 );
31              
32             sub sunrise {
33 610     610 1 436855 my %arg;
34 610 100       1540 if (ref($_[0]) eq 'HASH') {
35 492         763 %arg = %{$_[0]};
  492         2721  
36             }
37             else {
38 118         459 @arg{ qw/year month day lon lat tz isdst alt precise/ } = @_;
39             }
40             my ( $year, $month, $day, $lon, $lat, $TZ, $isdst)
41 610         1978 = @arg{ qw/year month day lon lat tz isdst/ };
42 610 100       1397 my $altit = defined($arg{alt} ) ? $arg{alt} : -0.833;
43 610   100     2667 $arg{precise} ||= 0;
44 610   100     1668 $arg{upper_limb} ||= 0;
45 610   100     1631 $arg{polar} ||= 'warn';
46 610 100       1159 croak "Year parameter is mandatory"
47             unless defined $year;
48 609 100       975 croak "Month parameter is mandatory"
49             unless defined $month;
50 608 100       1036 croak "Day parameter is mandatory"
51             unless defined $day;
52 607 100       1009 croak "Longitude parameter (keyword: 'lon') is mandatory"
53             unless defined $lon;
54 606 100       1026 croak "Latitude parameter (keyword: 'lat') is mandatory"
55             unless defined $lat;
56             croak "Wrong value of the 'polar' argument: should be either 'warn' or 'retval'"
57 605 100 100     1621 if $arg{polar} ne 'warn' and $arg{polar} ne 'retval';
58              
59 604 100       1008 if ($arg{precise}) {
60             # This is the initial start
61              
62 2         7 my $d = days_since_2000_Jan_0( $year, $month, $day ) + 0.5 - $lon / 360.0;
63 2         6 my ($tmp_rise_1, $tmp_set_1) = sun_rise_set($d, $lon, $lat, $altit, 15.04107, $arg{upper_limb}, $arg{polar});
64              
65             # Now we have the initial rise/set times next recompute d using the exact moment
66             # recompute sunrise
67              
68 2         2 my $tmp_rise_2 = 9;
69 2         4 my $tmp_rise_3 = 0;
70              
71 2         2 my $counter = 0;
72 2         5 until (equal($tmp_rise_2, $tmp_rise_3, 8) ) {
73              
74 103         170 my $d_sunrise_1 = $d + $tmp_rise_1/24.0;
75 103         172 ($tmp_rise_2, undef) = sun_rise_set($d_sunrise_1, $lon, $lat, $altit, 15.04107, $arg{upper_limb}, $arg{polar});
76 103         128 $tmp_rise_1 = $tmp_rise_3;
77 103         135 my $d_sunrise_2 = $d + $tmp_rise_2/24.0;
78 103         155 ($tmp_rise_3, undef) = sun_rise_set($d_sunrise_2, $lon, $lat, $altit, 15.04107, $arg{upper_limb}, $arg{polar});
79              
80             #print "tmp_rise2 is: $tmp_rise_2 tmp_rise_3 is:$tmp_rise_3\n";
81 103 100       209 last if ++$counter >= 100;
82             }
83              
84 2         4 my $tmp_set_2 = 9;
85 2         3 my $tmp_set_3 = 0;
86              
87 2         4 $counter = 0;
88 2         7 until (equal($tmp_set_2, $tmp_set_3, 8) ) {
89              
90 103         184 my $d_sunset_1 = $d + $tmp_set_1/24.0;
91 103         179 (undef, $tmp_set_2) = sun_rise_set($d_sunset_1, $lon, $lat, $altit, 15.04107, $arg{upper_limb}, $arg{polar});
92 103         122 $tmp_set_1 = $tmp_set_3;
93 103         131 my $d_sunset_2 = $d + $tmp_set_2/24.0;
94 103         169 (undef, $tmp_set_3) = sun_rise_set($d_sunset_2, $lon, $lat, $altit, 15.04107, $arg{upper_limb}, $arg{polar});
95              
96             #print "tmp_set_1 is: $tmp_set_1 tmp_set_3 is:$tmp_set_3\n";
97 103 100       208 last if ++$counter >= 100;
98              
99             }
100              
101 2         8 return convert_hour($tmp_rise_3, $tmp_set_3, $TZ, $isdst);
102              
103             }
104             else {
105 602         1171 my $d = days_since_2000_Jan_0( $year, $month, $day ) + 0.5 - $lon / 360.0;
106 602         1279 my ($h1, $h2) = sun_rise_set($d, $lon, $lat, $altit, 15.0, $arg{upper_limb}, $arg{polar});
107 602 50 100     5422 if ($h1 eq 'day' or $h1 eq 'night' or $h2 eq 'day' or $h2 eq 'night') {
      66        
      66        
108 150         629 return ($h1, $h2);
109             }
110 452         942 return convert_hour($h1, $h2, $TZ, $isdst);
111             }
112             }
113             #######################################################################################
114             # end sunrise
115             ###################################################################################
116              
117              
118             sub sun_rise_set {
119 1016     1016 0 1832 my ($d, $lon, $lat,$altit, $h, $upper_limb, $polar) = @_;
120              
121             # Compute local sidereal time of this moment
122 1016         1469 my $sidtime = revolution( GMST0($d) + 180.0 + $lon );
123              
124             # Compute Sun's RA + Decl + distance at this moment
125 1016         1595 my ( $sRA, $sdec, $sr ) = sun_RA_dec($d);
126              
127             # Compute time when Sun is at south - in hours UT
128 1016         1649 my $tsouth = 12.0 - rev180( $sidtime - $sRA ) / $h;
129              
130 1016 100       1770 if ($upper_limb) {
131             # Compute the Sun's apparent radius, degrees
132 234         298 my $sradius = 0.2666 / $sr;
133 234         481 $altit -= $sradius;
134             }
135              
136             # Compute the diurnal arc that the Sun traverses to reach
137             # the specified altitude altit:
138              
139 1016         1327 my $cost = ( sind($altit) - sind($lat) * sind($sdec) )
140             / ( cosd($lat) * cosd($sdec) );
141              
142 1016         1330 my $t;
143 1016 100       1933 if ( $cost >= 1.0 ) {
    100          
144 50 100       89 if ($polar eq 'retval') {
145 46         108 return ('night', 'night');
146             }
147 4         579 carp "Sun never rises!!\n";
148 4         204 $t = 0.0; # Sun always below altit
149             }
150             elsif ( $cost <= -1.0 ) {
151 112 100       218 if ($polar eq 'retval') {
152 104         295 return ('day', 'day');
153             }
154 8         1104 carp "Sun never sets!!\n";
155 8         381 $t = 12.0; # Sun always above altit
156             }
157             else {
158 854         1140 $t = acosd($cost) / 15.0; # The diurnal arc, hours
159             }
160              
161             # Store rise and set times - in hours UT
162              
163 866         4576 my $hour_rise_ut = $tsouth - $t;
164 866         1065 my $hour_set_ut = $tsouth + $t;
165 866         1587 return($hour_rise_ut, $hour_set_ut);
166             }
167              
168             #########################################################################################################
169             #
170             #
171             # FUNCTIONAL SEQUENCE for GMST0
172             #
173             # _GIVEN
174             # Day number
175             #
176             # _THEN
177             #
178             # computes GMST0, the Greenwich Mean Sidereal Time
179             # at 0h UT (i.e. the sidereal time at the Greenwich meridian at
180             # 0h UT). GMST is then the sidereal time at Greenwich at any
181             # time of the day..
182             #
183             #
184             # _RETURN
185             #
186             # Sidtime
187             #
188             sub GMST0 {
189 1016     1016 0 1285 my ($d) = @_;
190              
191 1016         1802 my $sidtim0 =
192             revolution( ( 180.0 + 356.0470 + 282.9404 ) +
193             ( 0.9856002585 + 4.70935E-5 ) * $d );
194 1016         1806 return $sidtim0;
195              
196             }
197              
198              
199             #
200             #
201             # FUNCTIONAL SEQUENCE for sunpos
202             #
203             # _GIVEN
204             # day number
205             #
206             # _THEN
207             #
208             # Computes the Sun's ecliptic longitude and distance
209             # at an instant given in d, number of days since
210             # 2000 Jan 0.0.
211             #
212             #
213             # _RETURN
214             #
215             # ecliptic longitude and distance
216             # ie. $True_solar_longitude, $Solar_distance
217             #
218             sub sunpos {
219 1016     1016 0 1226 my ($d) = @_;
220              
221             # Mean anomaly of the Sun
222             # Mean longitude of perihelion
223             # Note: Sun's mean longitude = M + w
224             # Eccentricity of Earth's orbit
225             # Eccentric anomaly
226             # x, y coordinates in orbit
227             # True anomaly
228              
229             # Compute mean elements
230 1016         1551 my $Mean_anomaly_of_sun = revolution( 356.0470 + 0.9856002585 * $d );
231 1016         1355 my $Mean_longitude_of_perihelion = 282.9404 + 4.70935E-5 * $d;
232 1016         1264 my $Eccentricity_of_Earth_orbit = 0.016709 - 1.151E-9 * $d;
233              
234             # Compute true longitude and radius vector
235 1016         1432 my $Eccentric_anomaly =
236             $Mean_anomaly_of_sun + $Eccentricity_of_Earth_orbit * $RADEG *
237             sind($Mean_anomaly_of_sun) *
238             ( 1.0 + $Eccentricity_of_Earth_orbit * cosd($Mean_anomaly_of_sun) );
239              
240 1016         1438 my $x = cosd($Eccentric_anomaly) - $Eccentricity_of_Earth_orbit;
241              
242 1016         1493 my $y =
243             sqrt( 1.0 - $Eccentricity_of_Earth_orbit * $Eccentricity_of_Earth_orbit )
244             * sind($Eccentric_anomaly);
245              
246 1016         1343 my $Solar_distance = sqrt( $x * $x + $y * $y ); # Solar distance
247 1016         1420 my $True_anomaly = atan2d( $y, $x ); # True anomaly
248              
249 1016         1273 my $True_solar_longitude =
250             $True_anomaly + $Mean_longitude_of_perihelion; # True solar longitude
251              
252 1016 100       1795 if ( $True_solar_longitude >= 360.0 ) {
253 273         358 $True_solar_longitude -= 360.0; # Make it 0..360 degrees
254             }
255              
256 1016         1909 return ( $Solar_distance, $True_solar_longitude );
257             }
258              
259              
260             #
261             #
262             # FUNCTIONAL SEQUENCE for sun_RA_dec
263             #
264             # _GIVEN
265             # day number, $r and $lon (from sunpos)
266             #
267             # _THEN
268             #
269             # compute RA and dec
270             #
271             #
272             # _RETURN
273             #
274             # Sun's Right Ascension (RA), Declination (dec) and distance (r)
275             #
276             #
277             sub sun_RA_dec {
278 1016     1016 0 1331 my ($d) = @_;
279              
280             # Compute Sun's ecliptical coordinates
281 1016         1333 my ( $r, $lon ) = sunpos($d);
282              
283             # Compute ecliptic rectangular coordinates (z=0)
284 1016         1463 my $x = $r * cosd($lon);
285 1016         1321 my $y = $r * sind($lon);
286              
287             # Compute obliquity of ecliptic (inclination of Earth's axis)
288 1016         1332 my $obl_ecl = 23.4393 - 3.563E-7 * $d;
289              
290             # Convert to equatorial rectangular coordinates - x is unchanged
291 1016         1261 my $z = $y * sind($obl_ecl);
292 1016         1327 $y = $y * cosd($obl_ecl);
293              
294             # Convert to spherical coordinates
295 1016         1348 my $RA = atan2d( $y, $x );
296 1016         1633 my $dec = atan2d( $z, sqrt( $x * $x + $y * $y ) );
297              
298 1016         1929 return ( $RA, $dec, $r );
299              
300             } # sun_RA_dec
301              
302              
303             #
304             #
305             # FUNCTIONAL SEQUENCE for days_since_2000_Jan_0
306             #
307             # _GIVEN
308             # year, month, day
309             #
310             # _THEN
311             #
312             # process the year month and day (counted in days)
313             # Day 0.0 is at Jan 1 2000 0.0 UT
314             # Note that ALL divisions here should be INTEGER divisions
315             #
316             # _RETURN
317             #
318             # day number
319             #
320             sub days_since_2000_Jan_0 {
321 10     10   4215 use integer;
  10         140  
  10         56  
322 604     604 0 1000 my ( $year, $month, $day ) = @_;
323              
324 604         1755 my $d =
325             ( 367 * ($year) -
326             int( ( 7 * ( ($year) + ( ( ($month) + 9 ) / 12 ) ) ) / 4 ) +
327             int( ( 275 * ($month) ) / 9 ) + ($day) - 730530 );
328              
329 604         1528 return $d;
330              
331             }
332              
333             sub sind {
334 7118     7118 1 13140 sin( ( $_[0] ) * $DEGRAD );
335             }
336              
337             sub cosd {
338 6102     6102 1 8763 cos( ( $_[0] ) * $DEGRAD );
339             }
340              
341             sub tand {
342 6     6 1 31 tan( ( $_[0] ) * $DEGRAD );
343             }
344              
345             sub atand {
346 6     6 1 16 ( $RADEG * atan( $_[0] ) );
347             }
348              
349             sub asind {
350 6     6 1 16 ( $RADEG * asin( $_[0] ) );
351             }
352              
353             sub acosd {
354 860     860 1 1834 ( $RADEG * acos( $_[0] ) );
355             }
356              
357             sub atan2d {
358 3055     3055 1 6281 ( $RADEG * atan2( $_[0], $_[1] ) );
359             }
360              
361             #
362             #
363             # FUNCTIONAL SEQUENCE for revolution
364             #
365             # _GIVEN
366             # any angle
367             #
368             # _THEN
369             #
370             # reduces any angle to within the first revolution
371             # by subtracting or adding even multiples of 360.0
372             #
373             #
374             # _RETURN
375             #
376             # the value of the input is >= 0.0 and < 360.0
377             #
378              
379             sub revolution {
380 3048     3048 0 3447 my $x = $_[0];
381 3048         5828 return ( $x - 360.0 * floor( $x * $INV360 ) );
382             }
383              
384             #
385             #
386             # FUNCTIONAL SEQUENCE for rev180
387             #
388             # _GIVEN
389             #
390             # any angle
391             #
392             # _THEN
393             #
394             # Reduce input to within +180..+180 degrees
395             #
396             #
397             # _RETURN
398             #
399             # angle that was reduced
400             #
401             sub rev180 {
402 1016     1016 0 1304 my ($x) = @_;
403            
404 1016         2334 return ( $x - 360.0 * floor( $x * $INV360 + 0.5 ) );
405             }
406              
407             sub equal {
408 251     251 1 534 my ($A, $B, $dp) = @_;
409              
410 251         1323 return sprintf("%.${dp}g", $A) eq sprintf("%.${dp}g", $B);
411             }
412              
413             #
414             #
415             # FUNCTIONAL SEQUENCE for convert_hour
416             #
417             # _GIVEN
418             # Hour_rise, Hour_set, Time zone offset, DST setting
419             # hours are in UT
420             #
421             # _THEN
422             #
423             # convert to local time
424             #
425             #
426             # _RETURN
427             #
428             # hour:min rise and set
429             #
430              
431             sub convert_hour {
432 454     454 0 787 my ($hour_rise_ut, $hour_set_ut, $TZ, $isdst) = @_;
433 454         752 return (convert_1_hour($hour_rise_ut, $TZ, $isdst),
434             convert_1_hour($hour_set_ut, $TZ, $isdst));
435             }
436             #
437             #
438             # FUNCTIONAL SEQUENCE for convert_1_hour
439             #
440             # _GIVEN
441             # Hour, Time zone offset, DST setting
442             # hours are in UT
443             #
444             # _THEN
445             #
446             # convert to local time
447             #
448             #
449             # _RETURN
450             #
451             # hour:min
452             #
453              
454             sub convert_1_hour {
455 908     908 0 1319 my ($hour_ut, $TZ, $isdst) = @_;
456 908         1134 my $hour_local = $hour_ut + $TZ;
457 908 50       1381 if ($isdst) {
458 0         0 $hour_local ++;
459             }
460              
461             # The hour should be between 0 and 24;
462 908 50       1696 if ($hour_local < 0) {
    100          
463 0         0 $hour_local += 24;
464             }
465             elsif ($hour_local > 24) {
466 20         28 $hour_local -= 24;
467             }
468              
469 908         1244 my $hour = int ($hour_local);
470              
471 908         1714 my $min = floor(($hour_local - $hour) * 60 + 0.5);
472              
473 908 100       1536 if ($min >= 60) {
474 24         33 $min -= 60;
475 24         38 $hour++;
476 24 50       50 $hour -= 24 if $hour >= 24;
477             }
478              
479 908         4260 return sprintf("%02d:%02d", $hour, $min);
480             }
481              
482             sub sun_rise {
483 11     11 1 3746 my ($sun_rise, undef) = sun_rise_sun_set(@_);
484 8         41 return $sun_rise;
485             }
486             sub sun_set {
487 8     8 1 2276 my (undef, $sun_set) = sun_rise_sun_set(@_);
488 8         36 return $sun_set;
489             }
490              
491             sub sun_rise_sun_set {
492 19     19 0 51 my %arg;
493 19 100       47 if (ref($_[0]) eq 'HASH') {
494 11         15 %arg = %{$_[0]};
  11         42  
495             }
496             else {
497 8         24 @arg{ qw/lon lat alt offset/ } = @_;
498             }
499              
500             # This trick aims to fulfill two antagonistic purposes:
501             # -- do not load DateTime if the only function called is "sunrise"
502             # -- load DateTime implicitly if the user calls "sun_rise" or "sun_set". This is to be backward
503             # compatible with 0.92 or earlier, when Astro::Sunrise would load DateTime and thus, allow
504             # the user to remove this line from his script.
505 19 50       58 unless ($INC{DateTime}) {
506 19     3   1160 eval "use DateTime";
  3     3   28  
  3     3   6  
  3     2   73  
  3     1   20  
  3     1   6  
  3     1   35  
  3     1   19  
  3     1   6  
  3     1   36  
  2     1   12  
  2     1   4  
  2         23  
  1         6  
  1         3  
  1         12  
  1         6  
  1         2  
  1         11  
  1         8  
  1         2  
  1         13  
  1         7  
  1         2  
  1         13  
  1         6  
  1         17  
  1         13  
  1         5  
  1         2  
  1         11  
  1         6  
  1         2  
  1         11  
  1         6  
  1         2  
  1         12  
507 19 50       61 croak $@
508             if $@;
509             }
510              
511 19         46 my ($longitude, $latitude) = @arg{ qw/lon lat/ };
512 19 100       53 my $alt = defined($arg{alt} ) ? $arg{alt} : -0.833;
513 19 100       39 my $offset = defined($arg{offset} ) ? int($arg{offset}) : 0 ;
514 19 100       37 my $tz = defined($arg{time_zone}) ? $arg{time_zone} : 'local';
515 19   50     78 $arg{precise} ||= 0;
516 19   50     62 $arg{upper_limb} ||= 0;
517 19   100     64 $arg{polar} ||= 'warn';
518 19 100       118 croak "Longitude parameter (keyword: 'lon') is mandatory"
519             unless defined $longitude;
520 18 100       123 croak "Latitude parameter (keyword: 'lat') is mandatory"
521             unless defined $latitude;
522             croak "Wrong value of the 'polar' argument: should be either 'warn' or 'retval'"
523 17 100 66     131 if $arg{polar} ne 'warn' and $arg{polar} ne 'retval';
524              
525 16         52 my $today = DateTime->today(time_zone => $tz);
526 16         31956 $today->set( hour => 12 );
527 16         7829 $today->add( days => $offset );
528              
529             my( $sun_rise, $sun_set ) = sunrise( { year => $today->year,
530             month => $today->mon,
531             day => $today->mday,
532             lon => $longitude,
533             lat => $latitude,
534             tz => ( $today->offset / 3600 ),
535             #
536             # DST is always 0 because DateTime
537             # currently (v 0.16) adds one to the
538             # offset during DST hours
539             isdst => 0,
540             alt => $alt,
541             precise => $arg{precise},
542             upper_limb => $arg{upper_limb},
543             polar => $arg{polar},
544 16         2544 } );
545 16         117 return ($sun_rise, $sun_set);
546             }
547              
548             sub DEFAULT () { -0.833 }
549             sub CIVIL () { - 6 }
550             sub NAUTICAL () { -12 }
551             sub AMATEUR () { -15 }
552             sub ASTRONOMICAL () { -18 }
553              
554             # Ending a module with whatever, which risks to be zero, is wrong.
555             # Ending a module with 1 is boring. So, let us end it with:
556             1950;
557             # Hint: directed by BW, with GS, WH and EVS
558              
559             __END__
560              
561             =encoding utf8
562              
563             =head1 NAME
564              
565             Astro::Sunrise - Perl extension for computing the sunrise/sunset on a given day
566              
567             =head1 VERSION
568              
569             This documentation refers to C<Astro::Sunrise> version 0.97.
570              
571             =head1 SYNOPSIS
572              
573             # When will the sun rise on YAPC::Europe 2015?
574             use Astro::Sunrise;
575             my ($sunrise, $sunset) = sunrise( { year => 2015, month => 9, day => 2, # YAPC::EU starts on 2nd September 2015
576             lon => -3.6, lat => 37.17, # Granada is 37°10'N, 3°36'W
577             tz => 1, isdst => 1 } ); # This is still summer, therefore DST
578              
579             # When does the sun rise today in Salt Lake City (home to YAPC::NA 2015)?
580             use Astro::Sunrise;
581             use DateTime;
582             $sunrise_today = sun_rise( { lon => -111.88, lat => 40.75 } ); # 40°45'N, 111°53'W
583              
584             # And when does it set tomorrow at Salt Lake City?
585             use Astro::Sunrise;
586             use DateTime;
587             $sunset_tomorrow = sun_set( { lat => 40.75, # 40°45'N,
588             lon => -111.88, # 111°53'W
589             alt => -0.833, # standard value for the sun altitude at sunset
590             offset => 1 } ); # day offset up to tomorrow
591              
592             =head1 DESCRIPTION
593              
594             This module will return the sunrise and sunset for a given day.
595              
596             Months are numbered 1 to 12, in the usual way, not 0 to 11 as in
597             C and in Perl's localtime.
598              
599             Eastern longitude is entered as a positive number
600             Western longitude is entered as a negative number
601             Northern latitude is entered as a positive number
602             Southern latitude is entered as a negative number
603              
604             Please note that, when given as positional parameters, the longitude is specified before the
605             latitude.
606              
607             The time zone is given as the numeric value of the offset from UTC.
608              
609             The C<precise> parameter is set to either 0 or 1.
610             If set to 0 no Iteration will occur.
611             If set to 1 Iteration will occur, which will give a more precise result.
612             Default is 0.
613              
614             There are a number of sun altitudes to chose from. The default is
615             -0.833 because this is what most countries use. Feel free to
616             specify it if you need to. Here is the list of values to specify
617             altitude (ALT) with, including symbolic constants for each.
618              
619             =over
620              
621             =item B<0> degrees
622              
623             Center of Sun's disk touches a mathematical horizon
624              
625             =item B<-0.25> degrees
626              
627             Sun's upper limb touches a mathematical horizon
628              
629             =item B<-0.583> degrees
630              
631             Center of Sun's disk touches the horizon; atmospheric refraction accounted for
632              
633             =item B<-0.833> degrees, DEFAULT
634              
635             Sun's upper limb touches the horizon; atmospheric refraction accounted for
636              
637             =item B<-6> degrees, CIVIL
638              
639             Civil twilight (one can no longer read outside without artificial illumination)
640              
641             =item B<-12> degrees, NAUTICAL
642              
643             Nautical twilight (navigation using a sea horizon no longer possible)
644              
645             =item B<-15> degrees, AMATEUR
646              
647             Amateur astronomical twilight (the sky is dark enough for most astronomical observations)
648              
649             =item B<-18> degrees, ASTRONOMICAL
650              
651             Astronomical twilight (the sky is completely dark)
652              
653             =back
654              
655             =head1 USAGE
656              
657             =head2 B<sunrise>
658              
659             ($sunrise, $sunset) = sunrise( { year => $year, month => $month, day => $day,
660             lon => $longitude, lat => $latitude,
661             tz => $tz_offset, isdst => $is_dst,
662             alt => $altitude, upper_limb => $upper_limb,
663             precise => $precise, polar => $action } );
664              
665             ($sunrise, $sunset) = sunrise(YYYY,MM,DD,longitude,latitude,Time Zone,DST);
666              
667             ($sunrise, $sunset) = sunrise(YYYY,MM,DD,longitude,latitude,Time Zone,DST,ALT);
668              
669             ($sunrise, $sunset) = sunrise(YYYY,MM,DD,longitude,latitude,Time Zone,DST,ALT,precise);
670              
671             Returns the sunrise and sunset times, in HH:MM format.
672              
673             The first form uses a hash reference to pass arguments by name.
674             The other forms are kept for backward compatibility. The arguments are:
675              
676             =over 4
677              
678             =item year, month, day
679              
680             The three elements of the date for which you want to compute the sunrise and sunset.
681             Months are numbered 1 to 12, in the usual way, not 0 to 11 as in C and in Perl's localtime.
682              
683             Mandatory, can be positional (#1, #2 and #3).
684              
685             =item lon, lat
686              
687             The longitude and latitude of the place for which you want to compute the sunrise and sunset.
688             They are given in decimal degrees. For example:
689              
690             lon => -3.6, # 3° 36' W
691             lat => 37.17, # 37° 10' N
692              
693             Eastern longitude is entered as a positive number
694             Western longitude is entered as a negative number
695             Northern latitude is entered as a positive number
696             Southern latitude is entered as a negative number
697              
698             Mandatory, can be positional (#4 and #5).
699              
700             =item tz
701              
702             Time Zone is the offset from GMT
703              
704             Mandatory, can be positional (#6).
705              
706             =item isdst
707              
708             1 if daylight saving time is in effect, 0 if not.
709              
710             Mandatory, can be positional (#7).
711              
712             =item alt
713              
714             Altitude of the sun, in decimal degrees. Usually a negative number,
715             because the sun should be I<under> the mathematical horizon.
716             But if there is a high mountain range sunward (that is, southward if you
717             live in the Northern hemisphere), you may need to enter a positive altitude.
718              
719             This parameter is optional. Its default value is -0.833. It can be positional (#8).
720              
721             =item upper_limb
722              
723             If this parameter set to a true value (usually 1), the algorithm computes
724             the sun apparent radius and takes it into account when computing the sun
725             altitude. This parameter is useful only when the C<alt> parameter is set
726             to C<0> or C<-0.583> degrees. When using C<-0.25> or C<-0.833> degrees,
727             the sun radius is already taken into account. When computing twilights
728             (C<-6> to C<-18>), the sun radius is irrelevant.
729              
730             Since the default value for the C<alt> parameter is -0.833, the
731             default value for C<upper_limb> is 0.
732              
733             This parameter is optional and it can be specified only by keyword.
734              
735             =item polar
736              
737             When dealing with a polar location, there may be dates where there is
738             a polar night (sun never rises) or a polar day. The default behaviour of
739             the module is to emit a warning in these cases ("Sun never rises!!"
740             or "Sun never sets!!"). But some programmers may find this inconvenient.
741             An alternate behaviour is to return special values reflecting the
742             situation.
743              
744             So, if the C<polar> parameter is set to C<'warn'>, the module emits
745             a warning. If the C<polar> parameter is set to C<'retval'>, the
746             module emits no warning, but it returns either C<'day'> or C<'night'>.
747              
748             Example:
749              
750             # Loosely based on Alex Gough's activities: scientist and Perl programmer, who spent a year
751             # in Halley Base in 2006. Let us suppose he arrived there on 15th January 2006.
752             my ($sunrise, $sunset) = sunrise( { year => 2006, month => 1, day => 15,
753             lon => -26.65, lat => -75.58, # Halley Base: 75°35'S 26°39'W
754             tz => 3, polar => 'retval' } );
755             if ($sunrise eq 'day') {
756             say "Alex Gough saw the midnight sun the first day he arrived at Halley Base";
757             }
758             elsif ($sunrise eq 'night') {
759             say "It would be days, maybe weeks, before the sun would rise.";
760             }
761             else {
762             say "Alex saw his first antarctic sunset at $sunset";
763             }
764              
765             This parameter is optional and it can be specified only by keyword.
766              
767             =item precise
768              
769             Choice between a precise algorithm and a simpler algorithm.
770             The default value is 0, that is, the simpler algorithm.
771             Any true value switches to the precise algorithm.
772              
773             The original method only gives an approximate value of the Sun's rise/set times.
774             The error rarely exceeds one or two minutes, but at high latitudes, when the Midnight Sun
775             soon will start or just has ended, the errors may be much larger. If you want higher accuracy,
776             you must then use the precise algorithm. This feature is new as of version 0.7. Here is
777             what I have tried to accomplish with this.
778              
779             a) Compute sunrise or sunset as always, with one exception: to convert LHA from degrees to hours,
780             divide by 15.04107 instead of 15.0 (this accounts for the difference between the solar day
781             and the sidereal day).
782              
783             b) Re-do the computation but compute the Sun's RA and Decl, and also GMST0, for the moment
784             of sunrise or sunset last computed.
785              
786             c) Iterate b) until the computed sunrise or sunset no longer changes significantly.
787             Usually 2 iterations are enough, in rare cases 3 or 4 iterations may be needed.
788              
789             This parameter is optional. It can be positional (#9).
790              
791             =back
792              
793             =head3 I<For Example>
794              
795             ($sunrise, $sunset) = sunrise( 2001, 3, 10, 17.384, 98.625, -5, 0 );
796             ($sunrise, $sunset) = sunrise( 2002, 10, 14, -105.181, 41.324, -7, 1, -18);
797             ($sunrise, $sunset) = sunrise( 2002, 10, 14, -105.181, 41.324, -7, 1, -18, 1);
798              
799             =head2 B<sun_rise>, B<sun_set>
800              
801             $sun_rise = sun_rise( { lon => $longitude, lat => $latitude,
802             alt => $altitude, upper_limb => $bool,
803             offset => $day_offset,
804             precise => $bool_precise, polar => $action } );
805             $sun_set = sun_set ( { lon => $longitude, lat => $latitude,
806             alt => $altitude, upper_limb => $bool,
807             offset => $day_offset,
808             precise => $bool_precise, polar => $action } );
809             $sun_rise = sun_rise( $longitude, $latitude );
810             $sun_rise = sun_rise( $longitude, $latitude, $alt );
811             $sun_rise = sun_rise( $longitude, $latitude, $alt, $day_offset );
812              
813             Returns the sun rise time (resp. the sun set time) for the given location
814             and for today's date (as given by DateTime), plus or minus some offset in days.
815             The first form use all parameters and transmit them by name. The second form
816             uses today's date (from DateTime) and the default altitude. The third
817             form adds specifying a custom altitude. The fourth form allows for specifying
818             an integer day offset from today, either positive or negative.
819              
820             The parameters are the same as the parameters for C<sunrise>. There is an additional
821             parameter, C<offset>, which allows using a date other than today: C<+1> for
822             to-morrow, C<-7> for one week ago, etc.
823              
824             The arguments are:
825              
826             =over 4
827              
828             =item lon, lat
829              
830             The longitude and latitude of the place for which you want to compute the sunrise and sunset.
831             They are given in decimal degrees. For example:
832              
833             lon => -3.6, # 3° 36' W
834             lat => 37.17, # 37° 10' N
835              
836             Eastern longitude is entered as a positive number
837             Western longitude is entered as a negative number
838             Northern latitude is entered as a positive number
839             Southern latitude is entered as a negative number
840              
841             Mandatory, can be positional (#1 and #2).
842              
843             =item alt
844              
845             Altitude of the sun, in decimal degrees. Usually a negative number,
846             because the sun should be I<under> the mathematical horizon.
847             But if there is a high mountain range sunward (that is, southward if you
848             live in the Northern hemisphere), you may need to enter a positive altitude.
849              
850             This parameter is optional. Its default value is -0.833. It can be positional (#3).
851              
852             =item offset
853              
854             By default, C<sun_rise> and C<sun_set> use the current day. If you need another
855             day, you give an offset relative to the current day. For example, C<+7> means
856             next week, while C<-365> means last year.
857              
858             This parameter has nothing to do with timezones.
859              
860             Optional, 0 by default, can be positional (#4).
861              
862             =item time_zone
863              
864             Time Zone is the Olson name for a timezone. By default, the functions
865             C<sun_rise> and C<sun_set> will try to use the C<local> timezone.
866              
867             This parameter is optional and it can be specified only by keyword.
868              
869             =item upper_limb
870              
871             If this parameter set to a true value (usually 1), the algorithm computes
872             the sun apparent radius and takes it into account when computing the sun
873             altitude. This parameter is useful only when the C<alt> parameter is set
874             to C<0> or C<-0.583> degrees. When using C<-0.25> or C<-0.833> degrees,
875             the sun radius is already taken into account. When computing twilights
876             (C<-6> to C<-18>), the sun radius is irrelevant.
877              
878             Since the default value for the C<alt> parameter is -0.833, the
879             default value for C<upper_limb> is 0.
880              
881             This parameter is optional and it can be specified only by keyword.
882              
883             =item polar
884              
885             When dealing with a polar location, there may be dates where there is
886             a polar night (sun never rises) or a polar day. The default behaviour of
887             the module is to emit a warning in these cases ("Sun never rises!!"
888             or "Sun never sets!!"). But some programmers may find this inconvenient.
889             An alternate behaviour is to return special values reflecting the
890             situation.
891              
892             So, if the C<polar> parameter is set to C<'warn'>, the module emits
893             a warning. If the C<polar> parameter is set to C<'retval'>, the
894             module emits no warning, but it returns either C<'day'> or C<'night'>.
895              
896             This parameter is optional and it can be specified only by keyword.
897              
898             =item precise
899              
900             Choice between a precise algorithm and a simpler algorithm.
901             The default value is 0, that is, the simpler algorithm.
902             Any true value switches to the precise algorithm.
903              
904             For more documentation, see the corresponding parameter
905             for the C<sunrise> function.
906              
907             This parameter is optional and it can be specified only by keyword.
908              
909             =back
910              
911             =head3 For Example
912              
913             $sunrise = sun_rise( -105.181, 41.324 );
914             $sunrise = sun_rise( -105.181, 41.324, -15 );
915             $sunrise = sun_rise( -105.181, 41.324, -12, +3 );
916             $sunrise = sun_rise( -105.181, 41.324, undef, -12);
917              
918             =head2 Trigonometric functions using degrees
919              
920             Since the module use trigonometry with degrees, the corresponding functions
921             are available to the module user, free of charge. Just mention the
922             tag C<:trig> in the C<use> statement. These functions are:
923              
924             =over 4
925              
926             =item sind, cosd, tand
927              
928             The direct functions, that is, sine, cosine and tangent functions, respectively.
929             Each one receives one parameter, in degrees, and returns the trigonometric value.
930              
931             =item asind, acosd, atand
932              
933             The reverse functions, that is, arc-sine, arc-cosine, and arc-tangent.
934             Each one receives one parameter, the trigonometric value, and returns the corresponding
935             angle in degrees.
936              
937             =item atan2d
938              
939             Arc-tangent. This function receives two parameters: the numerator and the denominator
940             of a fraction equal to the tangent. Use this function instead of C<atand> when you
941             are not sure the denominator is not zero. E.g.:
942              
943             use Astro::Sunrise qw(:trig);
944             say atan2d(1, 2) # prints 26,5
945             say atan2d(1, 0) # prints 90, without triggering a "division by zero" error
946              
947             =item equal
948              
949             Not really a trigonometrical function, but still useful at some times. This function
950             receives two floating values and an integer value. It compares the floating numbers,
951             and returns "1" if their most significant digits are equal. The integer value
952             specifies how many digits are kept. E.g.
953              
954             say equal(22/7, 355/113, 3) # prints 1, because : 22/7 = 3.14285715286 rounded to 3.14
955             # 355/113 = 3.14159292035 rounded to 3.14
956             say equal(22/7, 355/113, 4) # prints 0, because : 22/7 = 3.14285715286 rounded to 3.143
957             # 355/113 = 3.14159292035 rounded to 3.142
958              
959             =back
960              
961             =head1 EXPORTS
962              
963             By default, the functions C<sunrise>, C<sun_rise> and C<sun_set> are exported.
964              
965             The constants C<DEFAULT>, C<CIVIL>, C<NAUTICAL>, C<AMATEUR> and C<ASTRONOMICAL> are
966             exported on request with the tag C<:constants>.
967              
968             The functions C<sind>, C<cosd>, C<tand>, C<asind>, C<acosd>, C<atand>, C<atan2d> and C<equal>
969             exported on request with the tag C<:trig>.
970              
971             =head1 DEPENDENCIES
972              
973             This module requires only core modules: L<POSIX>, L<Math::Trig> and L<Carp>.
974              
975             If you use the C<sun_rise> and C<sun_set> functions, you will need also L<DateTime>.
976              
977             =head1 AUTHOR
978              
979             Ron Hill
980             rkhill@firstlight.net
981              
982             Co-maintainer: Jean Forget (JFORGET at cpan dot org)
983              
984             =head1 SPECIAL THANKS
985              
986             Robert Creager [Astro-Sunrise@LogicalChaos.org]
987             for providing help with converting Paul's C code to Perl,
988             for providing code for sun_rise, sun_set subs.
989             Also adding options for different altitudes.
990              
991             Joshua Hoblitt [jhoblitt@ifa.hawaii.edu]
992             for providing the patch to convert to DateTime.
993              
994             Chris Phillips for providing patch for conversion to
995             local time.
996              
997             Brian D Foy for providing patch for constants :)
998              
999             Gabor Szabo for pointing POD mistakes.
1000              
1001             People at L<https://geocoder.opencagedata.com/> for noticing an endless
1002             loop condition and for fixing it.
1003              
1004             =head1 CREDITS
1005              
1006             =over 4
1007              
1008             =item Paul Schlyter, Stockholm, Sweden
1009              
1010             for his excellent web page on the subject.
1011              
1012             =item Rich Bowen (rbowen@rbowen.com)
1013              
1014             for suggestions.
1015              
1016             =item Adrian Blockley [adrian.blockley@environ.wa.gov.au]
1017              
1018             for finding a bug in the conversion to local time.
1019              
1020             =item Slaven Rezić
1021              
1022             for finding and fixing a bug with DST.
1023              
1024             =back
1025              
1026             Lightly verified against L<http://aa.usno.navy.mil/data/docs/RS_OneYear.html>
1027              
1028             In addition, checked to be compatible with a C implementation of Paul Schlyter's algorithm.
1029              
1030             =head1 COPYRIGHT and LICENSE
1031              
1032             =head2 Perl Module
1033              
1034             This program is distributed under the same terms as Perl 5.16.3:
1035             GNU Public License version 1 or later and Perl Artistic License
1036              
1037             You can find the text of the licenses in the F<LICENSE> file or at
1038             L<http://www.perlfoundation.org/artistic_license_1_0>
1039             and L<https://www.gnu.org/licenses/gpl-1.0.html>.
1040              
1041             Here is the summary of GPL:
1042              
1043             This program is free software; you can redistribute it and/or modify
1044             it under the terms of the GNU General Public License as published by
1045             the Free Software Foundation; either version 1, or (at your option)
1046             any later version.
1047              
1048             This program is distributed in the hope that it will be useful,
1049             but WITHOUT ANY WARRANTY; without even the implied warranty of
1050             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1051             GNU General Public License for more details.
1052              
1053             You should have received a copy of the GNU General Public License
1054             along with this program; if not, write to the Free Software Foundation,
1055             Inc., L<https://www.fsf.org/>.
1056              
1057             =head2 Original C program
1058              
1059             Here is the copyright information provided by Paul Schlyter:
1060              
1061             Written as DAYLEN.C, 1989-08-16
1062              
1063             Modified to SUNRISET.C, 1992-12-01
1064              
1065             (c) Paul Schlyter, 1989, 1992
1066              
1067             Released to the public domain by Paul Schlyter, December 1992
1068              
1069             Permission is hereby granted, free of charge, to any person obtaining a
1070             copy of this software and associated documentation files (the "Software"),
1071             to deal in the Software without restriction, including without limitation
1072             the rights to use, copy, modify, merge, publish, distribute, sublicense,
1073             and/or sell copies of the Software, and to permit persons to whom the
1074             Software is furnished to do so, subject to the following conditions:
1075              
1076             The above copyright notice and this permission notice shall be included
1077             in all copies or substantial portions of the Software.
1078              
1079             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1080             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1081             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1082             THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
1083             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
1084             OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1085             THE SOFTWARE.
1086              
1087             =head1 BUGS
1088              
1089             =head1 SEE ALSO
1090              
1091             perl(1).
1092              
1093             L<DateTime::Event::Sunrise>
1094              
1095             L<DateTime::Event::Jewish::Sunrise>
1096              
1097             The text F<doc/astronomical-notes.pod> (or its original French version
1098             F<doc/notes-astronomiques>) in this distribution.
1099              
1100             =cut