astrology-insights 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/constants.js +165 -0
- package/dist/core/karana.js +23 -0
- package/dist/core/nakshatra.js +21 -0
- package/dist/core/rashi.js +17 -0
- package/dist/core/tithi.js +36 -0
- package/dist/core/yoga.js +16 -0
- package/dist/panchang/src/calculations/ephemeris.js +7 -7
- package/dist/panchang/src/calculations/planetary.js +1 -1
- package/dist/panchang/src/panchang-v2.js +104 -4
- package/dist/panchang-v2.js +396 -0
- package/dist/timings/kalam.js +114 -0
- package/dist/timings/muhurat.js +134 -0
- package/dist/types.js +7 -0
- package/package.json +2 -1
- package/panchang/src/panchang-v2.ts +117 -4
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Panchang constants — astronomical degrees, names, and lookup tables.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RASHIS = exports.KARANA_NAMES_FIXED = exports.KARANA_NAMES_REPEATING = exports.YOGA_NAMES = exports.NAKSHATRAS = exports.TITHI_NAMES = exports.normalizeAngle = exports.DEGREES_PER_RASHI = exports.DEGREES_PER_YOGA = exports.DEGREES_PER_PADA = exports.DEGREES_PER_NAKSHATRA = exports.DEGREES_PER_TITHI = void 0;
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Degree constants
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
exports.DEGREES_PER_TITHI = 12;
|
|
11
|
+
exports.DEGREES_PER_NAKSHATRA = 13.333333;
|
|
12
|
+
exports.DEGREES_PER_PADA = 3.333333;
|
|
13
|
+
exports.DEGREES_PER_YOGA = 13.333333;
|
|
14
|
+
exports.DEGREES_PER_RASHI = 30;
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Helper
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
function normalizeAngle(angle) {
|
|
19
|
+
let normalized = angle % 360;
|
|
20
|
+
if (normalized < 0) {
|
|
21
|
+
normalized += 360;
|
|
22
|
+
}
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
exports.normalizeAngle = normalizeAngle;
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Tithi names (30 total: 15 Shukla + 15 Krishna)
|
|
28
|
+
// Index 0-14 = Shukla Paksha; index 15-29 = Krishna Paksha
|
|
29
|
+
// The 15th tithi of each paksha is Purnima / Amavasya respectively.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
exports.TITHI_NAMES = [
|
|
32
|
+
// Shukla Paksha (1–15)
|
|
33
|
+
'Shukla Pratipada',
|
|
34
|
+
'Shukla Dwitiya',
|
|
35
|
+
'Shukla Tritiya',
|
|
36
|
+
'Shukla Chaturthi',
|
|
37
|
+
'Shukla Panchami',
|
|
38
|
+
'Shukla Shashthi',
|
|
39
|
+
'Shukla Saptami',
|
|
40
|
+
'Shukla Ashtami',
|
|
41
|
+
'Shukla Navami',
|
|
42
|
+
'Shukla Dashami',
|
|
43
|
+
'Shukla Ekadashi',
|
|
44
|
+
'Shukla Dwadashi',
|
|
45
|
+
'Shukla Trayodashi',
|
|
46
|
+
'Shukla Chaturdashi',
|
|
47
|
+
'Purnima',
|
|
48
|
+
// Krishna Paksha (1–15)
|
|
49
|
+
'Krishna Pratipada',
|
|
50
|
+
'Krishna Dwitiya',
|
|
51
|
+
'Krishna Tritiya',
|
|
52
|
+
'Krishna Chaturthi',
|
|
53
|
+
'Krishna Panchami',
|
|
54
|
+
'Krishna Shashthi',
|
|
55
|
+
'Krishna Saptami',
|
|
56
|
+
'Krishna Ashtami',
|
|
57
|
+
'Krishna Navami',
|
|
58
|
+
'Krishna Dashami',
|
|
59
|
+
'Krishna Ekadashi',
|
|
60
|
+
'Krishna Dwadashi',
|
|
61
|
+
'Krishna Trayodashi',
|
|
62
|
+
'Krishna Chaturdashi',
|
|
63
|
+
'Amavasya',
|
|
64
|
+
];
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Nakshatras (27)
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
exports.NAKSHATRAS = [
|
|
69
|
+
{ name: 'Ashwini', lord: 'Ketu', deity: 'Ashwini Kumaras' },
|
|
70
|
+
{ name: 'Bharani', lord: 'Venus', deity: 'Yama' },
|
|
71
|
+
{ name: 'Krittika', lord: 'Sun', deity: 'Agni' },
|
|
72
|
+
{ name: 'Rohini', lord: 'Moon', deity: 'Brahma' },
|
|
73
|
+
{ name: 'Mrigashira', lord: 'Mars', deity: 'Soma' },
|
|
74
|
+
{ name: 'Ardra', lord: 'Rahu', deity: 'Rudra' },
|
|
75
|
+
{ name: 'Punarvasu', lord: 'Jupiter', deity: 'Aditi' },
|
|
76
|
+
{ name: 'Pushya', lord: 'Saturn', deity: 'Brihaspati' },
|
|
77
|
+
{ name: 'Ashlesha', lord: 'Mercury', deity: 'Nagas' },
|
|
78
|
+
{ name: 'Magha', lord: 'Ketu', deity: 'Pitrs' },
|
|
79
|
+
{ name: 'Purva Phalguni', lord: 'Venus', deity: 'Bhaga' },
|
|
80
|
+
{ name: 'Uttara Phalguni', lord: 'Sun', deity: 'Aryaman' },
|
|
81
|
+
{ name: 'Hasta', lord: 'Moon', deity: 'Savitar' },
|
|
82
|
+
{ name: 'Chitra', lord: 'Mars', deity: 'Vishvakarma' },
|
|
83
|
+
{ name: 'Swati', lord: 'Rahu', deity: 'Vayu' },
|
|
84
|
+
{ name: 'Vishakha', lord: 'Jupiter', deity: 'Indragni' },
|
|
85
|
+
{ name: 'Anuradha', lord: 'Saturn', deity: 'Mitra' },
|
|
86
|
+
{ name: 'Jyeshtha', lord: 'Mercury', deity: 'Indra' },
|
|
87
|
+
{ name: 'Mula', lord: 'Ketu', deity: 'Nirriti' },
|
|
88
|
+
{ name: 'Purva Ashadha', lord: 'Venus', deity: 'Apah' },
|
|
89
|
+
{ name: 'Uttara Ashadha', lord: 'Sun', deity: 'Vishvedevas' },
|
|
90
|
+
{ name: 'Shravana', lord: 'Moon', deity: 'Vishnu' },
|
|
91
|
+
{ name: 'Dhanishtha', lord: 'Mars', deity: 'Vasus' },
|
|
92
|
+
{ name: 'Shatabhisha', lord: 'Rahu', deity: 'Varuna' },
|
|
93
|
+
{ name: 'Purva Bhadrapada', lord: 'Jupiter', deity: 'Ajaikapat' },
|
|
94
|
+
{ name: 'Uttara Bhadrapada', lord: 'Saturn', deity: 'Ahirbudhnya' },
|
|
95
|
+
{ name: 'Revati', lord: 'Mercury', deity: 'Pushan' },
|
|
96
|
+
];
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Yoga names (27)
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
exports.YOGA_NAMES = [
|
|
101
|
+
'Vishkumbha',
|
|
102
|
+
'Preeti',
|
|
103
|
+
'Ayushman',
|
|
104
|
+
'Saubhagya',
|
|
105
|
+
'Shobhana',
|
|
106
|
+
'Atiganda',
|
|
107
|
+
'Sukarman',
|
|
108
|
+
'Dhriti',
|
|
109
|
+
'Shoola',
|
|
110
|
+
'Ganda',
|
|
111
|
+
'Vriddhi',
|
|
112
|
+
'Dhruva',
|
|
113
|
+
'Vyaghata',
|
|
114
|
+
'Harshana',
|
|
115
|
+
'Vajra',
|
|
116
|
+
'Siddhi',
|
|
117
|
+
'Vyatipata',
|
|
118
|
+
'Variyan',
|
|
119
|
+
'Parigha',
|
|
120
|
+
'Shiva',
|
|
121
|
+
'Siddha',
|
|
122
|
+
'Sadhya',
|
|
123
|
+
'Shubha',
|
|
124
|
+
'Shukla',
|
|
125
|
+
'Brahma',
|
|
126
|
+
'Indra',
|
|
127
|
+
'Vaidhriti',
|
|
128
|
+
];
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Karana names
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
/** 7 movable (repeating) karanas */
|
|
133
|
+
exports.KARANA_NAMES_REPEATING = [
|
|
134
|
+
'Bava',
|
|
135
|
+
'Balava',
|
|
136
|
+
'Kaulava',
|
|
137
|
+
'Taitila',
|
|
138
|
+
'Garija',
|
|
139
|
+
'Vanija',
|
|
140
|
+
'Vishti',
|
|
141
|
+
];
|
|
142
|
+
/** 4 fixed karanas (occur once per lunar month at the end) */
|
|
143
|
+
exports.KARANA_NAMES_FIXED = [
|
|
144
|
+
'Shakuni',
|
|
145
|
+
'Chatushpad',
|
|
146
|
+
'Naga',
|
|
147
|
+
'Kimstughna',
|
|
148
|
+
];
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Rashis (12)
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
exports.RASHIS = [
|
|
153
|
+
{ name: 'Aries', sanskritName: 'Mesha', lord: 'Mars', element: 'Fire' },
|
|
154
|
+
{ name: 'Taurus', sanskritName: 'Vrishabha', lord: 'Venus', element: 'Earth' },
|
|
155
|
+
{ name: 'Gemini', sanskritName: 'Mithuna', lord: 'Mercury', element: 'Air' },
|
|
156
|
+
{ name: 'Cancer', sanskritName: 'Karka', lord: 'Moon', element: 'Water' },
|
|
157
|
+
{ name: 'Leo', sanskritName: 'Simha', lord: 'Sun', element: 'Fire' },
|
|
158
|
+
{ name: 'Virgo', sanskritName: 'Kanya', lord: 'Mercury', element: 'Earth' },
|
|
159
|
+
{ name: 'Libra', sanskritName: 'Tula', lord: 'Venus', element: 'Air' },
|
|
160
|
+
{ name: 'Scorpio', sanskritName: 'Vrishchika', lord: 'Mars', element: 'Water' },
|
|
161
|
+
{ name: 'Sagittarius', sanskritName: 'Dhanu', lord: 'Jupiter', element: 'Fire' },
|
|
162
|
+
{ name: 'Capricorn', sanskritName: 'Makara', lord: 'Saturn', element: 'Earth' },
|
|
163
|
+
{ name: 'Aquarius', sanskritName: 'Kumbha', lord: 'Saturn', element: 'Air' },
|
|
164
|
+
{ name: 'Pisces', sanskritName: 'Meena', lord: 'Jupiter', element: 'Water' },
|
|
165
|
+
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateKarana = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const DEGREES_PER_KARANA = 6;
|
|
6
|
+
function calculateKarana(sunLongitude, moonLongitude) {
|
|
7
|
+
const diff = (0, constants_1.normalizeAngle)(moonLongitude - sunLongitude);
|
|
8
|
+
const karanaIndex = Math.floor(diff / DEGREES_PER_KARANA);
|
|
9
|
+
const posInKarana = diff - (karanaIndex * DEGREES_PER_KARANA);
|
|
10
|
+
const progress = (posInKarana / DEGREES_PER_KARANA) * 100;
|
|
11
|
+
let name;
|
|
12
|
+
if (karanaIndex === 0) {
|
|
13
|
+
name = 'Kimstughna';
|
|
14
|
+
}
|
|
15
|
+
else if (karanaIndex >= 57) {
|
|
16
|
+
name = constants_1.KARANA_NAMES_FIXED[karanaIndex - 57];
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
name = constants_1.KARANA_NAMES_REPEATING[(karanaIndex - 1) % 7];
|
|
20
|
+
}
|
|
21
|
+
return { name, number: karanaIndex + 1, progress: Math.round(progress * 10) / 10 };
|
|
22
|
+
}
|
|
23
|
+
exports.calculateKarana = calculateKarana;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateNakshatra = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
function calculateNakshatra(moonLongitude) {
|
|
6
|
+
const lon = (0, constants_1.normalizeAngle)(moonLongitude);
|
|
7
|
+
const nakshatraIndex = Math.floor(lon / constants_1.DEGREES_PER_NAKSHATRA);
|
|
8
|
+
const posInNakshatra = lon - (nakshatraIndex * constants_1.DEGREES_PER_NAKSHATRA);
|
|
9
|
+
const pada = Math.min(Math.floor(posInNakshatra / constants_1.DEGREES_PER_PADA) + 1, 4);
|
|
10
|
+
const posInPada = posInNakshatra - ((pada - 1) * constants_1.DEGREES_PER_PADA);
|
|
11
|
+
const nk = constants_1.NAKSHATRAS[nakshatraIndex];
|
|
12
|
+
const progress = (posInNakshatra / constants_1.DEGREES_PER_NAKSHATRA) * 100;
|
|
13
|
+
const padaProgress = (posInPada / constants_1.DEGREES_PER_PADA) * 100;
|
|
14
|
+
return {
|
|
15
|
+
name: nk.name, number: nakshatraIndex + 1, pada,
|
|
16
|
+
lord: nk.lord, deity: nk.deity,
|
|
17
|
+
progress: Math.round(progress * 10) / 10,
|
|
18
|
+
padaProgress: Math.round(padaProgress * 10) / 10,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
exports.calculateNakshatra = calculateNakshatra;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateRashi = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
function calculateRashi(longitude) {
|
|
6
|
+
const lon = (0, constants_1.normalizeAngle)(longitude);
|
|
7
|
+
const rashiIndex = Math.floor(lon / constants_1.DEGREES_PER_RASHI);
|
|
8
|
+
const degree = lon - (rashiIndex * constants_1.DEGREES_PER_RASHI);
|
|
9
|
+
const rashi = constants_1.RASHIS[rashiIndex];
|
|
10
|
+
return {
|
|
11
|
+
name: rashi.name,
|
|
12
|
+
lord: rashi.lord,
|
|
13
|
+
degree: Math.round(degree * 10) / 10,
|
|
14
|
+
number: rashiIndex + 1,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
exports.calculateRashi = calculateRashi;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateTithi = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
// Bare tithi names (without paksha prefix) for both halves.
|
|
6
|
+
// Index 0–14: Shukla Paksha tithis; index 15–29: Krishna Paksha tithis.
|
|
7
|
+
// Purnima (index 14) and Amavasya (index 29) have no paksha prefix.
|
|
8
|
+
const BARE_TITHI_NAMES = [
|
|
9
|
+
'Pratipada', 'Dwitiya', 'Tritiya', 'Chaturthi', 'Panchami',
|
|
10
|
+
'Shashthi', 'Saptami', 'Ashtami', 'Navami', 'Dashami',
|
|
11
|
+
'Ekadashi', 'Dwadashi', 'Trayodashi', 'Chaturdashi', 'Purnima',
|
|
12
|
+
'Pratipada', 'Dwitiya', 'Tritiya', 'Chaturthi', 'Panchami',
|
|
13
|
+
'Shashthi', 'Saptami', 'Ashtami', 'Navami', 'Dashami',
|
|
14
|
+
'Ekadashi', 'Dwadashi', 'Trayodashi', 'Chaturdashi', 'Amavasya',
|
|
15
|
+
];
|
|
16
|
+
function calculateTithi(sunLongitude, moonLongitude) {
|
|
17
|
+
const diff = (0, constants_1.normalizeAngle)(moonLongitude - sunLongitude);
|
|
18
|
+
const tithiIndex = Math.floor(diff / constants_1.DEGREES_PER_TITHI);
|
|
19
|
+
const progress = ((diff % constants_1.DEGREES_PER_TITHI) / constants_1.DEGREES_PER_TITHI) * 100;
|
|
20
|
+
const isShukla = tithiIndex < 15;
|
|
21
|
+
const paksha = isShukla ? 'Shukla' : 'Krishna';
|
|
22
|
+
const pakshaNumber = isShukla ? tithiIndex + 1 : tithiIndex - 15 + 1;
|
|
23
|
+
const baseName = BARE_TITHI_NAMES[tithiIndex];
|
|
24
|
+
// Purnima and Amavasya are standalone names; all others get paksha prefix.
|
|
25
|
+
const name = (baseName === 'Purnima' || baseName === 'Amavasya')
|
|
26
|
+
? baseName
|
|
27
|
+
: `${paksha} ${baseName}`;
|
|
28
|
+
return {
|
|
29
|
+
name,
|
|
30
|
+
number: pakshaNumber,
|
|
31
|
+
tithiIndex: tithiIndex + 1,
|
|
32
|
+
paksha,
|
|
33
|
+
progress: Math.round(progress * 10) / 10,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
exports.calculateTithi = calculateTithi;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateYoga = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
function calculateYoga(sunLongitude, moonLongitude) {
|
|
6
|
+
const sum = (0, constants_1.normalizeAngle)(sunLongitude + moonLongitude);
|
|
7
|
+
const yogaIndex = Math.floor(sum / constants_1.DEGREES_PER_YOGA);
|
|
8
|
+
const posInYoga = sum - (yogaIndex * constants_1.DEGREES_PER_YOGA);
|
|
9
|
+
const progress = (posInYoga / constants_1.DEGREES_PER_YOGA) * 100;
|
|
10
|
+
return {
|
|
11
|
+
name: constants_1.YOGA_NAMES[yogaIndex],
|
|
12
|
+
number: yogaIndex + 1,
|
|
13
|
+
progress: Math.round(progress * 10) / 10,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
exports.calculateYoga = calculateYoga;
|
|
@@ -25,9 +25,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.Ephemeris = void 0;
|
|
27
27
|
const swisseph = __importStar(require("swisseph"));
|
|
28
|
-
const index_1 =
|
|
28
|
+
const index_1 = require("../utils/index");
|
|
29
29
|
const planetary_1 = require("./planetary");
|
|
30
|
-
const path =
|
|
30
|
+
const path = __importStar(require("path"));
|
|
31
31
|
class Ephemeris {
|
|
32
32
|
constructor(ephemeris_path) {
|
|
33
33
|
this.planet_map = {
|
|
@@ -49,15 +49,15 @@ class Ephemeris {
|
|
|
49
49
|
if (!ephemeris_path) {
|
|
50
50
|
// Try multiple possible paths for the ephemeris files
|
|
51
51
|
const possible_paths = [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
path.join(__dirname, '../../ephe'),
|
|
53
|
+
path.join(__dirname, '../node_modules/swisseph/ephe'),
|
|
54
|
+
path.join(process.cwd(), 'ephe'),
|
|
55
|
+
path.join(process.cwd(), 'node_modules/swisseph/ephe') // CWD node_modules
|
|
56
56
|
];
|
|
57
57
|
// Use the first existing path
|
|
58
58
|
this.ephemeris_path = possible_paths.find(p => {
|
|
59
59
|
try {
|
|
60
|
-
const fs =
|
|
60
|
+
const fs = require('fs');
|
|
61
61
|
return fs.existsSync(p);
|
|
62
62
|
}
|
|
63
63
|
catch {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Planetary = exports.NAKSHATRAS = exports.RASHIS = void 0;
|
|
4
|
-
const index_1 =
|
|
4
|
+
const index_1 = require("../utils/index");
|
|
5
5
|
exports.RASHIS = [
|
|
6
6
|
{ name: "Mesha", element: "Fire", ruler: "Mars" },
|
|
7
7
|
{ name: "Vrishabha", element: "Earth", ruler: "Venus" },
|
|
@@ -180,6 +180,94 @@ function calculateFullPanchang(date, latitude, longitude, timezone) {
|
|
|
180
180
|
const karana = (0, karana_1.calculateKarana)(sunLon, moonLon);
|
|
181
181
|
const moonSign = (0, rashi_1.calculateRashi)(moonLon);
|
|
182
182
|
const sunSign = (0, rashi_1.calculateRashi)(sunLon);
|
|
183
|
+
// ── Transition detection with binary search for exact time ──────────────
|
|
184
|
+
// Check sunrise vs sunset. If tithi/nakshatra/yoga/karana changed,
|
|
185
|
+
// binary-search for the exact transition moment.
|
|
186
|
+
function findTransitionTime(startDate, endDate, getValueFn, startValue, maxIterations = 14) {
|
|
187
|
+
let lo = startDate.getTime();
|
|
188
|
+
let hi = endDate.getTime();
|
|
189
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
190
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
191
|
+
const midDate = new Date(mid);
|
|
192
|
+
const lons = computeLongitudes(midDate, ayanamsa);
|
|
193
|
+
const val = getValueFn(midDate);
|
|
194
|
+
if (val === startValue) {
|
|
195
|
+
lo = mid;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
hi = mid;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return new Date(hi);
|
|
202
|
+
}
|
|
203
|
+
function formatTransitionTime(dt, tz) {
|
|
204
|
+
try {
|
|
205
|
+
return dt.toLocaleTimeString('en-IN', {
|
|
206
|
+
timeZone: tz,
|
|
207
|
+
hour: '2-digit',
|
|
208
|
+
minute: '2-digit',
|
|
209
|
+
hour12: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
const h = dt.getUTCHours();
|
|
214
|
+
const m = dt.getUTCMinutes();
|
|
215
|
+
return `${h % 12 || 12}:${m < 10 ? '0' + m : m} ${h < 12 ? 'AM' : 'PM'}`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
let tithi2 = null;
|
|
219
|
+
let nakshatra2 = null;
|
|
220
|
+
let yoga2 = null;
|
|
221
|
+
let karana2 = null;
|
|
222
|
+
let tithiTransitionTime = '';
|
|
223
|
+
let nakshatraTransitionTime = '';
|
|
224
|
+
let yogaTransitionTime = '';
|
|
225
|
+
let karanaTransitionTime = '';
|
|
226
|
+
if (sunset) {
|
|
227
|
+
try {
|
|
228
|
+
const sunsetLons = computeLongitudes(sunset, ayanamsa);
|
|
229
|
+
const tithiAtSunset = (0, tithi_1.calculateTithi)(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
230
|
+
const nakshatraAtSunset = (0, nakshatra_1.calculateNakshatra)(sunsetLons.moonLon);
|
|
231
|
+
const yogaAtSunset = (0, yoga_1.calculateYoga)(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
232
|
+
const karanaAtSunset = (0, karana_1.calculateKarana)(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
233
|
+
const sunriseDate = sunrise ?? noonDate;
|
|
234
|
+
if (tithiAtSunset.number !== tithi.number) {
|
|
235
|
+
tithi2 = tithiAtSunset;
|
|
236
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
237
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
238
|
+
return (0, tithi_1.calculateTithi)(l.sunLon, l.moonLon).number;
|
|
239
|
+
}, tithi.number);
|
|
240
|
+
tithiTransitionTime = formatTransitionTime(transTime, timezone);
|
|
241
|
+
}
|
|
242
|
+
if (nakshatraAtSunset.number !== nakshatra.number) {
|
|
243
|
+
nakshatra2 = nakshatraAtSunset;
|
|
244
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
245
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
246
|
+
return (0, nakshatra_1.calculateNakshatra)(l.moonLon).number;
|
|
247
|
+
}, nakshatra.number);
|
|
248
|
+
nakshatraTransitionTime = formatTransitionTime(transTime, timezone);
|
|
249
|
+
}
|
|
250
|
+
if (yogaAtSunset.number !== yoga.number) {
|
|
251
|
+
yoga2 = yogaAtSunset;
|
|
252
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
253
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
254
|
+
return (0, yoga_1.calculateYoga)(l.sunLon, l.moonLon).number;
|
|
255
|
+
}, yoga.number);
|
|
256
|
+
yogaTransitionTime = formatTransitionTime(transTime, timezone);
|
|
257
|
+
}
|
|
258
|
+
if (karanaAtSunset.number !== karana.number) {
|
|
259
|
+
karana2 = karanaAtSunset;
|
|
260
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
261
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
262
|
+
return (0, karana_1.calculateKarana)(l.sunLon, l.moonLon).number;
|
|
263
|
+
}, karana.number);
|
|
264
|
+
karanaTransitionTime = formatTransitionTime(transTime, timezone);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// Transition detection failed, skip
|
|
269
|
+
}
|
|
270
|
+
}
|
|
183
271
|
// Sun Nakshatra
|
|
184
272
|
const sunNak = (0, nakshatra_1.calculateNakshatra)(sunLon);
|
|
185
273
|
// Ayana: Uttarayana from Makara Sankranti (Sun enters Capricorn, ~270°) to Sun at ~90° (end of Gemini)
|
|
@@ -270,10 +358,22 @@ function calculateFullPanchang(date, latitude, longitude, timezone) {
|
|
|
270
358
|
sunset: sunsetStr,
|
|
271
359
|
moonrise: formatTimeHHMM(moonrise, timezone),
|
|
272
360
|
moonset: formatTimeHHMM(moonset, timezone),
|
|
273
|
-
tithi: [
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
361
|
+
tithi: [
|
|
362
|
+
{ name: tithi.name, number: tithi.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: tithiTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: tithi.progress },
|
|
363
|
+
...(tithi2 ? [{ name: tithi2.name, number: tithi2.number, startTime: tithiTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: tithi2.progress }] : []),
|
|
364
|
+
],
|
|
365
|
+
nakshatra: [
|
|
366
|
+
{ name: nakshatra.name, number: nakshatra.number, pada: nakshatra.pada, lord: nakshatra.lord, deity: nakshatra.deity, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: nakshatraTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: nakshatra.progress },
|
|
367
|
+
...(nakshatra2 ? [{ name: nakshatra2.name, number: nakshatra2.number, pada: nakshatra2.pada, lord: nakshatra2.lord, deity: nakshatra2.deity, startTime: nakshatraTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: nakshatra2.progress }] : []),
|
|
368
|
+
],
|
|
369
|
+
yoga: [
|
|
370
|
+
{ name: yoga.name, number: yoga.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: yogaTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: yoga.progress },
|
|
371
|
+
...(yoga2 ? [{ name: yoga2.name, number: yoga2.number, startTime: yogaTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: yoga2.progress }] : []),
|
|
372
|
+
],
|
|
373
|
+
karana: [
|
|
374
|
+
{ name: karana.name, number: karana.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: karanaTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: karana.progress },
|
|
375
|
+
...(karana2 ? [{ name: karana2.name, number: karana2.number, startTime: karanaTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: karana2.progress }] : []),
|
|
376
|
+
],
|
|
277
377
|
vara,
|
|
278
378
|
moonSign,
|
|
279
379
|
sunSign,
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateFullPanchang = void 0;
|
|
4
|
+
const tithi_1 = require("./core/tithi");
|
|
5
|
+
const nakshatra_1 = require("./core/nakshatra");
|
|
6
|
+
const yoga_1 = require("./core/yoga");
|
|
7
|
+
const karana_1 = require("./core/karana");
|
|
8
|
+
const rashi_1 = require("./core/rashi");
|
|
9
|
+
const constants_1 = require("./core/constants");
|
|
10
|
+
const muhurat_1 = require("./timings/muhurat");
|
|
11
|
+
const kalam_1 = require("./timings/kalam");
|
|
12
|
+
const MOON_PHASES = [
|
|
13
|
+
'New Moon', 'Waxing Crescent', 'First Quarter', 'Waxing Gibbous',
|
|
14
|
+
'Full Moon', 'Waning Gibbous', 'Last Quarter', 'Waning Crescent',
|
|
15
|
+
];
|
|
16
|
+
function getMoonPhaseName(tithiIndex) {
|
|
17
|
+
// Derive phase from tithi number (more accurate than angular division)
|
|
18
|
+
// Tithi 0 = Shukla Pratipad (just after new moon)
|
|
19
|
+
// Tithi 14 = Purnima (full moon)
|
|
20
|
+
// Tithi 15 = Krishna Pratipad (just after full moon)
|
|
21
|
+
// Tithi 29 = Amavasya (new moon)
|
|
22
|
+
if (tithiIndex <= 0)
|
|
23
|
+
return 'New Moon';
|
|
24
|
+
if (tithiIndex <= 3)
|
|
25
|
+
return 'Waxing Crescent';
|
|
26
|
+
if (tithiIndex <= 6)
|
|
27
|
+
return 'First Quarter';
|
|
28
|
+
if (tithiIndex <= 10)
|
|
29
|
+
return 'Waxing Gibbous';
|
|
30
|
+
if (tithiIndex <= 14)
|
|
31
|
+
return 'Full Moon';
|
|
32
|
+
if (tithiIndex <= 18)
|
|
33
|
+
return 'Waning Gibbous';
|
|
34
|
+
if (tithiIndex <= 21)
|
|
35
|
+
return 'Last Quarter';
|
|
36
|
+
if (tithiIndex <= 25)
|
|
37
|
+
return 'Waning Crescent';
|
|
38
|
+
return 'New Moon';
|
|
39
|
+
}
|
|
40
|
+
const RITUS = [
|
|
41
|
+
{ vedic: 'Vasanta', english: 'Spring' },
|
|
42
|
+
{ vedic: 'Grishma', english: 'Summer' },
|
|
43
|
+
{ vedic: 'Varsha', english: 'Monsoon' },
|
|
44
|
+
{ vedic: 'Sharad', english: 'Autumn' },
|
|
45
|
+
{ vedic: 'Hemanta', english: 'Pre-Winter' },
|
|
46
|
+
{ vedic: 'Shishira', english: 'Winter' },
|
|
47
|
+
];
|
|
48
|
+
const SOLAR_MONTHS = [
|
|
49
|
+
'Mesha', 'Vrishabha', 'Mithuna', 'Karka', 'Simha', 'Kanya',
|
|
50
|
+
'Tula', 'Vrishchika', 'Dhanu', 'Makara', 'Kumbha', 'Meena',
|
|
51
|
+
];
|
|
52
|
+
const SAMVATSARS = [
|
|
53
|
+
'Prabhava', 'Vibhava', 'Shukla', 'Pramodoota', 'Prajothpatti',
|
|
54
|
+
'Angirasa', 'Srimukha', 'Bhava', 'Yuva', 'Dhata',
|
|
55
|
+
'Ishvara', 'Bahudhanya', 'Pramathi', 'Vikrama', 'Vrisha',
|
|
56
|
+
'Chitrabhanu', 'Svabhanu', 'Tarana', 'Parthiva', 'Vyaya',
|
|
57
|
+
'Sarvajit', 'Sarvadhari', 'Virodhi', 'Vikrita', 'Khara',
|
|
58
|
+
'Nandana', 'Vijaya', 'Jaya', 'Manmatha', 'Durmukhi',
|
|
59
|
+
'Hevilambi', 'Vilambi', 'Vikari', 'Sharvari', 'Plava',
|
|
60
|
+
'Shubhakrit', 'Shobhakrit', 'Krodhi', 'Vishvavasu', 'Parabhava',
|
|
61
|
+
'Plavanga', 'Kilaka', 'Saumya', 'Sadharana', 'Virodhikrit',
|
|
62
|
+
'Paridhaavi', 'Pramadicha', 'Ananda', 'Rakshasa', 'Nala',
|
|
63
|
+
'Pingala', 'Kalayukti', 'Siddharthi', 'Raudri', 'Durmathi',
|
|
64
|
+
'Dundubhi', 'Rudhirodgari', 'Raktakshi', 'Krodhana', 'Akshaya',
|
|
65
|
+
];
|
|
66
|
+
function calculateDurations(sunriseStr, sunsetStr) {
|
|
67
|
+
const [sh, sm] = sunriseStr.split(':').map(Number);
|
|
68
|
+
const [eh, em] = sunsetStr.split(':').map(Number);
|
|
69
|
+
const sunriseMin = sh * 60 + sm;
|
|
70
|
+
const sunsetMin = eh * 60 + em;
|
|
71
|
+
const dayMins = sunsetMin - sunriseMin;
|
|
72
|
+
const nightMins = 1440 - dayMins;
|
|
73
|
+
const midMins = sunriseMin + Math.floor(dayMins / 2);
|
|
74
|
+
const fmtDuration = (mins) => {
|
|
75
|
+
const h = Math.floor(mins / 60);
|
|
76
|
+
const m = mins % 60;
|
|
77
|
+
return `${h}h ${String(m).padStart(2, '0')}m`;
|
|
78
|
+
};
|
|
79
|
+
const fmtTime = (mins) => {
|
|
80
|
+
const h = mins % 1440;
|
|
81
|
+
const hh = Math.floor(h / 60);
|
|
82
|
+
const mm = h % 60;
|
|
83
|
+
return `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`;
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
dinamana: fmtDuration(dayMins),
|
|
87
|
+
ratrimana: fmtDuration(nightMins),
|
|
88
|
+
madhyahna: fmtTime(midMins),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function formatTimeHHMM(date, timezone) {
|
|
92
|
+
if (!date)
|
|
93
|
+
return '';
|
|
94
|
+
return date.toLocaleTimeString('en-US', { timeZone: timezone, hour: '2-digit', minute: '2-digit', hour12: false });
|
|
95
|
+
}
|
|
96
|
+
function calculateFullPanchang(date, latitude, longitude, timezone) {
|
|
97
|
+
// Use noon local time as an initial reference for sunrise/sunset calculation.
|
|
98
|
+
// After computing sunrise, we re-compute planetary positions at sunrise
|
|
99
|
+
// to match Drik Panchang convention (prevailing tithi/nakshatra at sunrise).
|
|
100
|
+
const noonDate = typeof date === 'string'
|
|
101
|
+
? new Date(date + 'T12:00:00')
|
|
102
|
+
: date;
|
|
103
|
+
const location = { latitude, longitude, timezone };
|
|
104
|
+
// Helper: compute sidereal sun/moon longitudes for a given Date
|
|
105
|
+
function computeLongitudes(dt, ayan) {
|
|
106
|
+
try {
|
|
107
|
+
const { Ephemeris } = require('./calculations/ephemeris');
|
|
108
|
+
const eph = new Ephemeris();
|
|
109
|
+
const sunT = eph.calculatePosition(dt, 'Sun');
|
|
110
|
+
const moonT = eph.calculatePosition(dt, 'Moon');
|
|
111
|
+
return {
|
|
112
|
+
sunLon: (0, constants_1.normalizeAngle)(sunT.longitude - ayan),
|
|
113
|
+
moonLon: (0, constants_1.normalizeAngle)(moonT.longitude - ayan),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Fallback: Jean Meeus low-precision
|
|
118
|
+
const jd = dt.getTime() / 86400000 + 2440587.5;
|
|
119
|
+
const T = (jd - 2451545.0) / 36525;
|
|
120
|
+
const L0 = (0, constants_1.normalizeAngle)(280.46646 + 36000.76983 * T);
|
|
121
|
+
const M = (0, constants_1.normalizeAngle)(357.52911 + 35999.05029 * T);
|
|
122
|
+
const Mrad = M * Math.PI / 180;
|
|
123
|
+
const C = (1.914602 - 0.004817 * T) * Math.sin(Mrad)
|
|
124
|
+
+ 0.019993 * Math.sin(2 * Mrad)
|
|
125
|
+
+ 0.000289 * Math.sin(3 * Mrad);
|
|
126
|
+
const sLon = (0, constants_1.normalizeAngle)((0, constants_1.normalizeAngle)(L0 + C) - ayan);
|
|
127
|
+
const Lm = (0, constants_1.normalizeAngle)(218.3165 + 481267.8813 * T);
|
|
128
|
+
const Mm = (0, constants_1.normalizeAngle)(134.9634 + 477198.8676 * T);
|
|
129
|
+
const F = (0, constants_1.normalizeAngle)(93.2721 + 483202.0175 * T);
|
|
130
|
+
const MmRad = Mm * Math.PI / 180;
|
|
131
|
+
const FRad = F * Math.PI / 180;
|
|
132
|
+
const moonCorr = 6.289 * Math.sin(MmRad)
|
|
133
|
+
- 1.274 * Math.sin(MmRad - 2 * FRad)
|
|
134
|
+
+ 0.658 * Math.sin(2 * FRad)
|
|
135
|
+
- 0.186 * Math.sin(Mrad)
|
|
136
|
+
- 0.114 * Math.sin(2 * FRad)
|
|
137
|
+
+ 0.059 * Math.sin(2 * MmRad)
|
|
138
|
+
- 0.057 * Math.sin(MmRad - 2 * FRad + Mrad)
|
|
139
|
+
+ 0.053 * Math.sin(MmRad + 2 * FRad)
|
|
140
|
+
+ 0.046 * Math.sin(2 * FRad - Mrad)
|
|
141
|
+
+ 0.041 * Math.sin(MmRad - Mrad);
|
|
142
|
+
const mLon = (0, constants_1.normalizeAngle)((0, constants_1.normalizeAngle)(Lm + moonCorr) - ayan);
|
|
143
|
+
return { sunLon: sLon, moonLon: mLon };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Try to use Swiss Ephemeris, fall back to SunCalc-based calculations
|
|
147
|
+
let sunLon = 0, moonLon = 0;
|
|
148
|
+
let sunrise = null, sunset = null;
|
|
149
|
+
let moonrise = null, moonset = null;
|
|
150
|
+
let ayanamsa = 24.17; // Default Lahiri approximation for 2026
|
|
151
|
+
try {
|
|
152
|
+
const { Ephemeris } = require('./calculations/ephemeris');
|
|
153
|
+
const ephemeris = new Ephemeris();
|
|
154
|
+
ayanamsa = ephemeris.calculate_lahiri_ayanamsa(noonDate);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Use default ayanamsa
|
|
158
|
+
}
|
|
159
|
+
// Get sunrise/sunset first
|
|
160
|
+
try {
|
|
161
|
+
const SunCalc = require('suncalc');
|
|
162
|
+
const times = SunCalc.getTimes(noonDate, latitude, longitude);
|
|
163
|
+
sunrise = times.sunrise;
|
|
164
|
+
sunset = times.sunset;
|
|
165
|
+
const moonTimes = SunCalc.getMoonTimes(noonDate, latitude, longitude);
|
|
166
|
+
moonrise = moonTimes.rise ?? null;
|
|
167
|
+
moonset = moonTimes.set ?? null;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// No SunCalc available
|
|
171
|
+
}
|
|
172
|
+
// Compute longitudes at SUNRISE (Drik Panchang convention: prevailing tithi at sunrise)
|
|
173
|
+
const sunriseDate = sunrise ?? noonDate;
|
|
174
|
+
const lons = computeLongitudes(sunriseDate, ayanamsa);
|
|
175
|
+
sunLon = lons.sunLon;
|
|
176
|
+
moonLon = lons.moonLon;
|
|
177
|
+
const tithi = (0, tithi_1.calculateTithi)(sunLon, moonLon);
|
|
178
|
+
const nakshatra = (0, nakshatra_1.calculateNakshatra)(moonLon);
|
|
179
|
+
const yoga = (0, yoga_1.calculateYoga)(sunLon, moonLon);
|
|
180
|
+
const karana = (0, karana_1.calculateKarana)(sunLon, moonLon);
|
|
181
|
+
const moonSign = (0, rashi_1.calculateRashi)(moonLon);
|
|
182
|
+
const sunSign = (0, rashi_1.calculateRashi)(sunLon);
|
|
183
|
+
// ── Transition detection with binary search for exact time ──────────────
|
|
184
|
+
// Check sunrise vs sunset. If tithi/nakshatra/yoga/karana changed,
|
|
185
|
+
// binary-search for the exact transition moment.
|
|
186
|
+
function findTransitionTime(startDate, endDate, getValueFn, startValue, maxIterations = 14) {
|
|
187
|
+
let lo = startDate.getTime();
|
|
188
|
+
let hi = endDate.getTime();
|
|
189
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
190
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
191
|
+
const midDate = new Date(mid);
|
|
192
|
+
const lons = computeLongitudes(midDate, ayanamsa);
|
|
193
|
+
const val = getValueFn(midDate);
|
|
194
|
+
if (val === startValue) {
|
|
195
|
+
lo = mid;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
hi = mid;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return new Date(hi);
|
|
202
|
+
}
|
|
203
|
+
function formatTransitionTime(dt, tz) {
|
|
204
|
+
try {
|
|
205
|
+
return dt.toLocaleTimeString('en-IN', {
|
|
206
|
+
timeZone: tz,
|
|
207
|
+
hour: '2-digit',
|
|
208
|
+
minute: '2-digit',
|
|
209
|
+
hour12: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
const h = dt.getUTCHours();
|
|
214
|
+
const m = dt.getUTCMinutes();
|
|
215
|
+
return `${h % 12 || 12}:${m < 10 ? '0' + m : m} ${h < 12 ? 'AM' : 'PM'}`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
let tithi2 = null;
|
|
219
|
+
let nakshatra2 = null;
|
|
220
|
+
let yoga2 = null;
|
|
221
|
+
let karana2 = null;
|
|
222
|
+
let tithiTransitionTime = '';
|
|
223
|
+
let nakshatraTransitionTime = '';
|
|
224
|
+
let yogaTransitionTime = '';
|
|
225
|
+
let karanaTransitionTime = '';
|
|
226
|
+
if (sunset) {
|
|
227
|
+
try {
|
|
228
|
+
const sunsetLons = computeLongitudes(sunset, ayanamsa);
|
|
229
|
+
const tithiAtSunset = (0, tithi_1.calculateTithi)(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
230
|
+
const nakshatraAtSunset = (0, nakshatra_1.calculateNakshatra)(sunsetLons.moonLon);
|
|
231
|
+
const yogaAtSunset = (0, yoga_1.calculateYoga)(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
232
|
+
const karanaAtSunset = (0, karana_1.calculateKarana)(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
233
|
+
const sunriseDate = sunrise ?? noonDate;
|
|
234
|
+
if (tithiAtSunset.number !== tithi.number) {
|
|
235
|
+
tithi2 = tithiAtSunset;
|
|
236
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
237
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
238
|
+
return (0, tithi_1.calculateTithi)(l.sunLon, l.moonLon).number;
|
|
239
|
+
}, tithi.number);
|
|
240
|
+
tithiTransitionTime = formatTransitionTime(transTime, timezone);
|
|
241
|
+
}
|
|
242
|
+
if (nakshatraAtSunset.number !== nakshatra.number) {
|
|
243
|
+
nakshatra2 = nakshatraAtSunset;
|
|
244
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
245
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
246
|
+
return (0, nakshatra_1.calculateNakshatra)(l.moonLon).number;
|
|
247
|
+
}, nakshatra.number);
|
|
248
|
+
nakshatraTransitionTime = formatTransitionTime(transTime, timezone);
|
|
249
|
+
}
|
|
250
|
+
if (yogaAtSunset.number !== yoga.number) {
|
|
251
|
+
yoga2 = yogaAtSunset;
|
|
252
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
253
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
254
|
+
return (0, yoga_1.calculateYoga)(l.sunLon, l.moonLon).number;
|
|
255
|
+
}, yoga.number);
|
|
256
|
+
yogaTransitionTime = formatTransitionTime(transTime, timezone);
|
|
257
|
+
}
|
|
258
|
+
if (karanaAtSunset.number !== karana.number) {
|
|
259
|
+
karana2 = karanaAtSunset;
|
|
260
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
261
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
262
|
+
return (0, karana_1.calculateKarana)(l.sunLon, l.moonLon).number;
|
|
263
|
+
}, karana.number);
|
|
264
|
+
karanaTransitionTime = formatTransitionTime(transTime, timezone);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// Transition detection failed, skip
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Sun Nakshatra
|
|
272
|
+
const sunNak = (0, nakshatra_1.calculateNakshatra)(sunLon);
|
|
273
|
+
// Ayana: Uttarayana from Makara Sankranti (Sun enters Capricorn, ~270°) to Sun at ~90° (end of Gemini)
|
|
274
|
+
// Sidereal: 270°→360°→0°→90° = Uttarayana; 90°→270° = Dakshinayana
|
|
275
|
+
const ayana = (sunLon >= 270 || sunLon < 90) ? 'Uttarayana' : 'Dakshinayana';
|
|
276
|
+
// Ritu (season) based on Sun's sidereal sign
|
|
277
|
+
// Ritu mapping (North Indian): Pisces-Aries=Vasanta, Taurus-Gemini=Grishma, etc.
|
|
278
|
+
// Shift by -1 from sign number: sign 12(Pisces)→0, sign 1(Aries)→0, sign 2→1, etc.
|
|
279
|
+
const rituSignMap = {
|
|
280
|
+
12: 0, 1: 0,
|
|
281
|
+
2: 1, 3: 1,
|
|
282
|
+
4: 2, 5: 2,
|
|
283
|
+
6: 3, 7: 3,
|
|
284
|
+
8: 4, 9: 4,
|
|
285
|
+
10: 5, 11: 5, // Shishira (Capricorn, Aquarius)
|
|
286
|
+
};
|
|
287
|
+
const ritu = RITUS[rituSignMap[sunSign.number] ?? 0];
|
|
288
|
+
// Solar month
|
|
289
|
+
const solarMonth = SOLAR_MONTHS[sunSign.number - 1];
|
|
290
|
+
// Samvatsar (60-year cycle)
|
|
291
|
+
// Vikram new year = Chaitra Shukla Pratipad (day after Chaitra Amavasya)
|
|
292
|
+
// This falls when Sun is in Pisces (Meena) and Moon is new → Shukla Pratipad
|
|
293
|
+
// Approximation: if Sun is in Pisces (sign 12) and tithi is in Shukla Paksha,
|
|
294
|
+
// we're in the new Vikram year. Otherwise check if we've passed the spring new moon.
|
|
295
|
+
//
|
|
296
|
+
// More robust: The Vikram year changes when Chaitra Shukla Paksha begins.
|
|
297
|
+
// Chaitra = lunar month when Sun is in Pisces/Aries.
|
|
298
|
+
// If tithi is Shukla (waxing) and Sun is in Pisces → new year has started.
|
|
299
|
+
// If tithi is Krishna (waning) and Sun is in Pisces → still old year (Chaitra Krishna = Phalguna Amanta).
|
|
300
|
+
const gregYear = noonDate.getFullYear();
|
|
301
|
+
const sunInPisces = sunSign.number === 12; // Meena
|
|
302
|
+
const sunInAries = sunSign.number === 1; // Mesha
|
|
303
|
+
const isShukla = tithi.paksha === 'Shukla';
|
|
304
|
+
// New year starts when: Sun in Pisces + Shukla Paksha (Chaitra Shukla)
|
|
305
|
+
// OR Sun has moved past Pisces into Aries+ (definitely new year)
|
|
306
|
+
const newYearStarted = (sunInPisces && isShukla) ||
|
|
307
|
+
(sunSign.number >= 1 && sunSign.number <= 6); // Aries through Virgo = after spring
|
|
308
|
+
// But Jan-Feb is always before new year (Sun in Capricorn/Aquarius)
|
|
309
|
+
const month = noonDate.getMonth();
|
|
310
|
+
const isEarlyYear = month <= 1; // Jan, Feb always before Hindu new year
|
|
311
|
+
const vikramSamvat = (newYearStarted && !isEarlyYear) ? gregYear + 57 : gregYear + 56;
|
|
312
|
+
const shakaSamvat = (newYearStarted && !isEarlyYear) ? gregYear - 78 : gregYear - 79;
|
|
313
|
+
const samvatsarIndex = ((vikramSamvat - 51) % 60 + 60) % 60;
|
|
314
|
+
const samvatsar = SAMVATSARS[samvatsarIndex];
|
|
315
|
+
// Use the date string to determine weekday (timezone-independent)
|
|
316
|
+
const varaDateStr = typeof date === 'string' ? date : noonDate.toISOString().split('T')[0];
|
|
317
|
+
const varaDate = new Date(varaDateStr + 'T12:00:00Z'); // Noon UTC — safe for any timezone
|
|
318
|
+
const vara = {
|
|
319
|
+
name: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][varaDate.getUTCDay()],
|
|
320
|
+
number: varaDate.getUTCDay(),
|
|
321
|
+
};
|
|
322
|
+
const diff = (0, constants_1.normalizeAngle)(moonLon - sunLon);
|
|
323
|
+
// Use SunCalc for accurate illumination when available
|
|
324
|
+
let moonIllumination = Math.round(((1 - Math.cos(diff * Math.PI / 180)) / 2) * 100);
|
|
325
|
+
try {
|
|
326
|
+
const SunCalc = require('suncalc');
|
|
327
|
+
const illum = SunCalc.getMoonIllumination(noonDate);
|
|
328
|
+
moonIllumination = Math.round(illum.fraction * 100);
|
|
329
|
+
}
|
|
330
|
+
catch { }
|
|
331
|
+
;
|
|
332
|
+
const sunriseStr = formatTimeHHMM(sunrise, timezone);
|
|
333
|
+
const sunsetStr = formatTimeHHMM(sunset, timezone);
|
|
334
|
+
const dateStr = noonDate.toISOString().split('T')[0];
|
|
335
|
+
// Calculate auspicious muhurats and inauspicious kalams
|
|
336
|
+
let auspiciousMuhurats = [];
|
|
337
|
+
let inauspiciousKalams = [];
|
|
338
|
+
if (sunriseStr && sunsetStr) {
|
|
339
|
+
try {
|
|
340
|
+
auspiciousMuhurats = (0, muhurat_1.calculateMuhurats)(sunriseStr, sunsetStr, dateStr, latitude, longitude, timezone);
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
// Timing calculation failed; return empty array
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
inauspiciousKalams = (0, kalam_1.calculateKalams)(sunriseStr, sunsetStr, dateStr, latitude, longitude, timezone);
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
// Timing calculation failed; return empty array
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Dinamana / Ratrimana / Madhyahna
|
|
353
|
+
const durations = (sunriseStr && sunsetStr) ? calculateDurations(sunriseStr, sunsetStr) : { dinamana: '', ratrimana: '', madhyahna: '' };
|
|
354
|
+
return {
|
|
355
|
+
date: dateStr,
|
|
356
|
+
location: { lat: latitude, lon: longitude, timezone },
|
|
357
|
+
sunrise: sunriseStr,
|
|
358
|
+
sunset: sunsetStr,
|
|
359
|
+
moonrise: formatTimeHHMM(moonrise, timezone),
|
|
360
|
+
moonset: formatTimeHHMM(moonset, timezone),
|
|
361
|
+
tithi: [
|
|
362
|
+
{ name: tithi.name, number: tithi.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: tithiTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: tithi.progress },
|
|
363
|
+
...(tithi2 ? [{ name: tithi2.name, number: tithi2.number, startTime: tithiTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: tithi2.progress }] : []),
|
|
364
|
+
],
|
|
365
|
+
nakshatra: [
|
|
366
|
+
{ name: nakshatra.name, number: nakshatra.number, pada: nakshatra.pada, lord: nakshatra.lord, deity: nakshatra.deity, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: nakshatraTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: nakshatra.progress },
|
|
367
|
+
...(nakshatra2 ? [{ name: nakshatra2.name, number: nakshatra2.number, pada: nakshatra2.pada, lord: nakshatra2.lord, deity: nakshatra2.deity, startTime: nakshatraTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: nakshatra2.progress }] : []),
|
|
368
|
+
],
|
|
369
|
+
yoga: [
|
|
370
|
+
{ name: yoga.name, number: yoga.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: yogaTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: yoga.progress },
|
|
371
|
+
...(yoga2 ? [{ name: yoga2.name, number: yoga2.number, startTime: yogaTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: yoga2.progress }] : []),
|
|
372
|
+
],
|
|
373
|
+
karana: [
|
|
374
|
+
{ name: karana.name, number: karana.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: karanaTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: karana.progress },
|
|
375
|
+
...(karana2 ? [{ name: karana2.name, number: karana2.number, startTime: karanaTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: karana2.progress }] : []),
|
|
376
|
+
],
|
|
377
|
+
vara,
|
|
378
|
+
moonSign,
|
|
379
|
+
sunSign,
|
|
380
|
+
moonPhase: { name: getMoonPhaseName(tithi.tithiIndex - 1), illumination: moonIllumination },
|
|
381
|
+
paksha: tithi.paksha,
|
|
382
|
+
auspiciousMuhurats,
|
|
383
|
+
inauspiciousKalams,
|
|
384
|
+
sunNakshatra: { name: sunNak.name, number: sunNak.number, pada: sunNak.pada, lord: sunNak.lord, deity: sunNak.deity, startTime: '', endTime: '', progress: sunNak.progress },
|
|
385
|
+
ayana,
|
|
386
|
+
ritu,
|
|
387
|
+
solarMonth,
|
|
388
|
+
dinamana: durations.dinamana,
|
|
389
|
+
ratrimana: durations.ratrimana,
|
|
390
|
+
madhyahna: durations.madhyahna,
|
|
391
|
+
samvatsar,
|
|
392
|
+
vikramSamvat: vikramSamvat,
|
|
393
|
+
shakaSamvat: shakaSamvat,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
exports.calculateFullPanchang = calculateFullPanchang;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Kalam (inauspicious timing) calculations for Panchang v2.
|
|
4
|
+
* Wraps existing lib/ functions with typed interfaces.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.calculateKalams = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Strip seconds from "HH:mm:ss" -> "HH:mm", or return as-is if already "HH:mm".
|
|
10
|
+
*/
|
|
11
|
+
function toHHMM(time) {
|
|
12
|
+
const parts = time.split(':');
|
|
13
|
+
return `${parts[0]}:${parts[1]}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse ISO timestamp or "HH:mm:ss" to "HH:mm".
|
|
17
|
+
*/
|
|
18
|
+
function normalizeTime(time) {
|
|
19
|
+
// Handle ISO format like "2026-03-20T06:10:00.000+05:30"
|
|
20
|
+
if (time.includes('T')) {
|
|
21
|
+
const timePart = time.split('T')[1];
|
|
22
|
+
const parts = timePart.split(':');
|
|
23
|
+
return `${parts[0]}:${parts[1]}`;
|
|
24
|
+
}
|
|
25
|
+
return toHHMM(time);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Calculate all inauspicious kalams for a given date.
|
|
29
|
+
*
|
|
30
|
+
* @param sunrise - "HH:mm" format
|
|
31
|
+
* @param sunset - "HH:mm" format
|
|
32
|
+
* @param date - "yyyy-MM-dd" format
|
|
33
|
+
* @param lat - latitude
|
|
34
|
+
* @param lon - longitude
|
|
35
|
+
* @param tz - IANA timezone
|
|
36
|
+
*/
|
|
37
|
+
function calculateKalams(sunrise, sunset, date, lat, lon, tz) {
|
|
38
|
+
const kalams = [];
|
|
39
|
+
// Ensure sunrise/sunset have seconds for lib functions that expect "HH:mm:ss"
|
|
40
|
+
const sunriseWithSec = sunrise.length === 5 ? sunrise + ':00' : sunrise;
|
|
41
|
+
const sunsetWithSec = sunset.length === 5 ? sunset + ':00' : sunset;
|
|
42
|
+
// 1. Rahu Kalam
|
|
43
|
+
try {
|
|
44
|
+
const calculateRahuKalam = require('../../../lib/rahuKalam');
|
|
45
|
+
const rahu = calculateRahuKalam(date, sunriseWithSec, sunsetWithSec, tz);
|
|
46
|
+
kalams.push({
|
|
47
|
+
name: 'Rahu Kalam',
|
|
48
|
+
startTime: toHHMM(rahu.start),
|
|
49
|
+
endTime: toHHMM(rahu.end),
|
|
50
|
+
description: 'Inauspicious period ruled by Rahu; avoid new ventures & travel',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Skip if lib unavailable
|
|
55
|
+
}
|
|
56
|
+
// 2. Gulika Kalam (same segment-based calculation as Rahu)
|
|
57
|
+
try {
|
|
58
|
+
const calculateGulikaKalam = require('../../../lib/gulikaKalam');
|
|
59
|
+
const gulikaResult = calculateGulikaKalam(date, sunriseWithSec, sunsetWithSec, tz);
|
|
60
|
+
if (gulikaResult && gulikaResult.start) {
|
|
61
|
+
kalams.push({
|
|
62
|
+
name: 'Gulika Kalam',
|
|
63
|
+
startTime: toHHMM(gulikaResult.start),
|
|
64
|
+
endTime: toHHMM(gulikaResult.end),
|
|
65
|
+
description: 'Inauspicious period ruled by Saturn\'s son Gulika; avoid auspicious activities',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Skip if lib unavailable
|
|
71
|
+
}
|
|
72
|
+
// 3. Yamaganda (Yamghant Kalam)
|
|
73
|
+
try {
|
|
74
|
+
const calculateYamghantKalam = require('../../../lib/yamghantKalam');
|
|
75
|
+
const yama = calculateYamghantKalam(date, sunriseWithSec, sunsetWithSec, tz);
|
|
76
|
+
kalams.push({
|
|
77
|
+
name: 'Yamaganda',
|
|
78
|
+
startTime: toHHMM(yama.start),
|
|
79
|
+
endTime: toHHMM(yama.end),
|
|
80
|
+
description: 'Inauspicious period ruled by Yama; avoid important activities',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Skip if lib unavailable
|
|
85
|
+
}
|
|
86
|
+
// 4. Varjyam — nakshatra-based timing (placeholder with note)
|
|
87
|
+
kalams.push({
|
|
88
|
+
name: 'Varjyam',
|
|
89
|
+
startTime: '',
|
|
90
|
+
endTime: '',
|
|
91
|
+
description: 'Nakshatra-dependent inauspicious period (requires precise nakshatra transition times)',
|
|
92
|
+
});
|
|
93
|
+
// 5. Dur Muhurtam
|
|
94
|
+
try {
|
|
95
|
+
const calculateDurMuhurtam = require('../../../lib/durMuhurtam');
|
|
96
|
+
const durPeriods = calculateDurMuhurtam(date, sunriseWithSec, sunsetWithSec, tz);
|
|
97
|
+
durPeriods.forEach((period, index) => {
|
|
98
|
+
const startTime = normalizeTime(period.start);
|
|
99
|
+
const endTime = normalizeTime(period.end);
|
|
100
|
+
const label = durPeriods.length > 1 ? `Dur Muhurtam ${index + 1}` : 'Dur Muhurtam';
|
|
101
|
+
kalams.push({
|
|
102
|
+
name: label,
|
|
103
|
+
startTime,
|
|
104
|
+
endTime,
|
|
105
|
+
description: 'Inauspicious muhurta; avoid starting new activities during this period',
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Skip if lib unavailable
|
|
111
|
+
}
|
|
112
|
+
return kalams;
|
|
113
|
+
}
|
|
114
|
+
exports.calculateKalams = calculateKalams;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Muhurat (auspicious timing) calculations for Panchang v2.
|
|
4
|
+
* Wraps existing lib/ functions and adds computed muhurats.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.calculateMuhurats = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Parse "HH:mm" or "HH:mm:ss" into total minutes from midnight.
|
|
10
|
+
*/
|
|
11
|
+
function parseTimeToMinutes(time) {
|
|
12
|
+
const parts = time.split(':').map(Number);
|
|
13
|
+
return parts[0] * 60 + parts[1];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Convert total minutes from midnight to "HH:mm" string.
|
|
17
|
+
* Handles wrap-around past midnight (mod 1440).
|
|
18
|
+
*/
|
|
19
|
+
function minutesToHHMM(minutes) {
|
|
20
|
+
let m = Math.round(minutes) % 1440;
|
|
21
|
+
if (m < 0)
|
|
22
|
+
m += 1440;
|
|
23
|
+
const h = Math.floor(m / 60);
|
|
24
|
+
const min = m % 60;
|
|
25
|
+
return `${h.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Strip seconds from "HH:mm:ss" -> "HH:mm", or return as-is if already "HH:mm".
|
|
29
|
+
*/
|
|
30
|
+
function toHHMM(time) {
|
|
31
|
+
const parts = time.split(':');
|
|
32
|
+
return `${parts[0]}:${parts[1]}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Calculate all auspicious muhurats for a given date.
|
|
36
|
+
*
|
|
37
|
+
* @param sunrise - "HH:mm" format
|
|
38
|
+
* @param sunset - "HH:mm" format
|
|
39
|
+
* @param date - "yyyy-MM-dd" format
|
|
40
|
+
* @param lat - latitude
|
|
41
|
+
* @param lon - longitude
|
|
42
|
+
* @param tz - IANA timezone
|
|
43
|
+
*/
|
|
44
|
+
function calculateMuhurats(sunrise, sunset, date, lat, lon, tz) {
|
|
45
|
+
const sunriseMin = parseTimeToMinutes(sunrise);
|
|
46
|
+
const sunsetMin = parseTimeToMinutes(sunset);
|
|
47
|
+
const dayDuration = sunsetMin - sunriseMin; // minutes of daytime
|
|
48
|
+
const muhurtaDuration = dayDuration / 15; // one daytime muhurta
|
|
49
|
+
// Night duration (sunset to next sunrise, approximated as 24h - dayDuration)
|
|
50
|
+
const nightDuration = 1440 - dayDuration;
|
|
51
|
+
const muhurats = [];
|
|
52
|
+
// 1. Brahma Muhurta — 2 muhurtas before sunrise (1h 36min before, lasts 48 min)
|
|
53
|
+
const brahmaStart = sunriseMin - (muhurtaDuration * 2);
|
|
54
|
+
const brahmaEnd = sunriseMin - muhurtaDuration;
|
|
55
|
+
muhurats.push({
|
|
56
|
+
name: 'Brahma Muhurta',
|
|
57
|
+
startTime: minutesToHHMM(brahmaStart),
|
|
58
|
+
endTime: minutesToHHMM(brahmaEnd),
|
|
59
|
+
description: 'Most auspicious time for meditation & spiritual practice, 2 muhurtas before sunrise',
|
|
60
|
+
});
|
|
61
|
+
// 2. Pratah Sandhya — from ~1h12m (1.5 muhurtas) before sunrise to sunrise
|
|
62
|
+
// Drik Panchang uses: start = sunrise - (nightMuhurta * 1.5), end = sunrise
|
|
63
|
+
const nightMuhurta = nightDuration / 15;
|
|
64
|
+
const pratahStart = sunriseMin - (nightMuhurta * 1.5);
|
|
65
|
+
muhurats.push({
|
|
66
|
+
name: 'Pratah Sandhya',
|
|
67
|
+
startTime: minutesToHHMM(pratahStart),
|
|
68
|
+
endTime: minutesToHHMM(sunriseMin),
|
|
69
|
+
description: 'Morning twilight period, ideal for Sandhyavandanam',
|
|
70
|
+
});
|
|
71
|
+
// 3. Abhijit Muhurat — use existing lib function
|
|
72
|
+
try {
|
|
73
|
+
const calculateAbhijeetMuhurt = require('../../../lib/abhijeetMuhurt');
|
|
74
|
+
// The lib expects sunrise/sunset with seconds
|
|
75
|
+
const sunriseWithSec = sunrise.length === 5 ? sunrise + ':00' : sunrise;
|
|
76
|
+
const sunsetWithSec = sunset.length === 5 ? sunset + ':00' : sunset;
|
|
77
|
+
const abhijit = calculateAbhijeetMuhurt(date, sunriseWithSec, sunsetWithSec, lat, lon, tz);
|
|
78
|
+
muhurats.push({
|
|
79
|
+
name: 'Abhijit Muhurat',
|
|
80
|
+
startTime: toHHMM(abhijit.start),
|
|
81
|
+
endTime: toHHMM(abhijit.end),
|
|
82
|
+
description: 'Midday auspicious period ruled by Lord Vishnu, ideal for important tasks',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Fallback: compute manually (muhurta #8, i.e. index 7-8 after sunrise)
|
|
87
|
+
const abhijitStart = sunriseMin + muhurtaDuration * 7;
|
|
88
|
+
const abhijitEnd = sunriseMin + muhurtaDuration * 9;
|
|
89
|
+
muhurats.push({
|
|
90
|
+
name: 'Abhijit Muhurat',
|
|
91
|
+
startTime: minutesToHHMM(abhijitStart),
|
|
92
|
+
endTime: minutesToHHMM(abhijitEnd),
|
|
93
|
+
description: 'Midday auspicious period ruled by Lord Vishnu, ideal for important tasks',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// 4. Vijaya Muhurat — the muhurta spanning the midpoint between Abhijit and sunset
|
|
97
|
+
// Drik Panchang: roughly the 11th muhurta (index 10) from sunrise
|
|
98
|
+
const vijayaStart = sunriseMin + muhurtaDuration * 10;
|
|
99
|
+
const vijayaEnd = sunriseMin + muhurtaDuration * 11;
|
|
100
|
+
muhurats.push({
|
|
101
|
+
name: 'Vijaya Muhurat',
|
|
102
|
+
startTime: minutesToHHMM(vijayaStart),
|
|
103
|
+
endTime: minutesToHHMM(vijayaEnd),
|
|
104
|
+
description: 'Afternoon auspicious period for victory & success in endeavors',
|
|
105
|
+
});
|
|
106
|
+
// 5. Godhuli Muhurat — starts ~1 min before sunset, lasts ~24 min (sunset-1 to sunset+23)
|
|
107
|
+
// Drik Panchang pattern: from ~sunset to sunset + half a nightMuhurta
|
|
108
|
+
muhurats.push({
|
|
109
|
+
name: 'Godhuli Muhurat',
|
|
110
|
+
startTime: minutesToHHMM(sunsetMin - 1),
|
|
111
|
+
endTime: minutesToHHMM(sunsetMin + 22),
|
|
112
|
+
description: 'Cow-dust time around sunset, auspicious for marriages & ceremonies',
|
|
113
|
+
});
|
|
114
|
+
// 6. Sayahna Sandhya — from sunset to sunset + 1.5 night muhurtas
|
|
115
|
+
// Mirrors Pratah Sandhya: Pratah = 1.5 night muhurtas before sunrise, Sayahna = 1.5 after sunset
|
|
116
|
+
const sayahnaEnd = sunsetMin + (nightMuhurta * 1.5);
|
|
117
|
+
muhurats.push({
|
|
118
|
+
name: 'Sayahna Sandhya',
|
|
119
|
+
startTime: minutesToHHMM(sunsetMin),
|
|
120
|
+
endTime: minutesToHHMM(sayahnaEnd),
|
|
121
|
+
description: 'Evening twilight period, ideal for evening prayers & Sandhyavandanam',
|
|
122
|
+
});
|
|
123
|
+
// 7. Nishita Muhurat — midnight +/- 24 min
|
|
124
|
+
// Midnight = midpoint between sunset and next sunrise
|
|
125
|
+
const midnightMin = sunsetMin + nightDuration / 2;
|
|
126
|
+
muhurats.push({
|
|
127
|
+
name: 'Nishita Muhurat',
|
|
128
|
+
startTime: minutesToHHMM(midnightMin - 24),
|
|
129
|
+
endTime: minutesToHHMM(midnightMin + 24),
|
|
130
|
+
description: 'Midnight auspicious period, sacred for worship of Lord Shiva',
|
|
131
|
+
});
|
|
132
|
+
return muhurats;
|
|
133
|
+
}
|
|
134
|
+
exports.calculateMuhurats = calculateMuhurats;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for Panchang v2 calculations.
|
|
4
|
+
* NOTE: The existing types/panchang.ts is preserved as-is — do NOT import from here
|
|
5
|
+
* in files that already import from '../types/panchang'.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astrology-insights",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Comprehensive Vedic astrology engine for Node.js — Panchang, birth charts (Kundli), Vimshottari Dasha, divisional charts, dosha analysis, and planetary remedies. Swiss Ephemeris precision, validated against Drik Panchang.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"react-native": "index.rn.js",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"homepage": "https://github.com/adarshsrii/astrology#readme",
|
|
62
62
|
"dependencies": {
|
|
63
|
+
"date-fns": "^4.1.0",
|
|
63
64
|
"date-fns-tz": "^3.2.0",
|
|
64
65
|
"luxon": "^3.5.0",
|
|
65
66
|
"suncalc": "^1.9.0",
|
|
@@ -190,6 +190,107 @@ export function calculateFullPanchang(
|
|
|
190
190
|
const moonSign = calculateRashi(moonLon);
|
|
191
191
|
const sunSign = calculateRashi(sunLon);
|
|
192
192
|
|
|
193
|
+
// ── Transition detection with binary search for exact time ──────────────
|
|
194
|
+
// Check sunrise vs sunset. If tithi/nakshatra/yoga/karana changed,
|
|
195
|
+
// binary-search for the exact transition moment.
|
|
196
|
+
|
|
197
|
+
function findTransitionTime(
|
|
198
|
+
startDate: Date,
|
|
199
|
+
endDate: Date,
|
|
200
|
+
getValueFn: (dt: Date) => number,
|
|
201
|
+
startValue: number,
|
|
202
|
+
maxIterations: number = 14, // ~1 minute precision over 12 hours
|
|
203
|
+
): Date {
|
|
204
|
+
let lo = startDate.getTime();
|
|
205
|
+
let hi = endDate.getTime();
|
|
206
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
207
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
208
|
+
const midDate = new Date(mid);
|
|
209
|
+
const lons = computeLongitudes(midDate, ayanamsa);
|
|
210
|
+
const val = getValueFn(midDate);
|
|
211
|
+
if (val === startValue) {
|
|
212
|
+
lo = mid;
|
|
213
|
+
} else {
|
|
214
|
+
hi = mid;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return new Date(hi);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function formatTransitionTime(dt: Date, tz: string): string {
|
|
221
|
+
try {
|
|
222
|
+
return dt.toLocaleTimeString('en-IN', {
|
|
223
|
+
timeZone: tz,
|
|
224
|
+
hour: '2-digit',
|
|
225
|
+
minute: '2-digit',
|
|
226
|
+
hour12: true,
|
|
227
|
+
});
|
|
228
|
+
} catch {
|
|
229
|
+
const h = dt.getUTCHours();
|
|
230
|
+
const m = dt.getUTCMinutes();
|
|
231
|
+
return `${h % 12 || 12}:${m < 10 ? '0' + m : m} ${h < 12 ? 'AM' : 'PM'}`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let tithi2: any = null;
|
|
236
|
+
let nakshatra2: any = null;
|
|
237
|
+
let yoga2: any = null;
|
|
238
|
+
let karana2: any = null;
|
|
239
|
+
let tithiTransitionTime = '';
|
|
240
|
+
let nakshatraTransitionTime = '';
|
|
241
|
+
let yogaTransitionTime = '';
|
|
242
|
+
let karanaTransitionTime = '';
|
|
243
|
+
|
|
244
|
+
if (sunset) {
|
|
245
|
+
try {
|
|
246
|
+
const sunsetLons = computeLongitudes(sunset, ayanamsa);
|
|
247
|
+
const tithiAtSunset = calculateTithi(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
248
|
+
const nakshatraAtSunset = calculateNakshatra(sunsetLons.moonLon);
|
|
249
|
+
const yogaAtSunset = calculateYoga(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
250
|
+
const karanaAtSunset = calculateKarana(sunsetLons.sunLon, sunsetLons.moonLon);
|
|
251
|
+
|
|
252
|
+
const sunriseDate = sunrise ?? noonDate;
|
|
253
|
+
|
|
254
|
+
if (tithiAtSunset.number !== tithi.number) {
|
|
255
|
+
tithi2 = tithiAtSunset;
|
|
256
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
257
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
258
|
+
return calculateTithi(l.sunLon, l.moonLon).number;
|
|
259
|
+
}, tithi.number);
|
|
260
|
+
tithiTransitionTime = formatTransitionTime(transTime, timezone);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (nakshatraAtSunset.number !== nakshatra.number) {
|
|
264
|
+
nakshatra2 = nakshatraAtSunset;
|
|
265
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
266
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
267
|
+
return calculateNakshatra(l.moonLon).number;
|
|
268
|
+
}, nakshatra.number);
|
|
269
|
+
nakshatraTransitionTime = formatTransitionTime(transTime, timezone);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (yogaAtSunset.number !== yoga.number) {
|
|
273
|
+
yoga2 = yogaAtSunset;
|
|
274
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
275
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
276
|
+
return calculateYoga(l.sunLon, l.moonLon).number;
|
|
277
|
+
}, yoga.number);
|
|
278
|
+
yogaTransitionTime = formatTransitionTime(transTime, timezone);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (karanaAtSunset.number !== karana.number) {
|
|
282
|
+
karana2 = karanaAtSunset;
|
|
283
|
+
const transTime = findTransitionTime(sunriseDate, sunset, (dt) => {
|
|
284
|
+
const l = computeLongitudes(dt, ayanamsa);
|
|
285
|
+
return calculateKarana(l.sunLon, l.moonLon).number;
|
|
286
|
+
}, karana.number);
|
|
287
|
+
karanaTransitionTime = formatTransitionTime(transTime, timezone);
|
|
288
|
+
}
|
|
289
|
+
} catch {
|
|
290
|
+
// Transition detection failed, skip
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
193
294
|
// Sun Nakshatra
|
|
194
295
|
const sunNak = calculateNakshatra(sunLon);
|
|
195
296
|
|
|
@@ -290,10 +391,22 @@ export function calculateFullPanchang(
|
|
|
290
391
|
sunset: sunsetStr,
|
|
291
392
|
moonrise: formatTimeHHMM(moonrise, timezone),
|
|
292
393
|
moonset: formatTimeHHMM(moonset, timezone),
|
|
293
|
-
tithi: [
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
394
|
+
tithi: [
|
|
395
|
+
{ name: tithi.name, number: tithi.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: tithiTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: tithi.progress },
|
|
396
|
+
...(tithi2 ? [{ name: tithi2.name, number: tithi2.number, startTime: tithiTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: tithi2.progress }] : []),
|
|
397
|
+
],
|
|
398
|
+
nakshatra: [
|
|
399
|
+
{ name: nakshatra.name, number: nakshatra.number, pada: nakshatra.pada, lord: nakshatra.lord, deity: nakshatra.deity, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: nakshatraTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: nakshatra.progress },
|
|
400
|
+
...(nakshatra2 ? [{ name: nakshatra2.name, number: nakshatra2.number, pada: nakshatra2.pada, lord: nakshatra2.lord, deity: nakshatra2.deity, startTime: nakshatraTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: nakshatra2.progress }] : []),
|
|
401
|
+
],
|
|
402
|
+
yoga: [
|
|
403
|
+
{ name: yoga.name, number: yoga.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: yogaTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: yoga.progress },
|
|
404
|
+
...(yoga2 ? [{ name: yoga2.name, number: yoga2.number, startTime: yogaTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: yoga2.progress }] : []),
|
|
405
|
+
],
|
|
406
|
+
karana: [
|
|
407
|
+
{ name: karana.name, number: karana.number, startTime: formatTransitionTime(sunrise ?? noonDate, timezone), endTime: karanaTransitionTime || formatTransitionTime(sunset ?? noonDate, timezone), progress: karana.progress },
|
|
408
|
+
...(karana2 ? [{ name: karana2.name, number: karana2.number, startTime: karanaTransitionTime, endTime: formatTransitionTime(sunset ?? noonDate, timezone), progress: karana2.progress }] : []),
|
|
409
|
+
],
|
|
297
410
|
vara,
|
|
298
411
|
moonSign,
|
|
299
412
|
sunSign,
|