File Coverage

blib/lib/DateTime/Indic/Utils.pm
Criterion Covered Total %
statement 48 155 30.9
branch 0 12 0.0
condition 0 6 0.0
subroutine 16 24 66.6
pod 8 8 100.0
total 72 205 35.1


line stmt bran cond sub pod time code
1             package DateTime::Indic::Utils;
2              
3 14     14   17295 use base 'Exporter';
  14         21  
  14         1111  
4 14     14   67 use warnings;
  14         23  
  14         464  
5 14     14   54 use strict;
  14         23  
  14         311  
6 14     14   67 use Carp qw/ carp croak /;
  14         25  
  14         803  
7 14     14   2861829 use DateTime::Util::Calc qw/ mod revolution sin_deg /;
  14         26064172  
  14         1227  
8 14     14   132 use POSIX qw/ ceil floor /;
  14         25  
  14         137  
9 14     14   916 use Math::Trig qw( pi pi2 atan deg2rad tan );
  14         24  
  14         2047  
10              
11             our @EXPORT_OK = qw/
12             epoch
13             anomalistic_year
14             anomalistic_month
15             J0
16             J1900
17             sidereal_year
18             sidereal_month
19             synodic_month
20             creation
21             ayanamsha
22             lunar_longitude
23             lunar_on_or_before
24             newmoon
25             saura_rashi
26             saura_varsha
27             solar_longitude
28             tithi_at_dt
29             /;
30              
31             =head1 NAME
32              
33             DateTime::Indic::Utils - Utility functions for Indian calendar calculation
34              
35             =head1 VERSION
36              
37             Version 0.3
38              
39             =cut
40              
41             our $VERSION = '0.3';
42              
43             =head1 SYNOPSIS
44              
45             my $dt = DateTime->now;
46            
47             my $ayanamsha = ayanamsha(J1900);
48              
49             my $moon = lunar_longitude($J1900);
50            
51             my $d1 = DateTime::Calendar::VikramaSamvata::Gujarati->new(
52             varsha => 2064,
53             masa => 7,
54             paksha => 1,
55             tithi => 30,
56             );
57             my $d2 = DateTime::Calendar::VikramaSamvata::Gujarati->new(
58             varsha => 2065,
59             masa => 1,
60             paksha => 0,
61             tithi => 15,
62             );
63             my $bool = lunar_on_or_before($d1, $d2);
64            
65             my $previous_newmoon = newmoon(J1900, 0);
66             my $next_newmoon = newmoon(J1900, 1);
67            
68             my $sun = solar_longitude(J1900);
69              
70             my $rashi = saura_rashi(J1900);
71            
72             my $year = saura_varsha($dt);
73            
74             my $lunar_day = tithi_at_dt($dt);
75              
76              
77             =head1 ABSTRACT
78              
79             A collection of utility functions and constants helpful in creating Indian
80             calendars.
81              
82             =head1 DESCRIPTION
83              
84             Note: In this document, Sanskrit words are transliterated using the ITRANS
85             scheme.
86              
87             These functions and constants were not included directly in
88             L as they are more useful stand-alone. None of
89             them are exported by default.
90              
91             =head1 CONSTANTS
92              
93             =head2 epoch
94              
95             Fixed date of the beginning of the Kali Yuga.
96              
97             =cut
98              
99             ## no critic 'ProhibitConstantPragma'
100              
101 14     14   75 use constant epoch => -1_132_959;
  14         19  
  14         884  
102              
103             =head2 anomalistic_year
104              
105             Mean time from aphelion to aphelion.
106              
107             =cut
108              
109 14     14   62 use constant anomalistic_year => 1_577_917_828_000 / ( 4_320_000_000 - 387 );
  14         22  
  14         879  
110              
111             =head2 anomalistic_month
112              
113             Mean time from apogee to apogee with bija correction.
114              
115             =cut
116              
117 14     14   78 use constant anomalistic_month => 1_577_917_828 / ( 57_753_336 - 488_199 );
  14         29  
  14         795  
118              
119             =head2 J0
120              
121             The fixed (RD) date of Julian date 0
122              
123             =cut
124              
125 14     14   62 use constant J0 => -1_721_425;
  14         17  
  14         633  
126              
127             =head2 J1900
128              
129             The Julian date at noon on Jan 1, 1900.
130              
131             =cut
132              
133 14     14   60 use constant J1900 => 2_415_020.0;
  14         18  
  14         717  
134              
135             =head2 sidereal_year
136              
137             Mean length of Hindu sidereal year.
138              
139             =cut
140              
141 14     14   60 use constant sidereal_year => 365 + ( 279_457 / 1_080_000 );
  14         18  
  14         739  
142              
143             =head2 sidereal_month
144              
145             Mean time it takes for the moon to make one revolution around the earth.
146              
147             =cut
148              
149 14     14   69 use constant sidereal_month => 27 + ( 4_644_439 / 14_438_334 );
  14         28  
  14         720  
150              
151             =head2 synodic_month
152              
153             Mean time from new moon to new moon.
154              
155             =cut
156              
157 14     14   66 use constant synodic_month => 29.530_588_68;
  14         21  
  14         808  
158              
159             =head2 creation
160              
161             Fixed (RD) date of the beginning of the present yuga cycle.
162              
163             =cut
164              
165 14     14   65 use constant creation => epoch - 1_955_880_000 * sidereal_year;
  14         24  
  14         21782  
166              
167             =head1 FUNCTIONS
168              
169             =head2 ayanamsha($jdate)
170              
171             Given a Julian date C<$jdate>, returns the chitrapakSha ayanAMsha in decimal
172             degrees.
173              
174             =cut
175              
176             sub ayanamsha {
177 0     0 1   my ($jdate) = @_;
178              
179 0           my $t = ( ( $jdate - J1900 ) - 0.5 ) / 36_525;
180              
181             # Mean lunar node
182 0           my $ln = ( ( 933_060 - 6_962_911 * $t + 7.5 * $t * $t ) / 3_600.0 ) % 360.0;
183              
184             # Mean Sun
185 0           my $off = ( 259_205_536.0 * $t + 2_013_816.0 ) / 3_600.0;
186              
187 0           $off =
188             17.23 * sin_deg($ln) +
189             1.27 * sin_deg($off) -
190             ( 5_025.64 + 1.11 * $t ) * $t;
191              
192             # 84038.27 = Fagan-Bradley 80861.27 = Chitrapaksha (Lahiri)
193 0           $off = ( $off - 80_861.27 ) / 3_600.0;
194              
195 0           return $off;
196             }
197              
198             =head2 lunar_longitude($jdate)
199              
200             Given a Julian date C<$jdate>, returns the sAyana longitude of the moon at
201             C<$jdate> in decimal degrees.
202              
203             =cut
204              
205             sub lunar_longitude {
206 0     0 1   my ($jdate) = @_;
207             ## no critic 'ProhibitParensWithBuiltins'
208              
209 0           my $t = ( $jdate - J1900 ) / 36_525.0;
210 0           my $dn = $t * 36_525.0;
211 0           my ( $A, $B, $C, $D, $E, $F, $l, $M, $mm );
212 0           my $t2 = $t * $t;
213 0           my $t3 = $t2 * $t;
214 0           my ( $ang, $ang1 );
215 0           my $anom = revolution(
216             358.475_833 + 35_999.04_975 * $t - 1.50e-4 * $t2 - 3.3e-6 * $t3 );
217 0           $A = 0.003964 * ( sin deg2rad( 346.56 + $t * 132.87 - $t2 * 0.0091731 ) );
218 0           $B = ( sin deg2rad( 51.2 + 20.2 * $t ) );
219 0           my $omeg = revolution(
220             259.183_275 - 1_934.1_420 * $t + 0.002_078 * $t2 + 0.0_000_022 * $t3 );
221 0           $C = ( sin deg2rad($omeg) );
222              
223 0           $l =
224             revolution( 270.434_164 +
225             481_267.8_831 * $t -
226             0.001_133 * $t2 +
227             0.0_000_019 * $t3 +
228             0.000_233 * $B + $A +
229             0.001_964 * $C );
230 0           $mm =
231             deg2rad( 296.104_608 +
232             477_198.8_491 * $t +
233             0.009_192 * $t2 +
234             1.44e-5 * $t3 +
235             0.000_817 * $B + $A +
236             0.002_541 * $C );
237 0           $D =
238             deg2rad( 350.737_486 +
239             445_267.1_142 * $t -
240             0.001_436 * $t2 +
241             1.9e-6 * $t3 + $A +
242             0.002_011 * $B +
243             0.001_964 * $C );
244 0           $F =
245             deg2rad( 11.250_889 +
246             483_202.0_251 * $t -
247             0.003_211 * $t2 -
248             0.0_000_003 * $t3 +
249             $A -
250             0.024_691 * $C -
251             0.004_328 * ( sin deg2rad( $omeg + 275.05 - 2.3 * $t ) ) );
252 0           $M = deg2rad( $anom - 0.001778 * $B );
253 0           $E = 1.0 - 0.002_495 * $t - 0.00_000_752 * $t2;
254 0           $ang =
255             $l +
256             6.288_750 * ( sin $mm ) +
257             1.274_018 * sin( $D + $D - $mm ) +
258             0.658_309 * sin( $D + $D ) +
259             0.213_616 * sin( $mm + $mm ) -
260             0.114_336 * sin( $F + $F ) +
261             0.058_793 * sin( $D + $D - $mm - $mm );
262 0           $ang =
263             $ang +
264             0.053_320 * sin( $D + $D + $mm ) -
265             0.034_718 * ( sin $D ) +
266             0.015_326 * sin( $D + $D - $F - $F ) -
267             0.012_528 * sin( $F + $F + $mm ) -
268             0.010_980 * sin( $F + $F - $mm );
269 0           $ang =
270             $ang +
271             0.010_674 * sin( 4.0 * $D - $mm ) +
272             0.010_034 * sin( 3.0 * $mm ) +
273             0.008_548 * sin( 4.0 * $D - $mm - $mm ) +
274             0.005_162 * sin( $mm - $D ) +
275             0.003_996 * sin( $mm + $mm + $D + $D ) +
276             0.003_862 * sin( 4.0 * $D );
277 0           $ang =
278             $ang +
279             0.003_665 * sin( $D + $D - $mm - $mm - $mm ) +
280             0.002_602 * sin( $mm - $F - $F - $D - $D ) -
281             0.002_349 * sin( $mm + $D ) -
282             0.001_773 * sin( $mm + $D + $D - $F - $F ) -
283             0.001_595 * sin( $F + $F + $D + $D ) -
284             0.001_110 * sin( $mm + $mm + $F + $F );
285 0           $ang1 =
286             -0.185_596 * ( sin $M ) +
287             0.057_212 * sin( $D + $D - $M - $mm ) +
288             0.045_874 * sin( $D + $D - $M ) +
289             0.041_024 * sin( $mm - $M ) -
290             0.030_465 * sin( $mm + $M ) -
291             0.007_910 * sin( $M - $mm + $D + $D ) -
292             0.006_783 * sin( $D + $D + $M ) +
293             0.005_000 * sin( $M + $D );
294 0           $ang1 =
295             $ang1 +
296             0.004_049 * sin( $D + $D + $mm - $M ) +
297             0.002_695 * sin( $mm + $mm - $M ) +
298             0.002_396 * sin( $D + $D - $M - $mm - $mm ) -
299             0.002_125 * sin( $mm + $mm + $M ) +
300             0.001_220 * sin( 4.0 * $D - $M - $mm );
301 0           $ang1 =
302             $ang1 +
303             $E *
304             ( 0.002_249 * sin( $D + $D - $M - $M ) -
305             0.002_079 * sin( $M + $M ) +
306             0.002_059 * sin( $D + $D - $M - $M - $mm ) );
307              
308 0           return revolution( $ang + $E * $ang1 );
309             }
310              
311             =head2 lunar_on_or_before ($d1, $d2)
312              
313             Given two lunar dates, C<$d1> and C<$d2>, returns true if C<$d1> is on or
314             before C<$d2>.
315              
316             =cut
317              
318             sub lunar_on_or_before {
319 0     0 1   my ( $d1, $d2 ) = @_;
320              
321             return $d1->{varsha} < $d2->{varsha}
322             || $d1->{varsha} == $d2->{varsha}
323             && (
324             $d1->{masa} < $d2->{masa}
325             || $d1->{masa} == $d2->{masa}
326             && (
327             $d1->{adhikamasa} && !$d2->{adhikamasa}
328             || $d1->{adhikamasa} == $d2->{adhikamasa}
329             && ( $d1->{lunar_day} < $d2->{lunar_day}
330             || $d1->{lunar_day} == $d2->{lunar_day}
331 0   0       && ( !$d1->{adhikatithi} || $d2->{adhikatithi} ) )
332             )
333             );
334             }
335              
336             =head2 newmoon($jdate, $arg)
337              
338             Calculates the moment of the nearest new moon at C<$jdate>. (the error does
339             not exceed 2 minutes). The result is Julian date/time in UT. C<$arg> = 0 for
340             the nearest previous new moon, 1 for the nearest next moon.
341              
342             =cut
343              
344             # See http://www.iclasses.org/assets/math/scripts/science/new_and_full_moon_calculator.html
345             sub newmoon {
346 0     0 1   my ( $jdate, $arg ) = @_;
347              
348             # Estimate of number of lunar cycles since J1900.
349 0           my $k = floor( ( ( $jdate - J1900 ) / 365.25 ) * 12.3685 ) + $arg - 1;
350              
351             # time in Julian centuries since J1900
352 0           my $t = ( $jdate - J1900 ) / 36525.0;
353              
354             # square for frequent use
355 0           my $t2 = $t * $t;
356              
357             # cube for frequent use
358 0           my $t3 = $t2 * $t;
359              
360 0           my $jdnv = 0;
361 0           while ( $jdnv <= $jdate ) {
362              
363             # mean time of phase
364 0           my $jdnext =
365             (2_415_020.759_33) +
366             synodic_month * $k +
367             0.000_117_8 * $t2 -
368             0.000_000_155 * $t3 +
369             0.000_33 * sin( deg2rad( 166.56 + 132.87 * $t - 0.009_173 * $t2 ) );
370              
371             # Sun's mean anomaly
372 0           my $m =
373             deg2rad( 359.224_2 +
374             29.105_356_08 * $k -
375             0.000_033_3 * $t2 -
376             0.000_003_47 * $t3 );
377              
378             # Moon's mean anomaly
379 0           my $mprime =
380             deg2rad( 306.025_3 +
381             385.816_918_06 * $k +
382             0.010_730_6 * $t2 +
383             0.000_012_36 * $t3 );
384              
385             # Moon's argument of latitude
386 0           my $f =
387             deg2rad( 21.296_4 +
388             390.670_506_46 * $k -
389             0.001_652_8 * $t2 -
390             0.000_002_39 * $t3 );
391              
392             # Correction for new moon
393 0           my $djd =
394             ( 0.1734 - 0.000_393 * $t ) * sin($m) + 0.002_1 * sin( 2 * $m );
395 0           $djd = $djd - 0.406_8 * sin($mprime) + 0.016_1 * sin( 2 * $mprime );
396 0           $djd = $djd - 0.000_4 * sin( 3 * $mprime ) + 0.010_4 * sin( 2 * $f );
397 0           $djd =
398             $djd - 0.005_1 * sin( $m + $mprime ) - 0.007_4 * sin( $m - $mprime );
399 0           $djd =
400             $djd + 0.000_4 * sin( 2 * $f + $m ) - 0.000_4 * sin( 2 * $f - $m );
401 0           $djd =
402             $djd -
403             0.000_6 * sin( 2 * $f + $mprime ) +
404             0.001 * sin( 2 * $f - $mprime );
405 0           $djd = $djd + 0.000_5 * sin( $m + 2 * $mprime );
406 0           $jdnext += $djd;
407 0           $k++;
408              
409             # This bit solves a problem where the function "overshoots" by one
410             # lunar cycle. It works for our purposes but I am not convinced it
411             # is a proper solution to the general problem.
412 0 0 0       if ( $arg < 1 && $jdnext >= $jdate ) {
413 0           last;
414             }
415              
416 0           $jdnv = $jdnext;
417             }
418 0           return $jdnv;
419             }
420              
421             =head2 saura_rashi ($jdate)
422              
423             returns the nirAyana rAshi of the sun at Julian date C<$jdate> as an integer
424             in the range 1 .. 12.
425              
426             =cut
427              
428             sub saura_rashi {
429 0     0 1   my ($jdate) = @_;
430              
431 0           return floor( ( solar_longitude($jdate) + ayanamsha($jdate) ) / 30.0 ) + 1;
432             }
433              
434             =head2 saura_varsha ($dt)
435              
436             Returns the saura varSha at datetime C<$dt>.
437              
438             =cut
439              
440             sub saura_varsha {
441 0     0 1   my ($dt) = @_;
442              
443 0           return floor( ( ( $dt->utc_rd_values )[0] - epoch ) / sidereal_year );
444             }
445              
446             =head2 solar_longitude($jdate)
447              
448             Given a Julian date C<$jdate>, returns the sAyana longitude of the sun at
449             C<$jdate> in decimal degrees.
450              
451             =cut
452              
453             sub solar_longitude {
454 0     0 1   my ($jdate) = @_;
455              
456 0           my $t = ( $jdate - J1900 ) / 36_525.0;
457 0           my $dn = $t * 36_525.0;
458 0           my $t2 = $t * $t;
459 0           my $t3 = $t2 * $t;
460 0           my $mnln = deg2rad( 279.69_668 + $t * 36_000.76_892 + $t2 * 0.0_003_025 );
461 0           my $ecc = 0.01675104 - $t * 0.0_000_418 - $t2 * 0.000_000_126;
462 0           my $orbr = 1.0_000_002;
463 0           my $anom =
464             deg2rad( 358.475_833 +
465             35_999.04_975 * $t -
466             1.50e-4 * $t * $t -
467             3.3e-6 * $t * $t * $t );
468 0           my $anmn = $anom;
469 0           my $daily = deg2rad(1.0);
470 0           my $A = deg2rad( 153.23 + 22_518.7_541 * $t );
471 0           my $B = deg2rad( 216.57 + 45_037.5_082 * $t );
472 0           my $C = deg2rad( 312.69 + 329_64.3_577 * $t );
473 0           my $D = deg2rad( 350.74 + 445_267.1_142 * $t - 0.00144 * $t2 );
474 0           my $E = deg2rad( 231.19 + 20.20 * $t );
475 0           my $H = deg2rad( 353.40 + 65_928.7_155 * $t );
476 0           my $c1 = deg2rad(
477             (
478             1.34 * ( cos $A ) +
479             1.54 * ( cos $B ) +
480             2.0 * ( cos $C ) +
481             1.79 * ( sin $D ) +
482             1.78 * ( sin $E )
483             ) * 1.00e-3
484             );
485 0           my $c2 = deg2rad(
486             (
487             0.543 * ( sin $A ) +
488             1.575 * ( sin $B ) +
489             1.627 * ( sin $C ) +
490             3.076 * ( cos $D ) +
491             0.927 * ( sin $H )
492             ) * 1.0e-5
493             );
494 0           my $incl = 0.0;
495 0           my $ascn = 0.0;
496 0           my $anec = 0.0;
497              
498 0           for ( my $eold = $anmn ; abs( $anec - $eold ) > 1.0e-8 ; $eold = $anec )
499             { ## no critic 'ProhibitCStyleForLoops'
500 0           $anec =
501             $eold +
502             ( $anmn + $ecc * ( sin $eold ) - $eold ) /
503             ( 1.0 - $ecc * ( cos $eold ) );
504             }
505 0           my $antr =
506             atan( sqrt( ( 1.0 + $ecc ) / ( 1.0 - $ecc ) ) * tan( $anec / 2.0 ) ) *
507             2.0;
508 0 0         if ( $antr < 0.0 ) {
509 0           $antr += pi2;
510             }
511              
512             # calculate the heliocentric longitude trlong.
513 0           my $u = $mnln + $antr - $anmn - $ascn;
514 0 0         if ( $u > pi2 ) {
515 0           $u -= pi2;
516             }
517 0 0         if ( $u < 0.0 ) {
518 0           $u += pi2;
519             }
520 0           my $n = int( $u * 2.0 / pi );
521 0           my $uu = atan( cos($incl) * tan($u) );
522 0 0         if ( $n != int( $uu * 2.0 / pi ) ) {
523 0           $uu += pi;
524             }
525 0 0         if ( $n == 3 ) {
526 0           $uu += pi;
527             }
528 0           my $trlong = $uu + $ascn + $c1;
529 0           my $rad = $orbr * ( 1.0 - $ecc * ( cos $anec ) ) + $c2;
530              
531 0           return revolution( $trlong * 180.0 / pi );
532             }
533              
534             =head2 tithi_at_dt ($dt)
535              
536             Returns the phase of the moon (tithi) at DateTime C<$dt>, as an integer in the
537             range 1..30.
538              
539             =cut
540              
541             sub tithi_at_dt {
542 0     0 1   my ($dt) = @_;
543              
544 0           my $t = mod( lunar_longitude( $dt->jd ) - solar_longitude( $dt->jd ), 360 );
545              
546 0           return ceil( $t / 12.0 );
547             }
548              
549             =head1 BUGS
550              
551             Please report any bugs or feature requests through the web interface at
552             L. I
553             will be notified, and then you’ll automatically be notified of progress
554             on your bug as I make changes. B
555              
556             =head1 SUPPORT
557              
558             You can find documentation for this module with the perldoc command.
559              
560             perldoc DateTime::Indic::Utils
561              
562             Support requests for this module and questions about panchanga ganita should
563             be sent to the panchanga-devel@lists.braincells.com email list. See
564             L for more details.
565              
566             Questions related to the DateTime API should be sent to the
567             datetime@perl.org email list. See L for more details.
568              
569             You can also look for information at:
570              
571             =over 4
572              
573             =item * This projects git source code repository
574              
575             L
576              
577             =item * AnnoCPAN: Annotated CPAN documentation
578              
579             L
580              
581             =item * CPAN Ratings
582              
583             L
584              
585             =item * Search CPAN
586              
587             L
588              
589             =back
590              
591             =head1 SEE ALSO
592              
593             L
594              
595             =head1 AUTHOR
596              
597             Jaldhar H. Vyas, C<< >>
598              
599             =head1 COPYRIGHT AND LICENSE
600              
601             Copyright (C) 2009, Consolidated Braincells Inc.
602              
603             This program is free software; you can redistribute it and/or modify it under
604             the terms of either:
605              
606             =over 4
607              
608             =item * the GNU General Public License as published by the Free Software
609             Foundation; either version 2, or (at your option) any later version, or
610              
611             =item * the Artistic License version 2.0.
612              
613             =back
614              
615             The full text of the license can be found in the LICENSE file included
616             with this distribution.
617              
618             =cut
619              
620             1; # End of DateTime::Indic::Utils