File Coverage

blib/lib/DateTime/Indic/Utils.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


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