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.
@@ -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 = { normalizeAngle: function(a) { var n = a % 360; if (n < 0) n += 360; return n; } };
28
+ const index_1 = require("../utils/index");
29
29
  const planetary_1 = require("./planetary");
30
- const path = { join: function() { return "/"; } };
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
- "/" // CWD node_modules
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 = { existsSync: function() { return false; } };
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 = { normalizeAngle: function(a) { var n = a % 360; if (n < 0) n += 360; return n; } };
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: [{ name: tithi.name, number: tithi.number, startTime: '', endTime: '', progress: tithi.progress }],
274
- nakshatra: [{ name: nakshatra.name, number: nakshatra.number, pada: nakshatra.pada, lord: nakshatra.lord, deity: nakshatra.deity, startTime: '', endTime: '', progress: nakshatra.progress }],
275
- yoga: [{ name: yoga.name, number: yoga.number, startTime: '', endTime: '', progress: yoga.progress }],
276
- karana: [{ name: karana.name, number: karana.number, startTime: '', endTime: '', progress: karana.progress }],
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.2.0",
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: [{ name: tithi.name, number: tithi.number, startTime: '', endTime: '', progress: tithi.progress }],
294
- nakshatra: [{ name: nakshatra.name, number: nakshatra.number, pada: nakshatra.pada, lord: nakshatra.lord, deity: nakshatra.deity, startTime: '', endTime: '', progress: nakshatra.progress }],
295
- yoga: [{ name: yoga.name, number: yoga.number, startTime: '', endTime: '', progress: yoga.progress }],
296
- karana: [{ name: karana.name, number: karana.number, startTime: '', endTime: '', progress: karana.progress }],
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,