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