File Coverage

blib/lib/Astro/Sunrise.pm
Criterion Covered Total %
statement 237 306 77.4
branch 74 94 78.7
condition 33 40 82.5
subroutine 42 45 93.3
pod 11 21 52.3
total 397 506 78.4


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, 2019, 2021 Ron Hill and Jean Forget
5             #
6             # See the license in the embedded documentation below.
7             #
8             package Astro::Sunrise;
9              
10 11     11   1041908 use strict;
  11         92  
  11         305  
11 11     11   49 use warnings;
  11         17  
  11         322  
12 11     11   904 use POSIX qw(floor);
  11         13931  
  11         67  
13 11     11   12219 use Math::Trig;
  11         151342  
  11         1431  
14 11     11   102 use Carp;
  11         24  
  11         654  
15 11     11   108 use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $RADEG $DEGRAD );
  11         26  
  11         12793  
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.99';
28             $RADEG = ( 180 / pi );
29             $DEGRAD = ( pi / 180 );
30             my $INV360 = ( 1.0 / 360.0 );
31              
32             sub sun_rise {
33 11     11 1 4024 my ($sun_rise, undef) = sun_rise_sun_set(@_);
34 8         27 return $sun_rise;
35             }
36             sub sun_set {
37 8     8 1 2104 my (undef, $sun_set) = sun_rise_sun_set(@_);
38 8         21 return $sun_set;
39             }
40              
41             sub sun_rise_sun_set {
42 19     19 0 57 my %arg;
43 19 100       46 if (ref($_[0]) eq 'HASH') {
44 11         14 %arg = %{$_[0]};
  11         49  
45             }
46             else {
47 8         20 @arg{ qw/lon lat alt offset/ } = @_;
48             }
49              
50             # This trick aims to fulfill two antagonistic purposes:
51             # -- do not load DateTime if the only function called is "sunrise"
52             # -- load DateTime implicitly if the user calls "sun_rise" or "sun_set". This is to be backward
53             # compatible with 0.92 or earlier, when Astro::Sunrise would load DateTime and thus, allow
54             # the user to remove this line from his script.
55 19 50       49 unless ($INC{DateTime}) {
56 19     3   1103 eval "use DateTime";
  3     3   36  
  3     3   7  
  3     2   49  
  3     1   18  
  3     1   6  
  3     1   35  
  3     1   17  
  3     1   6  
  3     1   34  
  2     1   11  
  2     1   26  
  2         26  
  1         5  
  1         2  
  1         11  
  1         6  
  1         18  
  1         15  
  1         5  
  1         2  
  1         10  
  1         6  
  1         2  
  1         11  
  1         6  
  1         2  
  1         10  
  1         6  
  1         2  
  1         11  
  1         6  
  1         2  
  1         22  
  1         5  
  1         2  
  1         11  
57 19 50       55 croak $@
58             if $@;
59             }
60              
61 19         47 my ($longitude, $latitude) = @arg{ qw/lon lat/ };
62 19 100       43 my $alt = defined($arg{alt} ) ? $arg{alt} : -0.833;
63 19 100       40 my $offset = defined($arg{offset} ) ? int($arg{offset}) : 0 ;
64 19 100       49 my $tz = defined($arg{time_zone}) ? $arg{time_zone} : 'local';
65 19   50     73 $arg{precise} ||= 0;
66 19   50     60 $arg{upper_limb} ||= 0;
67 19   100     63 $arg{polar} ||= 'warn';
68 19   50     59 $arg{trace} ||= 0;
69 19 100       131 croak "Longitude parameter (keyword: 'lon') is mandatory"
70             unless defined $longitude;
71 18 100       111 croak "Latitude parameter (keyword: 'lat') is mandatory"
72             unless defined $latitude;
73             croak "Wrong value of the 'polar' argument: should be either 'warn' or 'retval'"
74 17 100 66     130 if $arg{polar} ne 'warn' and $arg{polar} ne 'retval';
75              
76 16         51 my $today = DateTime->today(time_zone => $tz);
77 16         30208 $today->set( hour => 12 );
78 16         6915 $today->add( days => $offset );
79              
80             my( $sun_rise, $sun_set ) = sunrise( { year => $today->year,
81             month => $today->mon,
82             day => $today->mday,
83             lon => $longitude,
84             lat => $latitude,
85             tz => ( $today->offset / 3600 ),
86             #
87             # DST is always 0 because DateTime
88             # currently (v 0.16) adds one to the
89             # offset during DST hours
90             isdst => 0,
91             alt => $alt,
92             precise => $arg{precise},
93             upper_limb => $arg{upper_limb},
94             polar => $arg{polar},
95             trace => $arg{trace},
96 16         2202 } );
97 16         114 return ($sun_rise, $sun_set);
98             }
99              
100             sub sunrise {
101 790     790 1 719797 my %arg;
102 790 100       1966 if (ref($_[0]) eq 'HASH') {
103 672         1262 %arg = %{$_[0]};
  672         3442  
104             }
105             else {
106 118         493 @arg{ qw/year month day lon lat tz isdst alt precise/ } = @_;
107             }
108             my ( $year, $month, $day, $lon, $lat, $TZ, $isdst, $trace)
109 790         2650 = @arg{ qw/year month day lon lat tz isdst trace/ };
110 790 100       1717 my $altit = defined($arg{alt} ) ? $arg{alt} : -0.833;
111 790   100     2915 $arg{precise} ||= 0;
112 790   100     2211 $arg{upper_limb} ||= 0;
113 790   100     2295 $arg{polar} ||= 'warn';
114 790   50     2261 $trace ||= 0;
115 790 100       1363 croak "Year parameter is mandatory"
116             unless defined $year;
117 789 100       1253 croak "Month parameter is mandatory"
118             unless defined $month;
119 788 100       1299 croak "Day parameter is mandatory"
120             unless defined $day;
121 787 100       1362 croak "Longitude parameter (keyword: 'lon') is mandatory"
122             unless defined $lon;
123 786 100       1257 croak "Latitude parameter (keyword: 'lat') is mandatory"
124             unless defined $lat;
125             croak "Wrong value of the 'polar' argument: should be either 'warn' or 'retval'"
126 785 100 100     2326 if $arg{polar} ne 'warn' and $arg{polar} ne 'retval';
127              
128 784 100       1318 if (! $arg{precise}) {
129 602         1072 my $revsub = \&rev180; # normalizing angles around 0 degrees
130 602 50       1072 if ($trace) {
131             printf $trace "\nBasic computation for %04d-%02d-%02d, lon %.3f, lat %.3f, altitude %.3f, upper limb %d\n"
132             , $year, $month, $day
133             , $lon
134             , $lat
135             , $altit
136 0         0 , $arg{upper_limb};
137             }
138 602         1214 my $d = days_since_2000_Jan_0( $year, $month, $day ) + 0.5 - $lon / 360.0;
139 602         1436 my ($h1, $h2) = sun_rise_set($d, $lon, $lat, $altit, 15.0, $arg{upper_limb}, $arg{polar}, $trace, $revsub);
140 602 50 100     5214 if ($h1 eq 'day' or $h1 eq 'night' or $h2 eq 'day' or $h2 eq 'night') {
      66        
      66        
141 150         859 return ($h1, $h2);
142             }
143 452         880 return convert_hour($h1, $h2, $TZ, $isdst);
144             }
145             else {
146 182     516   655 my $revsub = sub { _rev_lon($_[0], $lon) }; # normalizing angles around the local longitude
  516         719  
147 182         247 my $ang_speed = 15.0;
148              
149             # This is the initial start
150 182         324 my $d = days_since_2000_Jan_0($year, $month, $day) - $lon / 360.0;
151              
152 182 50       312 if ($trace) {
153             printf $trace "\nPrecise sunrise computation for %04d-%02d-%02d, lon %.3f, lat %.3f, altitude %.3f, upper limb %d angular speed %.5f\n"
154             , $year, $month, $day
155             , $lon,
156             , $lat,
157             , $altit
158             , $arg{upper_limb}
159 0         0 , $ang_speed;
160             }
161 182         200 my $h1_lmt = 12; # LMT decimal hours, noon then the successive values of sunrise
162 182         207 my $h1_utc; # UTC decimal hours, noon LMT then the successive values of sunrise
163 182         310 for my $counter (1..9) {
164             # 9 is a failsafe precaution against a possibly runaway loop
165             # but hopefully, we will leave the loop long before, with "last"
166 269         290 my $h2_utc;
167 269         608 ($h2_utc, undef) = sun_rise_set($d + $h1_lmt / 24, $lon, $lat, $altit, $ang_speed, $arg{upper_limb}, $arg{polar}, $trace, $revsub);
168 269 100 100     999 if ($h2_utc eq 'day' or $h2_utc eq 'night') {
169 150         184 $h1_utc = $h2_utc;
170 150         199 last;
171             }
172 119         169 $h1_utc = $h1_lmt - $lon / 15;
173 119 100       165 if (equal($h1_utc, $h2_utc, 5)) {
174             # equal within 1e-5 hour, a little less than a second
175 32         42 $h1_utc = $h2_utc;
176 32         59 last;
177             }
178 87         133 $h1_utc = $h2_utc;
179 87         120 $h1_lmt = $h1_utc + $lon / 15;
180             }
181              
182 182 50       261 if ($trace) {
183             printf $trace "\nPrecise sunset computation for %04d-%02d-%02d, lon %.3f, lat %.3f, altitude %.3f, upper limb %d angular speed %.5f\n"
184             , $year, $month, $day
185             , $lon,
186             , $lat,
187             , $altit
188             , $arg{upper_limb}
189 0         0 , $ang_speed;
190             }
191 182         221 my $h3_lmt = 12; # LMT decimal hours, noon then the successive values of sunset
192 182         217 my $h3_utc; # UTC decimal hours, noon LMT then the successive values of sunset
193 182         284 for my $counter (1..9) {
194             # 9 is a failsafe precaution against a possibly runaway loop
195             # but hopefully, we will leave the loop long before, with "last"
196 247         254 my $h4_utc;
197 247         505 (undef, $h4_utc) = sun_rise_set($d + $h3_lmt / 24, $lon, $lat, $altit, $ang_speed, $arg{upper_limb}, $arg{polar}, $trace, $revsub);
198 247 100 100     970 if ($h4_utc eq 'day' or $h4_utc eq 'night') {
199 150         159 $h3_utc = $h4_utc;
200 150         180 last;
201             }
202 97         414 $h3_utc = $h3_lmt - $lon / 15;
203 97 100       123 if (equal($h3_utc, $h4_utc, 5)) {
204             # equal within 1e-5 hour, a little less than a second
205 32         40 $h3_utc = $h4_utc;
206 32         50 last;
207             }
208 65         94 $h3_utc = $h4_utc;
209 65         91 $h3_lmt = $h3_utc + $lon / 15;
210             }
211              
212 182         302 return convert_hour($h1_utc, $h3_utc, $TZ, $isdst);
213              
214             }
215             }
216             #######################################################################################
217             # end sunrise
218             ###################################################################################
219              
220             #
221             #
222             # FUNCTIONAL SEQUENCE for days_since_2000_Jan_0
223             #
224             # _GIVEN
225             # year, month, day
226             #
227             # _THEN
228             #
229             # process the year month and day (counted in days)
230             # Day 0.0 is at Jan 1 2000 0.0 UT
231             # Note that ALL divisions here should be INTEGER divisions
232             #
233             # _RETURN
234             #
235             # day number
236             #
237             sub days_since_2000_Jan_0 {
238 11     11   5558 use integer;
  11         138  
  11         57  
239 784     784 0 1294 my ( $year, $month, $day ) = @_;
240              
241 784         2366 my $d = 367 * $year
242             - int( ( 7 * ( $year + ( ($month + 9) / 12 ) ) ) / 4 )
243             + int( (275 * $month) / 9 )
244             + $day - 730530;
245              
246 784         1850 return $d;
247              
248             }
249              
250             #
251             #
252             # FUNCTIONAL SEQUENCE for convert_hour
253             #
254             # _GIVEN
255             # Hour_rise, Hour_set, Time zone offset, DST setting
256             # hours are in UT
257             #
258             # _THEN
259             #
260             # convert to local time
261             #
262             #
263             # _RETURN
264             #
265             # hour:min rise and set
266             #
267              
268             sub convert_hour {
269 634     634 0 990 my ($hour_rise_ut, $hour_set_ut, $TZ, $isdst) = @_;
270 634         939 return (convert_1_hour($hour_rise_ut, $TZ, $isdst),
271             convert_1_hour($hour_set_ut, $TZ, $isdst));
272             }
273             #
274             #
275             # FUNCTIONAL SEQUENCE for convert_1_hour
276             #
277             # _GIVEN
278             # Hour, Time zone offset, DST setting
279             # hours are in UT
280             #
281             # _THEN
282             #
283             # convert to local time
284             #
285             #
286             # _RETURN
287             #
288             # hour:min
289             #
290              
291             sub convert_1_hour {
292 1268     1268 0 1761 my ($hour_ut, $TZ, $isdst) = @_;
293              
294 1268 100 100     4865 if ($hour_ut eq 'day' or $hour_ut eq 'night') {
295 300         1213 return $hour_ut;
296             }
297              
298 968         1284 my $hour_local = $hour_ut + $TZ;
299 968 50       1321 if ($isdst) {
300 0         0 $hour_local ++;
301             }
302              
303             # The hour should be between 0 and 24;
304 968 100       1781 if ($hour_local < 0) {
    100          
305 2         3 $hour_local += 24;
306             }
307             elsif ($hour_local > 24) {
308 20         24 $hour_local -= 24;
309             }
310              
311 968         1185 my $hour = int ($hour_local);
312              
313 968         1776 my $min = floor(($hour_local - $hour) * 60 + 0.5);
314              
315 968 100       1624 if ($min >= 60) {
316 7         12 $min -= 60;
317 7         13 $hour++;
318 7 50       15 $hour -= 24 if $hour >= 24;
319             }
320              
321 968         4422 return sprintf("%02d:%02d", $hour, $min);
322             }
323              
324              
325             sub sun_rise_set {
326 1118     1118 0 2440 my ($d, $lon, $lat, $altit, $ang_spd, $upper_limb, $polar, $trace, $revsub) = @_;
327              
328 1118 50       1788 if ($trace) {
329 0         0 printf $trace "\n";
330             }
331              
332             # Compute local sidereal time of this moment
333 1118         1659 my $gmst0 = GMST0($d);
334 1118         1692 my $sidtime = revolution( $gmst0 + 180.0 );
335              
336             # Compute Sun's RA + Decl + distance at this moment
337 1118         1881 my ( $sRA, $sdec, $sr ) = sun_RA_dec($d, $lon, $trace);
338              
339             # Compute time when Sun is at south - in hours (LMT then UTC)
340 1118         2024 my $tsouth_lmt = 12.0 - $revsub->( $sidtime - $sRA ) / 15;
341 1118         1728 my $tsouth = $tsouth_lmt - $lon / 15;
342              
343 1118 50       1715 if ($trace) {
344 0         0 printf $trace "For day $d (%s), GMST0 $gmst0 %s %s\n", _fmt_hr(24 * ($d - int($d)), $lon, 0), _fmt_angle($gmst0 ), _fmt_dur($gmst0 / 15);
345 0         0 printf $trace "For day $d (%s), sidereal time $sidtime, %s %s\n", _fmt_hr(24 * ($d - int($d)), $lon, 0), _fmt_angle($sidtime), _fmt_dur($sidtime / 15);
346 0         0 printf $trace "For day $d (%s), right asc $sRA %s %s\n", _fmt_hr(24 * ($d - int($d)), $lon, 0), _fmt_angle($sRA ), _fmt_dur($sRA / 15);
347 0         0 printf $trace "For day $d (%s), declination $sdec %s %s\n", _fmt_hr(24 * ($d - int($d)), $lon, 0), _fmt_angle($sdec ), _fmt_dur($sdec / 15);
348 0         0 printf $trace "For day $d (%s), solar noon at $tsouth (%s)\n", _fmt_hr(24 * ($d - int($d)), $lon, 0), _fmt_hr($tsouth, $lon, 0);
349             }
350              
351 1118 100       1701 if ($upper_limb) {
352             # Compute the Sun's apparent radius, degrees
353 485         682 my $sradius = 0.2666 / $sr;
354 485         948 $altit -= $sradius;
355             }
356              
357             # Compute the diurnal arc that the Sun traverses to reach
358             # the specified altitude altit:
359 1118         1459 my $cost = ( sind($altit) - sind($lat) * sind($sdec) )
360             / ( cosd($lat) * cosd($sdec) );
361              
362 1118 50       1976 if ($trace) {
363 0         0 print $trace "altit = $altit, sind(altit) = ", sind($altit), ", lat = $lat, sind(lat) = ", sind($lat), "\n";
364 0         0 print $trace "sdec = $sdec, sind(sdec) = ", sind($sdec), ", lat = $lat, cosd(lat) = ", cosd($lat), "\n";
365 0         0 print $trace "sdec = $sdec, cosd(sdec) = ", cosd($sdec), ", cost = $cost\n";
366             }
367              
368 1118         1240 my $t;
369 1118 100       2304 if ( $cost >= 1.0 ) {
    100          
370 142 100       258 if ($polar eq 'retval') {
371 138         360 return ('night', 'night');
372             }
373 4         595 carp "Sun never rises!!\n";
374 4         228 $t = 0.0; # Sun always below altit
375             }
376             elsif ( $cost <= -1.0 ) {
377 320 100       638 if ($polar eq 'retval') {
378 312         858 return ('day', 'day');
379             }
380 8         944 carp "Sun never sets!!\n";
381 8         437 $t = 12.0; # Sun always above altit
382             }
383             else {
384 656         903 my $arc = acosd($cost); # The diurnal arc
385 656         3548 $t = $arc / $ang_spd; # Time to traverse the diurnal arc, hours
386 656 50       1038 if ($trace) {
387 0         0 printf $trace "Diurnal arc $arc -> $t hours (%s)\n", _fmt_dur($t);
388             }
389             }
390              
391             # Store rise and set times - in hours UT
392              
393 668         838 my $hour_rise_ut = $tsouth - $t;
394 668         837 my $hour_set_ut = $tsouth + $t;
395 668 50       925 if ($trace) {
396 0         0 printf $trace "For day $d (%s), sunrise at $hour_rise_ut (%s)\n", _fmt_hr(24 * ($d - int($d)), $lon),
397             _fmt_hr($hour_rise_ut, $lon);
398 0         0 printf $trace "For day $d (%s), sunset at $hour_set_ut (%s)\n", _fmt_hr(24 * ($d - int($d)), $lon),
399             _fmt_hr($hour_set_ut , $lon);
400             }
401 668         1292 return($hour_rise_ut, $hour_set_ut);
402             }
403              
404             #########################################################################################################
405             #
406             #
407             # FUNCTIONAL SEQUENCE for GMST0
408             #
409             # _GIVEN
410             # Day number
411             #
412             # _THEN
413             #
414             # computes GMST0, the Greenwich Mean Sidereal Time
415             # at 0h UT (i.e. the sidereal time at the Greenwich meridian at
416             # 0h UT). GMST is then the sidereal time at Greenwich at any
417             # time of the day..
418             #
419             #
420             # _RETURN
421             #
422             # Sidtime
423             #
424             sub GMST0 {
425 1118     1118 0 1515 my ($d) = @_;
426              
427 1118         1992 my $sidtim0 = revolution( ( 180.0 + 356.0470 + 282.9404 )
428             + ( 0.9856002585 + 4.70935E-5 ) * $d );
429 1118         1519 return $sidtim0;
430              
431             }
432              
433             #
434             #
435             # FUNCTIONAL SEQUENCE for sun_RA_dec
436             #
437             # _GIVEN
438             # day number, $r and $lon (from sunpos)
439             #
440             # _THEN
441             #
442             # compute RA and dec
443             #
444             #
445             # _RETURN
446             #
447             # Sun's Right Ascension (RA), Declination (dec) and distance (r)
448             #
449             #
450             sub sun_RA_dec {
451 1118     1118 0 1695 my ($d, $lon_noon, $trace) = @_;
452              
453             # Compute Sun's ecliptical coordinates
454 1118         1655 my ( $r, $lon ) = sunpos($d);
455 1118 50       1774 if ($trace) {
456 0         0 printf $trace "For day $d (%s), solar noon at ecliptic longitude $lon %s\n", _fmt_hr(24 * ($d - int($d)), $lon_noon), _fmt_angle($lon);
457             }
458              
459             # Compute ecliptic rectangular coordinates (z=0)
460 1118         1508 my $x = $r * cosd($lon);
461 1118         1467 my $y = $r * sind($lon);
462              
463             # Compute obliquity of ecliptic (inclination of Earth's axis)
464 1118         1489 my $obl_ecl = 23.4393 - 3.563E-7 * $d;
465              
466             # Convert to equatorial rectangular coordinates - x is unchanged
467 1118         1434 my $z = $y * sind($obl_ecl);
468 1118         1433 $y = $y * cosd($obl_ecl);
469              
470             # Convert to spherical coordinates
471 1118         1537 my $RA = atan2d( $y, $x );
472 1118         1818 my $dec = atan2d( $z, sqrt( $x * $x + $y * $y ) );
473              
474 1118         2127 return ( $RA, $dec, $r );
475              
476             } # sun_RA_dec
477              
478              
479             #
480             #
481             # FUNCTIONAL SEQUENCE for sunpos
482             #
483             # _GIVEN
484             # day number
485             #
486             # _THEN
487             #
488             # Computes the Sun's ecliptic longitude and distance
489             # at an instant given in d, number of days since
490             # 2000 Jan 0.0.
491             #
492             #
493             # _RETURN
494             #
495             # ecliptic longitude and distance
496             # ie. $True_solar_longitude, $Solar_distance
497             #
498             sub sunpos {
499 1118     1118 0 1487 my ($d) = @_;
500              
501             # Mean anomaly of the Sun
502             # Mean longitude of perihelion
503             # Note: Sun's mean longitude = M + w
504             # Eccentricity of Earth's orbit
505             # Eccentric anomaly
506             # x, y coordinates in orbit
507             # True anomaly
508              
509             # Compute mean elements
510 1118         1690 my $Mean_anomaly_of_sun = revolution( 356.0470 + 0.9856002585 * $d );
511 1118         1534 my $Mean_longitude_of_perihelion = 282.9404 + 4.70935E-5 * $d;
512 1118         1345 my $Eccentricity_of_Earth_orbit = 0.016709 - 1.151E-9 * $d;
513              
514             # Compute true longitude and radius vector
515 1118         1781 my $Eccentric_anomaly = $Mean_anomaly_of_sun
516             + $Eccentricity_of_Earth_orbit * $RADEG
517             * sind($Mean_anomaly_of_sun)
518             * ( 1.0 + $Eccentricity_of_Earth_orbit * cosd($Mean_anomaly_of_sun) );
519              
520 1118         1583 my $x = cosd($Eccentric_anomaly) - $Eccentricity_of_Earth_orbit;
521              
522 1118         1672 my $y = sqrt( 1.0 - $Eccentricity_of_Earth_orbit * $Eccentricity_of_Earth_orbit )
523             * sind($Eccentric_anomaly);
524              
525 1118         1483 my $Solar_distance = sqrt( $x * $x + $y * $y ); # Solar distance
526 1118         1562 my $True_anomaly = atan2d( $y, $x ); # True anomaly
527              
528 1118         1397 my $True_solar_longitude = $True_anomaly + $Mean_longitude_of_perihelion; # True solar longitude
529              
530 1118 100       2074 if ( $True_solar_longitude >= 360.0 ) {
531 447         555 $True_solar_longitude -= 360.0; # Make it 0..360 degrees
532             }
533              
534 1118         2080 return ( $Solar_distance, $True_solar_longitude );
535             }
536              
537              
538              
539             sub sind {
540 7832     7832 1 15193 sin( ( $_[0] ) * $DEGRAD );
541             }
542              
543             sub cosd {
544 6714     6714 1 9791 cos( ( $_[0] ) * $DEGRAD );
545             }
546              
547             sub tand {
548 6     6 1 41 tan( ( $_[0] ) * $DEGRAD );
549             }
550              
551             sub atand {
552 6     6 1 20 ( $RADEG * atan( $_[0] ) );
553             }
554              
555             sub asind {
556 6     6 1 17 ( $RADEG * asin( $_[0] ) );
557             }
558              
559             sub acosd {
560 662     662 1 1688 ( $RADEG * acos( $_[0] ) );
561             }
562              
563             sub atan2d {
564 3361     3361 1 7343 ( $RADEG * atan2( $_[0], $_[1] ) );
565             }
566              
567             #
568             #
569             # FUNCTIONAL SEQUENCE for revolution
570             #
571             # _GIVEN
572             # any angle
573             #
574             # _THEN
575             #
576             # reduces any angle to within the first revolution
577             # by subtracting or adding even multiples of 360.0
578             #
579             #
580             # _RETURN
581             #
582             # the value of the input is >= 0.0 and < 360.0
583             #
584              
585             sub revolution {
586 3354     3354 0 3651 my $x = $_[0];
587 3354         6722 return ( $x - 360.0 * floor( $x * $INV360 ) );
588             }
589             #
590             #
591             # FUNCTIONAL SEQUENCE for _rev_lon
592             #
593             # _GIVEN
594             #
595             # two angles in degrees, the variable angle and the reference angle (longitude)
596             #
597             # _THEN
598             #
599             # Reduce input variable angle to within reference-180 .. reference+180 degrees
600             #
601             #
602             # _RETURN
603             #
604             # angle that was reduced
605             #
606             sub _rev_lon {
607 516     516   744 my ($x, $lon) = @_;
608 516         753 return $lon + rev180($x - $lon);
609             }
610              
611             #
612             #
613             # FUNCTIONAL SEQUENCE for rev180
614             #
615             # _GIVEN
616             #
617             # any angle
618             #
619             # _THEN
620             #
621             # Reduce input to within -180..+180 degrees
622             #
623             #
624             # _RETURN
625             #
626             # angle that was reduced
627             #
628             sub rev180 {
629 1118     1118 0 1404 my ($x) = @_;
630              
631 1118         2810 return ( $x - 360.0 * floor( $x * $INV360 + 0.5 ) );
632             }
633              
634             sub equal {
635 259     259 1 886 my ($A, $B, $dp) = @_;
636              
637 259         1342 return sprintf("%.${dp}g", $A) eq sprintf("%.${dp}g", $B);
638             }
639              
640             sub _fmt_hr {
641 0     0   0 my ($hr, $lon, $is_lmt) = @_;
642 0         0 my ($lmt, $utc);
643 0 0       0 if ($is_lmt) {
644 0         0 $lmt = $hr;
645 0         0 $utc = $lmt - $lon / 15;
646             }
647             else {
648 0         0 $utc = $hr;
649 0         0 $lmt = $utc + $lon / 15;
650             }
651 0         0 my $hr_h_utc = $utc; my $hr_h_lmt = $lmt;
  0         0  
652 0         0 my $hr_d_utc = $utc / 24; my $hr_d_lmt = $lmt / 24;
  0         0  
653 0         0 my $hr_utc = floor($utc); my $hr_lmt = floor($lmt);
  0         0  
654 0         0 $utc -= $hr_utc; $lmt -= $hr_lmt;
  0         0  
655 0         0 $utc *= 60; $lmt *= 60;
  0         0  
656 0         0 my $mn_utc = floor($utc); my $mn_lmt = floor($lmt);
  0         0  
657 0         0 $utc -= $mn_utc; $lmt -= $mn_lmt;
  0         0  
658 0         0 $utc *= 60; $lmt *= 60;
  0         0  
659 0         0 my $sc_utc = floor($utc); my $sc_lmt = floor($lmt);
  0         0  
660 0         0 return sprintf("UTC: %02d:%02d:%02d %f h %f d, LMT: %02d:%02d:%02d %f h %f d", $hr_utc, $mn_utc, $sc_utc, $hr_h_utc, $hr_d_utc
661             , $hr_lmt, $mn_lmt, $sc_lmt, $hr_h_lmt, $hr_d_lmt);
662             }
663              
664             sub _fmt_dur {
665 0     0   0 my ($dur) = @_;
666 0         0 my $sign = '';
667 0 0       0 if ($dur < 0) {
668 0         0 $sign = '-';
669 0         0 $dur *= -1;
670             }
671 0         0 my $hr = floor($dur);
672 0         0 $dur -= $hr;
673 0         0 $dur *= 60;
674 0         0 my $mn = floor($dur);
675 0         0 $dur -= $mn;
676 0         0 $dur *= 60;
677 0         0 my $sc = floor($dur);
678 0         0 return sprintf("%s%02d h %02d mn %02d s", $sign, $hr, $mn, $sc);
679             }
680              
681             sub _fmt_angle {
682 0     0   0 my ($angle) = @_;
683 0         0 my $sign = '';
684 0 0       0 if ($angle < 0) {
685 0         0 $sign = '-';
686 0         0 $angle *= -1;
687             }
688 0         0 my $hr = floor($angle);
689 0         0 $angle -= $hr;
690 0         0 $angle *= 60;
691 0         0 my $mn = floor($angle);
692 0         0 $angle -= $mn;
693 0         0 $angle *= 60;
694 0         0 my $sc = floor($angle);
695 0         0 return sprintf(q<%s%02d°%02d'%02d">, $sign, $hr, $mn, $sc);
696             }
697              
698             sub DEFAULT () { -0.833 }
699             sub CIVIL () { - 6 }
700             sub NAUTICAL () { -12 }
701             sub AMATEUR () { -15 }
702             sub ASTRONOMICAL () { -18 }
703              
704             # Ending a module with whatever, which risks to be zero, is wrong.
705             # Ending a module with 1 is boring. So, let us end it with:
706             1950;
707             # Hint: directed by BW, with GS, WH and EVS
708              
709             __END__
710              
711             =encoding utf8
712              
713             =head1 NAME
714              
715             Astro::Sunrise - Perl extension for computing the sunrise/sunset on a given day
716              
717             =head1 VERSION
718              
719             This documentation refers to C<Astro::Sunrise> version 0.99.
720              
721             =head1 SYNOPSIS
722              
723             # When did the sun rise on YAPC::Europe 2015?
724             use Astro::Sunrise;
725             my ($sunrise, $sunset) = sunrise( { year => 2015, month => 9, day => 2, # YAPC::EU starts on 2nd September 2015
726             lon => -3.6, lat => 37.17, # Granada is 37°10'N, 3°36'W
727             tz => 1, isdst => 1 } ); # This is still summer, therefore DST
728              
729             # When does the sun rise today in Salt Lake City (home to YAPC::NA 2015)?
730             use Astro::Sunrise;
731             use DateTime;
732             $sunrise_today = sun_rise( { lon => -111.88, lat => 40.75 } ); # 40°45'N, 111°53'W
733              
734             # And when does it set tomorrow at Salt Lake City?
735             use Astro::Sunrise;
736             use DateTime;
737             $sunset_tomorrow = sun_set( { lat => 40.75, # 40°45'N,
738             lon => -111.88, # 111°53'W
739             alt => -0.833, # standard value for the sun altitude at sunset
740             offset => 1 } ); # day offset up to tomorrow
741              
742             =head1 DESCRIPTION
743              
744             This module will return the sunrise and sunset for a given day.
745              
746             Months are numbered 1 to 12, in the usual way, not 0 to 11 as in
747             C and in Perl's localtime.
748              
749             Eastern longitude is entered as a positive number
750             Western longitude is entered as a negative number
751             Northern latitude is entered as a positive number
752             Southern latitude is entered as a negative number
753              
754             Please note that, when given as positional parameters, the longitude is specified before the
755             latitude.
756              
757             The time zone is given as the numeric value of the offset from UTC.
758              
759             The C<precise> parameter is set to either 0 or 1.
760             If set to 0 no Iteration will occur.
761             If set to 1 Iteration will occur, which will give a more precise result.
762             Default is 0.
763              
764             There are a number of sun altitudes to chose from. The default is
765             -0.833 because this is what most countries use. Feel free to
766             specify it if you need to. Here is the list of values to specify
767             altitude (ALT) with, including symbolic constants for each.
768              
769             =over
770              
771             =item B<0> degrees
772              
773             Center of Sun's disk touches a mathematical horizon
774              
775             =item B<-0.25> degrees
776              
777             Sun's upper limb touches a mathematical horizon
778              
779             =item B<-0.583> degrees
780              
781             Center of Sun's disk touches the horizon; atmospheric refraction accounted for
782              
783             =item B<-0.833> degrees, DEFAULT
784              
785             Sun's upper limb touches the horizon; atmospheric refraction accounted for
786              
787             =item B<-6> degrees, CIVIL
788              
789             Civil twilight (one can no longer read outside without artificial illumination)
790              
791             =item B<-12> degrees, NAUTICAL
792              
793             Nautical twilight (navigation using a sea horizon no longer possible)
794              
795             =item B<-15> degrees, AMATEUR
796              
797             Amateur astronomical twilight (the sky is dark enough for most astronomical observations)
798              
799             =item B<-18> degrees, ASTRONOMICAL
800              
801             Astronomical twilight (the sky is completely dark)
802              
803             =back
804              
805             =head1 USAGE
806              
807             =head2 B<sunrise>
808              
809             ($sunrise, $sunset) = sunrise( { year => $year, month => $month,
810             day => $day,
811             lon => $longitude, lat => $latitude,
812             tz => $tz_offset, isdst => $is_dst,
813             alt => $altitude, upper_limb => $upper_limb,
814             precise => $precise, polar => $action,
815             trace => $fh } );
816              
817             ($sunrise, $sunset) = sunrise(YYYY,MM,DD,longitude,latitude,Time Zone,DST);
818              
819             ($sunrise, $sunset) = sunrise(YYYY,MM,DD,longitude,latitude,Time Zone,DST,ALT);
820              
821             ($sunrise, $sunset) = sunrise(YYYY,MM,DD,longitude,latitude,Time Zone,DST,ALT,precise);
822              
823             Returns the sunrise and sunset times, in HH:MM format.
824              
825             The first form uses a hash reference to pass arguments by name.
826             The other forms are kept for backward compatibility. The arguments are:
827              
828             =over 4
829              
830             =item year, month, day
831              
832             The three elements of the date for which you want to compute the sunrise and sunset.
833             Months are numbered 1 to 12, in the usual way, not 0 to 11 as in C and in Perl's localtime.
834              
835             Mandatory, can be positional (#1, #2 and #3).
836              
837             =item lon, lat
838              
839             The longitude and latitude of the place for which you want to compute the sunrise and sunset.
840             They are given in decimal degrees. For example:
841              
842             lon => -3.6, # 3° 36' W
843             lat => 37.17, # 37° 10' N
844              
845             Eastern longitude is entered as a positive number
846             Western longitude is entered as a negative number
847             Northern latitude is entered as a positive number
848             Southern latitude is entered as a negative number
849              
850             Mandatory, can be positional (#4 and #5).
851              
852             =item tz
853              
854             Time Zone is the offset from GMT
855              
856             Mandatory, can be positional (#6).
857              
858             =item isdst
859              
860             1 if daylight saving time is in effect, 0 if not.
861              
862             Mandatory, can be positional (#7).
863              
864             =item alt
865              
866             Altitude of the sun, in decimal degrees. Usually a negative number,
867             because the sun should be I<under> the mathematical horizon.
868             But if there is a high mountain range sunward (that is, southward if you
869             live in the Northern hemisphere), you may need to enter a positive altitude.
870              
871             This parameter is optional. Its default value is -0.833. It can be positional (#8).
872              
873             =item upper_limb
874              
875             If this parameter set to a true value (usually 1), the algorithm computes
876             the sun apparent radius and takes it into account when computing the sun
877             altitude. This parameter is useful only when the C<alt> parameter is set
878             to C<0> or C<-0.583> degrees. When using C<-0.25> or C<-0.833> degrees,
879             the sun radius is already taken into account. When computing twilights
880             (C<-6> to C<-18>), the sun radius is irrelevant.
881              
882             Since the default value for the C<alt> parameter is -0.833, the
883             default value for C<upper_limb> is 0.
884              
885             This parameter is optional and it can be specified only by keyword.
886              
887             =item polar
888              
889             When dealing with a polar location, there may be dates where there is
890             a polar night (sun never rises) or a polar day. The default behaviour of
891             the module is to emit a warning in these cases ("Sun never rises!!"
892             or "Sun never sets!!"). But some programmers may find this inconvenient.
893             An alternate behaviour is to return special values reflecting the
894             situation.
895              
896             So, if the C<polar> parameter is set to C<'warn'>, the module emits
897             a warning. If the C<polar> parameter is set to C<'retval'>, the
898             module emits no warning, but it returns either C<'day'> or C<'night'>.
899              
900             Example:
901              
902             # Loosely based on Alex Gough's activities: scientist and Perl programmer, who spent a year
903             # in Halley Base in 2006. Let us suppose he arrived there on 15th January 2006.
904             my ($sunrise, $sunset) = sunrise( { year => 2006, month => 1, day => 15,
905             lon => -26.65, lat => -75.58, # Halley Base: 75°35'S 26°39'W
906             tz => 3, polar => 'retval' } );
907             if ($sunrise eq 'day') {
908             say "Alex Gough saw the midnight sun the first day he arrived at Halley Base";
909             }
910             elsif ($sunrise eq 'night') {
911             say "It would be days, maybe weeks, before the sun would rise.";
912             }
913             else {
914             say "Alex saw his first antarctic sunset at $sunset";
915             }
916              
917             This parameter is optional and it can be specified only by keyword.
918              
919             =item trace
920              
921             This parameter should either be a false value or a filehandle opened for output.
922             In the latter case, a few messages are printed to the filehandle, which allows
923             the programmer to see step by step how the sunrise and the sunset are computed.
924              
925             Used for analysis and debugging purposes. You need to read the text
926             F<doc/astronomical-notes.pod> to understand what the traced values
927             represent.
928              
929             This parameter is optional and it can be specified only by keyword.
930              
931             =item precise
932              
933             Choice between a precise algorithm and a simpler algorithm.
934             The default value is 0, that is, the simpler algorithm.
935             Any true value switches to the precise algorithm.
936              
937             The original method only gives an approximate value of the Sun's rise/set times.
938             The error rarely exceeds one or two minutes, but at high latitudes, when the Midnight Sun
939             soon will start or just has ended, the errors may be much larger. If you want higher accuracy,
940             you must then use the precise algorithm. This feature is new as of version 0.7. Here is
941             what I have tried to accomplish with this.
942              
943             a) Compute sunrise or sunset as always, with one exception: to convert LHA from degrees to hours,
944             divide by 15.04107 instead of 15.0 (this accounts for the difference between the solar day
945             and the sidereal day).
946              
947             b) Re-do the computation but compute the Sun's RA and Decl, and also GMST0, for the moment
948             of sunrise or sunset last computed.
949              
950             c) Iterate b) until the computed sunrise or sunset no longer changes significantly.
951             Usually 2 iterations are enough, in rare cases 3 or 4 iterations may be needed.
952              
953             This parameter is optional. It can be positional (#9).
954              
955             =back
956              
957             =head3 I<For Example>
958              
959             ($sunrise, $sunset) = sunrise( 2001, 3, 10, 17.384, 98.625, -5, 0 );
960             ($sunrise, $sunset) = sunrise( 2002, 10, 14, -105.181, 41.324, -7, 1, -18);
961             ($sunrise, $sunset) = sunrise( 2002, 10, 14, -105.181, 41.324, -7, 1, -18, 1);
962              
963             =head2 B<sun_rise>, B<sun_set>
964              
965             $sun_rise = sun_rise( { lon => $longitude, lat => $latitude,
966             alt => $altitude, upper_limb => $bool,
967             offset => $day_offset,
968             precise => $bool_precise, polar => $action } );
969             $sun_set = sun_set ( { lon => $longitude, lat => $latitude,
970             alt => $altitude, upper_limb => $bool,
971             offset => $day_offset,
972             precise => $bool_precise, polar => $action } );
973             $sun_rise = sun_rise( $longitude, $latitude );
974             $sun_rise = sun_rise( $longitude, $latitude, $alt );
975             $sun_rise = sun_rise( $longitude, $latitude, $alt, $day_offset );
976              
977             Returns the sun rise time (resp. the sun set time) for the given location
978             and for today's date (as given by DateTime), plus or minus some offset in days.
979             The first form use all parameters and transmit them by name. The second form
980             uses today's date (from DateTime) and the default altitude. The third
981             form adds specifying a custom altitude. The fourth form allows for specifying
982             an integer day offset from today, either positive or negative.
983              
984             The parameters are the same as the parameters for C<sunrise>. There is an additional
985             parameter, C<offset>, which allows using a date other than today: C<+1> for
986             to-morrow, C<-7> for one week ago, etc.
987              
988             The arguments are:
989              
990             =over 4
991              
992             =item lon, lat
993              
994             The longitude and latitude of the place for which you want to compute the sunrise and sunset.
995             They are given in decimal degrees. For example:
996              
997             lon => -3.6, # 3° 36' W
998             lat => 37.17, # 37° 10' N
999              
1000             Eastern longitude is entered as a positive number
1001             Western longitude is entered as a negative number
1002             Northern latitude is entered as a positive number
1003             Southern latitude is entered as a negative number
1004              
1005             Mandatory, can be positional (#1 and #2).
1006              
1007             =item alt
1008              
1009             Altitude of the sun, in decimal degrees. Usually a negative number,
1010             because the sun should be I<under> the mathematical horizon.
1011             But if there is a high mountain range sunward (that is, southward if you
1012             live in the Northern hemisphere), you may need to enter a positive altitude.
1013              
1014             This parameter is optional. Its default value is -0.833. It can be positional (#3).
1015              
1016             =item offset
1017              
1018             By default, C<sun_rise> and C<sun_set> use the current day. If you need another
1019             day, you give an offset relative to the current day. For example, C<+7> means
1020             next week, while C<-365> means last year.
1021              
1022             This parameter has nothing to do with timezones.
1023              
1024             Optional, 0 by default, can be positional (#4).
1025              
1026             =item time_zone
1027              
1028             Time Zone is the Olson name for a timezone. By default, the functions
1029             C<sun_rise> and C<sun_set> will try to use the C<local> timezone.
1030              
1031             This parameter is optional and it can be specified only by keyword.
1032              
1033             =item upper_limb
1034              
1035             If this parameter set to a true value (usually 1), the algorithm computes
1036             the sun apparent radius and takes it into account when computing the sun
1037             altitude. This parameter is useful only when the C<alt> parameter is set
1038             to C<0> or C<-0.583> degrees. When using C<-0.25> or C<-0.833> degrees,
1039             the sun radius is already taken into account. When computing twilights
1040             (C<-6> to C<-18>), the sun radius is irrelevant.
1041              
1042             Since the default value for the C<alt> parameter is -0.833, the
1043             default value for C<upper_limb> is 0.
1044              
1045             This parameter is optional and it can be specified only by keyword.
1046              
1047             =item polar
1048              
1049             When dealing with a polar location, there may be dates where there is
1050             a polar night (sun never rises) or a polar day. The default behaviour of
1051             the module is to emit a warning in these cases ("Sun never rises!!"
1052             or "Sun never sets!!"). But some programmers may find this inconvenient.
1053             An alternate behaviour is to return special values reflecting the
1054             situation.
1055              
1056             So, if the C<polar> parameter is set to C<'warn'>, the module emits
1057             a warning. If the C<polar> parameter is set to C<'retval'>, the
1058             module emits no warning, but it returns either C<'day'> or C<'night'>.
1059              
1060             This parameter is optional and it can be specified only by keyword.
1061              
1062             =item trace
1063              
1064             This parameter should either be a false value or a filehandle opened for output.
1065             In the latter case, a few messages are printed to the filehandle, which allows
1066             the programmer to see step by step how the sunrise and the sunset are computed.
1067              
1068             Used for analysis and debugging purposes.
1069              
1070             This parameter is optional and it can be specified only by keyword.
1071              
1072             =item precise
1073              
1074             Choice between a precise algorithm and a simpler algorithm.
1075             The default value is 0, that is, the simpler algorithm.
1076             Any true value switches to the precise algorithm.
1077              
1078             For more documentation, see the corresponding parameter
1079             for the C<sunrise> function.
1080              
1081             This parameter is optional and it can be specified only by keyword.
1082              
1083             =back
1084              
1085             =head3 For Example
1086              
1087             $sunrise = sun_rise( -105.181, 41.324 );
1088             $sunrise = sun_rise( -105.181, 41.324, -15 );
1089             $sunrise = sun_rise( -105.181, 41.324, -12, +3 );
1090             $sunrise = sun_rise( -105.181, 41.324, undef, -12);
1091              
1092             =head2 Trigonometric functions using degrees
1093              
1094             Since the module use trigonometry with degrees, the corresponding functions
1095             are available to the module user, free of charge. Just mention the
1096             tag C<:trig> in the C<use> statement. These functions are:
1097              
1098             =over 4
1099              
1100             =item sind, cosd, tand
1101              
1102             The direct functions, that is, sine, cosine and tangent functions, respectively.
1103             Each one receives one parameter, in degrees, and returns the trigonometric value.
1104              
1105             =item asind, acosd, atand
1106              
1107             The reverse functions, that is, arc-sine, arc-cosine, and arc-tangent.
1108             Each one receives one parameter, the trigonometric value, and returns the corresponding
1109             angle in degrees.
1110              
1111             =item atan2d
1112              
1113             Arc-tangent. This function receives two parameters: the numerator and the denominator
1114             of a fraction equal to the tangent. Use this function instead of C<atand> when you
1115             are not sure the denominator is not zero. E.g.:
1116              
1117             use Astro::Sunrise qw(:trig);
1118             say atan2d(1, 2) # prints 26,5
1119             say atan2d(1, 0) # prints 90, without triggering a "division by zero" error
1120              
1121             =item equal
1122              
1123             Not really a trigonometrical function, but still useful at some times. This function
1124             receives two floating values and an integer value. It compares the floating numbers,
1125             and returns "1" if their most significant digits are equal. The integer value
1126             specifies how many digits are kept. E.g.
1127              
1128             say equal(22/7, 355/113, 3) # prints 1, because : 22/7 = 3.14285715286 rounded to 3.14
1129             # 355/113 = 3.14159292035 rounded to 3.14
1130             say equal(22/7, 355/113, 4) # prints 0, because : 22/7 = 3.14285715286 rounded to 3.143
1131             # 355/113 = 3.14159292035 rounded to 3.142
1132              
1133             =back
1134              
1135             =head1 EXPORTS
1136              
1137             By default, the functions C<sunrise>, C<sun_rise> and C<sun_set> are exported.
1138              
1139             The constants C<DEFAULT>, C<CIVIL>, C<NAUTICAL>, C<AMATEUR> and C<ASTRONOMICAL> are
1140             exported on request with the tag C<:constants>.
1141              
1142             The functions C<sind>, C<cosd>, C<tand>, C<asind>, C<acosd>, C<atand>, C<atan2d> and C<equal>
1143             exported on request with the tag C<:trig>.
1144              
1145             =head1 DEPENDENCIES
1146              
1147             This module requires only core modules: L<POSIX>, L<Math::Trig> and L<Carp>.
1148              
1149             If you use the C<sun_rise> and C<sun_set> functions, you will need also L<DateTime>.
1150              
1151             =head1 BUGS AND ISSUES
1152              
1153             Before reporting a bug, please read the text
1154             F<doc/astronomical-notes.pod> because the strange behavior you observed
1155             may be a correct one, or it may be a corner case already known and
1156             already mentioned in the text.
1157              
1158             Nevertheless, patches and (justified) bug reports are welcome.
1159              
1160             See L<https://github.com/jforget/Astro-Sunrise/issues>
1161             and L<https://github.com/jforget/Astro-Sunrise/pulls>.
1162              
1163             =head2 Precise Algorithm
1164              
1165             The explanations for the precise algorithm give a pretty good reason
1166             for using an angular speed of 15.04107 instead of 15. Yet,
1167             computations with 15.04107 do not give results conforming to what the
1168             NOAA website and Stellarium give, while computations with 15 give
1169             conforming results. The implementation of the precise algorithm should
1170             be analysed and checked to find the reason why 15.04107 does not give
1171             the proper results.
1172              
1173             =head2 Kwalitee
1174              
1175             The CPANTS tools do not recognize the LICENSE POD paragraph. But any
1176             human reader will admit that this LICENSE paragraph exists and is valid.
1177              
1178             =head2 Haiku-OS CPAN Tester
1179              
1180             The built-in test F<t/06datetime.t> fails on Haiku-OS because there is
1181             no way to extract the timezone name from the system parameters. This
1182             failure does not affect the core functions of L<Astro::Sunrise>.
1183              
1184             Also reported from a user working on a partially configured FreeBSD machine, see
1185             L<https://github.com/jforget/Astro-Sunrise/issues/16>.
1186              
1187             Hopefully, this will be fixed in the current version.
1188              
1189             =head1 AUTHOR
1190              
1191             Ron Hill
1192             rkhill@firstlight.net
1193              
1194             Co-maintainer: Jean Forget (JFORGET at cpan dot org)
1195              
1196             =head1 SPECIAL THANKS
1197              
1198             Robert Creager [Astro-Sunrise@LogicalChaos.org]
1199             for providing help with converting Paul's C code to Perl,
1200             for providing code for sun_rise, sun_set subs.
1201             Also adding options for different altitudes.
1202              
1203             Joshua Hoblitt [jhoblitt@ifa.hawaii.edu]
1204             for providing the patch to convert to DateTime.
1205              
1206             Chris Phillips for providing patch for conversion to
1207             local time.
1208              
1209             Brian D Foy for providing patch for constants :)
1210              
1211             Gabor Szabo for pointing POD mistakes.
1212              
1213             People at L<https://geocoder.opencagedata.com/> for noticing an endless
1214             loop condition and for fixing it.
1215              
1216             =head1 CREDITS
1217              
1218             =over 4
1219              
1220             =item Paul Schlyter, Stockholm, Sweden
1221              
1222             for his excellent web page on the subject.
1223              
1224             =item Rich Bowen (rbowen@rbowen.com)
1225              
1226             for suggestions.
1227              
1228             =item Adrian Blockley [adrian.blockley@environ.wa.gov.au]
1229              
1230             for finding a bug in the conversion to local time.
1231              
1232             =item Slaven Rezić
1233              
1234             for finding and fixing a bug with DST.
1235              
1236             =back
1237              
1238             Lightly verified against L<http://aa.usno.navy.mil/data/docs/RS_OneYear.html>
1239              
1240             In addition, checked to be compatible with a C implementation of Paul Schlyter's algorithm.
1241              
1242             =head1 COPYRIGHT and LICENSE
1243              
1244             =head2 Perl Module
1245              
1246             Copyright (C) 1999-2003, 2013, 2015, 2017, 2019, 2021 Ron Hill and
1247             Jean Forget, all rights reserved. This program is distributed under
1248             the same terms as Perl 5.16.3: GNU Public License version 1 or later
1249             and Perl Artistic License
1250              
1251             You can find the text of the licenses in the F<LICENSE> file or at
1252             L<https://dev.perl.org/licenses/artistic.html>
1253             and L<https://www.gnu.org/licenses/gpl-1.0.html>.
1254              
1255             Here is the summary of GPL:
1256              
1257             This program is free software; you can redistribute it and/or modify
1258             it under the terms of the GNU General Public License as published by
1259             the Free Software Foundation; either version 1, or (at your option)
1260             any later version.
1261              
1262             This program is distributed in the hope that it will be useful,
1263             but WITHOUT ANY WARRANTY; without even the implied warranty of
1264             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1265             GNU General Public License for more details.
1266              
1267             You should have received a copy of the GNU General Public License
1268             along with this program; if not, write to the Free Software Foundation,
1269             Inc., L<https://www.fsf.org/>.
1270              
1271             =head2 Original C program
1272              
1273             Here is the copyright information provided by Paul Schlyter:
1274              
1275             Written as DAYLEN.C, 1989-08-16
1276              
1277             Modified to SUNRISET.C, 1992-12-01
1278              
1279             (c) Paul Schlyter, 1989, 1992
1280              
1281             Released to the public domain by Paul Schlyter, December 1992
1282              
1283             Permission is hereby granted, free of charge, to any person obtaining a
1284             copy of this software and associated documentation files (the "Software"),
1285             to deal in the Software without restriction, including without limitation
1286             the rights to use, copy, modify, merge, publish, distribute, sublicense,
1287             and/or sell copies of the Software, and to permit persons to whom the
1288             Software is furnished to do so, subject to the following conditions:
1289              
1290             The above copyright notice and this permission notice shall be included
1291             in all copies or substantial portions of the Software.
1292              
1293             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1294             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1295             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1296             THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
1297             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
1298             OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1299             THE SOFTWARE.
1300              
1301             =head1 SEE ALSO
1302              
1303             perl(1).
1304              
1305             L<DateTime::Event::Sunrise>
1306              
1307             L<DateTime::Event::Jewish::Sunrise>
1308              
1309             The text F<doc/astronomical-notes.pod> (or its original French version
1310             F<doc/notes-astronomiques>) in this distribution.
1311              
1312             L<https://stjarnhimlen.se/comp/riset.html>
1313              
1314             =cut