File Coverage

blib/lib/Astro/Utils.pm
Criterion Covered Total %
statement 112 126 88.8
branch 26 56 46.4
condition 11 36 30.5
subroutine 20 20 100.0
pod 2 2 100.0
total 171 240 71.2


line stmt bran cond sub pod time code
1             package Astro::Utils;
2              
3             $Astro::Utils::VERSION = '0.05';
4             $Astro::Utils::AUTHORITY = 'cpan:MANWAR';
5              
6             =head1 NAME
7              
8             Astro::Utils - Utility package for Astronomical Calculations.
9              
10             =head1 VERSION
11              
12             Version 0.05
13              
14             =cut
15              
16 3     3   206756 use 5.006;
  3         26  
17 3     3   13 use strict; use warnings;
  3     3   5  
  3         70  
  3         12  
  3         6  
  3         79  
18 3     3   1214 use Data::Dumper;
  3         17117  
  3         234  
19 3     3   1805 use DateTime;
  3         1203994  
  3         141  
20              
21 3     3   23 use parent 'Exporter';
  3         6  
  3         28  
22             our @EXPORT = qw(calculate_equinox calculate_solstice);
23              
24             my $PI = 3.141592653589793;
25             my $TYPE = { 'MAR' => 0, 'JUN' => 1, 'SEP' => 2, 'DEC' => 3 };
26              
27             =head1 DESCRIPTION
28              
29             The entire algorithm is based on the book "Astronomical Algorithms", 2nd Edition
30             by Jean Meeus,(C)1998, published by Willmann-Bell, Inc. I needed this for one of
31             my disrtribution L<Calendar::Bahai>.
32              
33             The calculated times are in Terrestrial Dynamical Time (TDT or TT), a replacement
34             for Ephemeris Times(ET).TDT is a uniform time used for astronomical calculations.
35              
36             =head1 SYNOPSIS
37              
38             use strict; use warnings;
39             use Astro::Utils;
40              
41             print "Mar'2015 Equinox (UTC): ", calculate_equinox ('mar', 'utc', 2015),"\n";
42             print "Mar'2015 Equinox (TDT): ", calculate_equinox ('mar', 'tdt', 2015),"\n";
43              
44             print "Jun'2015 Solstice (UTC): ", calculate_solstice('jun', 'utc', 2015),"\n";
45             print "Jun'2015 Solstice (TDT): ", calculate_solstice('jun', 'tdt', 2015),"\n";
46              
47             print "Sep'2015 Equinox (UTC): ", calculate_equinox ('sep', 'utc', 2015),"\n";
48             print "Sep'2015 Equinox (TDT): ", calculate_equinox ('sep', 'tdt', 2015),"\n";
49              
50             print "Dec'2015 Solstice (UTC): ", calculate_solstice('dec', 'utc', 2015),"\n";
51             print "Dec'2015 Solstice (TDT): ", calculate_solstice('dec', 'tdt', 2015),"\n";
52              
53             =head1 METHODS
54              
55             =head2 calculate_equinox($type, $timezone, $year)
56              
57             The param C<$type> can be either 'mar' or 'sep'. The param C<$timezone> can be
58             either 'UTC' or 'TDT'. And finally C<$year> can be anything between -1000 & 3000.
59             All parameters are required.
60              
61             =cut
62              
63             sub calculate_equinox {
64 4     4 1 1315 my ($type, $timezone, $year) = @_;
65              
66 4 50 33     30 die "ERROR: Year should be between -1000 and 3000.\n"
      33        
67             unless (defined $year && ($year >= -1000) && ($year <= 3000));
68              
69 4 100       20 if ($timezone =~ /^utc$/i) {
    50          
70 2         10 return _calc_utc_equinox($TYPE->{uc($type)}, $year);
71             }
72             elsif ($timezone =~ /^tdt$/i) {
73 2         9 return _calc_tdt_equinox($TYPE->{uc($type)}, $year);
74             }
75             else {
76 0         0 die "ERROR: Invalid timezone [$timezone] received.\n";
77             }
78             }
79              
80             =head2 calculate_solstice($type. $timezone, $year)
81              
82             The param C<$type> can be either 'jun' or 'dec'. The param C<$timezone> can be
83             either 'UTC' or 'TDT'. And finally C<$year> can be anything between -1000 & 3000.
84             All parameters are required.
85              
86             =cut
87              
88             sub calculate_solstice {
89 4     4 1 1372 my ($type, $timezone, $year) = @_;
90              
91 4 50 33     28 die "ERROR: Year should be between -1000 and 3000.\n"
      33        
92             unless (defined $year && ($year >= -1000) && ($year <= 3000));
93              
94 4 100       23 if ($timezone =~ /^utc$/i) {
    50          
95 2         9 return _calc_utc_solstice($TYPE->{uc($type)}, $year);
96             }
97             elsif ($timezone =~ /^tdt$/i) {
98 2         7 return _calc_tdt_solstice($TYPE->{uc($type)}, $year);
99             }
100             else {
101 0         0 die "ERROR: Invalid timezone [$timezone] received.\n";
102             }
103             }
104              
105             #
106             #
107             # PRIVATE METHODS
108              
109             sub _calc_utc_equinox {
110 2     2   4 my ($k, $year) = @_;
111              
112 2 50 33     11 die "ERROR: Invalid type for equinox.\n" unless (defined $k && ($k =~ /^[0|2]$/));
113 2         7 my $jd = _calc_jd($k, $year);
114 2         6 return _jd_to_utc($jd);
115             }
116              
117             sub _calc_utc_solstice {
118 2     2   6 my ($k, $year) = @_;
119              
120 2 50 33     12 die "ERROR: Invalid type for solstice.\n" unless (defined $k && ($k =~ /^[1|3]$/));
121 2         8 my $jd = _calc_jd($k, $year);
122 2         6 return _jd_to_utc($jd);
123             }
124              
125             sub _calc_tdt_equinox {
126 2     2   4 my ($k, $year) = @_;
127              
128 2 50 33     11 die "ERROR: Invalid type for equinox.\n" unless (defined $k && ($k =~ /^[0|2]$/));
129 2         5 my $jd = _calc_jd($k, $year);
130 2         7 return _jd_to_tdt($jd);
131             }
132              
133             sub _calc_tdt_solstice {
134 2     2   5 my ($k, $year) = @_;
135              
136 2 50 33     12 die "ERROR: Invalid type for solstice.\n" unless (defined $k && ($k =~ /^[1|3]$/));
137 2         3 my $jd = _calc_jd($k, $year);
138 2         7 return _jd_to_tdt($jd);
139             }
140              
141             sub _calc_jd {
142 8     8   17 my ($k, $year) = @_;
143              
144             # Astronmical Algorithms, Chapter 27.
145 8         39 my $jde0 = _jde0($k, $year);
146 8         15 my $t = ($jde0 - 2451545.0) / 36525;
147 8         12 my $w = 35999.373 * $t - 2.47;
148 8         17 my $dl = 1 + 0.0334 * _cos($w) + 0.0007 * _cos(2 * $w);
149 8         22 my $s = _periodic_term_24($t);
150              
151 8         19 return ($jde0 + ( (0.00001 * $s) / $dl ));
152             }
153              
154             sub _jd_to_utc {
155 4     4   7 my ($jd) = @_;
156              
157 4         10 my ($yr, $mon, $day, $hr, $min, $sec) = _process($jd);
158              
159 4         30 my $date = DateTime->new(year => $yr, month => $mon, day => $day, hour => $hr, minute => $min, second => $sec);
160              
161             # Astronmical Algorithms, Chapter 10, page 79 (Table 10.A).
162 4         1399 my $first = 1620;
163 4         7 my $last = 2002;
164 4         81 my @tbl = (121,112,103,95,88,82,77,72,68,63,60,56,53,51,48,46,44,42,40,38, # 1620
165             35,33,31,29,26,24,22,20,18,16,14,12,11,10,9,8,7,7,7,7, # 1660
166             7,7,8,8,9,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11, # 1700
167             11,11,12,12,12,12,13,13,13,14,14,14,14,15,15,15,15,15,16,16, # 1740
168             16,16,16,16,16,16,15,15,14,13, # 1780
169             13.1,12.5,12.2,12.0,12.0,12.0,12.0,12.0,12.0,11.9,11.6,11.0,10.2,9.2,8.2, # 1800
170             7.1,6.2,5.6,5.4,5.3,5.4,5.6,5.9,6.2,6.5,6.8,7.1,7.3,7.5,7.6, # 1830
171             7.7,7.3,6.2,5.2,2.7,1.4,-1.2,-2.8,-3.8,-4.8,-5.5,-5.3,-5.6,-5.7,-5.9, # 1860
172             -6.0,-6.3,-6.5,-6.2,-4.7,-2.8,-0.1,2.6,5.3,7.7,10.4,13.3,16.0,18.2,20.2, # 1890
173             21.1,22.4,23.5,23.8,24.3,24.0,23.9,23.9,23.7,24.0,24.3,25.3,26.2,27.3,28.2, # 1920
174             29.1,30.0,30.7,31.4,32.2,33.1,34.0,35.0,36.5,38.3,40.2,42.2,44.5,46.5,48.5, # 1950
175             50.5,52.5,53.8,54.9,55.8,56.9,58.3,60.0,61.6,63.0,63.8,64.3 # 1980, 2002 last entry
176             );
177              
178 4         6 my $delta_t = 0;
179 4         10 my $t = ($yr - 2000) / 100;
180              
181 4 50 33     20 if ($yr >= $first && $yr <= $last) {
    0          
    0          
182 4 50       12 if ($yr % 2) {
183 0         0 $delta_t = ($tbl[($yr - $first - 1) / 2] + $tbl[($yr - $first + 1) / 2] ) / 2;
184             }
185             else {
186 4         13 $delta_t = $tbl[($yr - $first) / 2];
187             }
188             } elsif ($yr < 948) {
189 0         0 $delta_t = 2177 + 497 * $t + (44.1 * _pow($t, 2));
190             } elsif ($yr >= 948) {
191 0         0 $delta_t = 102 + 102 * $t + (25.3 * _pow($t, 2));
192 0 0 0     0 if ($yr >= 2000 && $yr <= 2100) {
193 0         0 $delta_t += 0.37 * ($yr - 2100);
194             }
195             }
196              
197 4         18 $date->subtract(seconds => $delta_t);
198              
199 4         4366 return sprintf("%04d-%02d-%02d %02d:%02d:%02d",
200             $date->year, $date->month, $date->day, $date->hour, $date->minute, $date->second);
201             }
202              
203             sub _jd_to_tdt {
204 4     4   9 my ($jd) = @_;
205              
206 4         9 my ($yr, $mon, $day, $hr, $min, $sec) = _process($jd);
207 4         34 return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $yr, $mon, $day, $hr, $min, $sec);
208             }
209              
210             sub _process {
211 8     8   13 my ($jd) = @_;
212              
213             # Astronmical Algorithms Chapter 7, page 63.
214 8         11 my ($a, $alpha);
215 8         14 my $z = int($jd + 0.5);
216 8         15 my $f = ($jd + 0.5) - $z;
217              
218 8 50       17 if ($z < 2299161) {
219 0         0 $a = $z;
220             }
221             else {
222 8         14 $alpha = int(($z - 1867216.25) / 36524.25);
223 8         15 $a = $z + 1 + $alpha - int($alpha / 4);
224             }
225              
226 8         14 my $b = $a + 1524;
227 8         12 my $c = int(($b - 122.1) / 365.25);
228 8         13 my $d = int(365.25 * $c);
229 8         13 my $e = int(($b -$d) / 30.6001);
230 8         15 my $dt = $b - $d - int(30.6001 * $e) + $f;
231 8 50       17 my $mon = $e - (($e < 13.5)?(1):(13));
232 8 50       16 my $yr = $c - (($mon > 2.5)?(4716):(4715));
233 8         12 my $day = int($dt);
234 8         12 my $h = 24 * ($dt - $day);
235 8         10 my $hr = int($h);
236 8         18 my $m = 60 * ($h - $hr);
237 8         9 my $min = int($m);
238 8         14 my $sec = int(60 * ($m - $min));
239              
240 8         28 return ($yr, $mon, $day, $hr, $min, $sec);
241             }
242              
243             sub _periodic_term_24 {
244 8     8   12 my ($t) = @_;
245              
246             # Astronmical Algorithms, Chapter 27, page 179 (Table 27.C).
247 8         28 my @a = (485,203,199,182,156,136,77,74,70,58,52,
248             50,45,44,29,18,17,16,14,12,12,12,9,8);
249 8         23 my @b = (324.96,337.23,342.08,27.85,73.14,171.52,
250             222.54,296.72,243.58,119.81,297.17,21.02,
251             247.54,325.15,60.93,155.12,288.79,198.04,
252             199.76,95.39,287.11,320.81,227.73,15.45);
253 8         20 my @c = (1934.136,32964.467,20.186,445267.112,45036.886,
254             22518.443,65928.934,3034.906,9037.513,
255             33718.147,150.678,2281.226,29929.562,31555.956,
256             4443.417,67555.328,4562.452,62894.029,
257             31436.921,14577.848,31931.756,34777.259,
258             1222.114,16859.074);
259              
260 8         11 my $s = 0;
261 8         18 foreach my $i (0..23) {
262 192         282 $s += ($a[$i] * _cos($b[$i] + ($c[$i] * $t)));
263             }
264              
265 8         24 return $s;
266             }
267              
268             sub _jde0 {
269 8     8   15 my ($k, $year) = @_;
270              
271             # Julian Ephemeris Day Calculation.
272 8         46 my ($jde0, $y);
273              
274 8 50 33     48 if ($year >= -1000 && $year <= 1000) {
    50 33        
275             # Astronmical Algorithms, Chapter 27, page 178 (Table 27.A).
276 0         0 $y = $year / 1000;
277              
278 0 0       0 if ($k == 0) {
    0          
    0          
    0          
279 0         0 $jde0 = 1721139.29189 +
280             (365242.13740 * $y) +
281             (0.06134 * _pow($y, 2)) +
282             (0.00111 * _pow($y, 3)) -
283             (0.00071 * _pow($y, 4));
284             }
285             elsif ($k == 1) {
286 0         0 $jde0 = 1721233.25401 +
287             (365241.72562 * $y) -
288             (0.05323 * _pow($y, 2)) +
289             (0.00907 * _pow($y, 3)) +
290             (0.00025 * _pow($y, 4));
291             }
292             elsif ($k == 2) {
293 0         0 $jde0 = 1721325.70455 +
294             (365242.49558 * $y) -
295             (0.11677 * _pow($y, 2)) -
296             (0.00297 * _pow($y, 3)) +
297             (0.00074 * _pow($y, 4));
298             }
299             elsif ($k == 3) {
300 0         0 $jde0 = 1721414.39987 +
301             (365242.88257 * $y) -
302             (0.00769 * _pow($y, 2)) -
303             (0.00933 * _pow($y, 3)) -
304             (0.00006 * _pow($y, 4));
305             }
306             }
307             elsif ($year > 1000 && $year <= 3000) {
308             # Astronmical Algorithms, Chapter 27, page 178 (Table 27.B).
309 8         22 $y = ($year - 2000) / 1000;
310              
311 8 100       35 if ($k == 0) {
    100          
    100          
    50          
312 2         8 $jde0 = 2451623.80984 +
313             (365242.37404 * $y) +
314             (0.05169 * _pow($y, 2)) -
315             (0.00411 * _pow($y, 3)) -
316             (0.00057 * _pow($y, 4));
317             }
318             elsif ($k == 1) {
319 2         9 $jde0 = 2451716.56767 +
320             (365241.62603 * $y) +
321             (0.00325 * _pow($y, 2)) +
322             (0.00888 * _pow($y, 3)) -
323             (0.00030 * _pow($y, 4));
324             }
325             elsif ($k == 2) {
326 2         6 $jde0 = 2451810.21715 +
327             (365242.01767 * $y) -
328             (0.11575 * _pow($y, 2)) +
329             (0.00337 * _pow($y, 3)) +
330             (0.00078 * _pow($y, 4));
331             }
332             elsif ($k == 3) {
333 2         6 $jde0 = 2451900.05952 +
334             (365242.74049 * $y) -
335             (0.06223 * _pow($y, 2)) -
336             (0.00823 * _pow($y, 3)) +
337             (0.00032 * _pow($y, 4));
338             }
339             }
340              
341 8         13 return $jde0;
342             }
343              
344             sub _pow {
345 24     24   37 my ($n, $m) = @_;
346              
347 24         29 my $r = 1;
348 24         37 foreach (1..$m) {
349 72         81 $r *= $n;
350             }
351              
352 24         54 return $r;
353             }
354              
355             sub _cos {
356 208     208   243 my ($degree) = @_;
357              
358 208         408 return cos(($degree * $PI)/180);
359             }
360              
361             =head1 AUTHOR
362              
363             Mohammad S Anwar, C<< <mohammad.anwar at yahoo.com> >>
364              
365             =head1 REPOSITORY
366              
367             L<https://github.com/manwar/Astro-Utils>
368              
369             =head1 BUGS
370              
371             Please report any bugs or feature requests to C<bug-astro-utils at rt.cpan.org>,
372             or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Astro-Utils>.
373             I will be notified and then you'll automatically be notified of progress on your
374             bug as I make changes.
375              
376             =head1 SUPPORT
377              
378             You can find documentation for this module with the perldoc command.
379              
380             perldoc Astro::Utils
381              
382             You can also look for information at:
383              
384             =over 4
385              
386             =item * RT: CPAN's request tracker (report bugs here)
387              
388             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Astro-Utils>
389              
390             =item * AnnoCPAN: Annotated CPAN documentation
391              
392             L<http://annocpan.org/dist/Astro-Utils>
393              
394             =item * CPAN Ratings
395              
396             L<http://cpanratings.perl.org/d/Astro-Utils>
397              
398             =item * Search CPAN
399              
400             L<http://search.cpan.org/dist/Astro-Utils/>
401              
402             =back
403              
404             =head1 LICENSE AND COPYRIGHT
405              
406             Copyright (C) 2015 - 2017 Mohammad S Anwar.
407              
408             This program is free software; you can redistribute it and / or modify it under
409             the terms of the the Artistic License (2.0). You may obtain a copy of the full
410             license at:
411              
412             L<http://www.perlfoundation.org/artistic_license_2_0>
413              
414             Any use, modification, and distribution of the Standard or Modified Versions is
415             governed by this Artistic License.By using, modifying or distributing the Package,
416             you accept this license. Do not use, modify, or distribute the Package, if you do
417             not accept this license.
418              
419             If your Modified Version has been derived from a Modified Version made by someone
420             other than you,you are nevertheless required to ensure that your Modified Version
421             complies with the requirements of this license.
422              
423             This license does not grant you the right to use any trademark, service mark,
424             tradename, or logo of the Copyright Holder.
425              
426             This license includes the non-exclusive, worldwide, free-of-charge patent license
427             to make, have made, use, offer to sell, sell, import and otherwise transfer the
428             Package with respect to any patent claims licensable by the Copyright Holder that
429             are necessarily infringed by the Package. If you institute patent litigation
430             (including a cross-claim or counterclaim) against any party alleging that the
431             Package constitutes direct or contributory patent infringement,then this Artistic
432             License to you shall terminate on the date that such litigation is filed.
433              
434             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
435             CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
436             WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
437             NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS
438             REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT,
439             INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE
440             OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
441              
442             =cut
443              
444             1; # End of Astro::Utils