jyotish-calc 1.2.0 → 1.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,370 @@
1
+ /**
2
+ * Muhurta (Auspicious/Inauspicious Time Periods)
3
+ * Various time divisions used in Vedic astrology
4
+ */
5
+
6
+ const sunriseSunset = require('./sunrise-sunset');
7
+ const vaaraModule = require('./vaara');
8
+ const utils = require('./utils');
9
+ const {
10
+ RAAHU_KAALAM_OFFSETS, GULIKAI_OFFSETS, YAMAGANDAM_OFFSETS,
11
+ DURMUHURTAM_OFFSETS, GAURI_CHOGHADIYA_DAY, GAURI_CHOGHADIYA_NIGHT,
12
+ CHOGHADIYA_NAMES, DAY_RULERS, NIGHT_RULERS
13
+ } = require('./constants');
14
+
15
+ /**
16
+ * Calculate Trikalam (Raahu Kaalam, Yamagandam, Gulikai)
17
+ * @param {number} jd - Julian Day Number
18
+ * @param {object} place - {latitude, longitude, timezone}
19
+ * @param {string} type - 'raahu', 'yamagandam', or 'gulikai'
20
+ * @returns {object} {startTime, endTime, startString, endString}
21
+ */
22
+ function trikalam(jd, place, type = 'raahu') {
23
+ const rise = sunriseSunset.sunrise(jd, place);
24
+ const dayDur = sunriseSunset.dayLength(jd, place);
25
+ const weekday = vaaraModule.vaara(jd).number;
26
+
27
+ if (!rise) return null;
28
+
29
+ let offsets;
30
+ switch (type.toLowerCase()) {
31
+ case 'raahu':
32
+ case 'raahukaalam':
33
+ offsets = RAAHU_KAALAM_OFFSETS;
34
+ break;
35
+ case 'gulikai':
36
+ case 'gulikaikaalam':
37
+ offsets = GULIKAI_OFFSETS;
38
+ break;
39
+ case 'yamagandam':
40
+ case 'yamagandakaalam':
41
+ offsets = YAMAGANDAM_OFFSETS;
42
+ break;
43
+ default:
44
+ offsets = RAAHU_KAALAM_OFFSETS;
45
+ }
46
+
47
+ const startTime = rise.hours + dayDur * offsets[weekday];
48
+ const endTime = startTime + 0.125 * dayDur; // 1/8 of day
49
+
50
+ return {
51
+ startTime,
52
+ endTime,
53
+ startString: utils.formatTime(startTime),
54
+ endString: utils.formatTime(endTime),
55
+ duration: endTime - startTime
56
+ };
57
+ }
58
+
59
+ // Convenience functions
60
+ const raahuKaalam = (jd, place) => trikalam(jd, place, 'raahu');
61
+ const yamagandaKaalam = (jd, place) => trikalam(jd, place, 'yamagandam');
62
+ const gulikaiKaalam = (jd, place) => trikalam(jd, place, 'gulikai');
63
+
64
+ /**
65
+ * Calculate Abhijit Muhurta (most auspicious - 8th muhurta around noon)
66
+ * @param {number} jd - Julian Day Number
67
+ * @param {object} place - {latitude, longitude, timezone}
68
+ * @returns {object} {startTime, endTime}
69
+ */
70
+ function abhijitMuhurta(jd, place) {
71
+ const rise = sunriseSunset.sunrise(jd, place);
72
+ const dayDur = sunriseSunset.dayLength(jd, place);
73
+
74
+ if (!rise) return null;
75
+
76
+ // Abhijit = 7/15 to 8/15 of day (8th of 15 day muhurtas)
77
+ const startTime = rise.hours + (7 / 15) * dayDur;
78
+ const endTime = rise.hours + (8 / 15) * dayDur;
79
+
80
+ return {
81
+ startTime,
82
+ endTime,
83
+ startString: utils.formatTime(startTime),
84
+ endString: utils.formatTime(endTime),
85
+ duration: endTime - startTime
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Calculate Durmuhurtam (inauspicious muhurta)
91
+ * @param {number} jd - Julian Day Number
92
+ * @param {object} place - {latitude, longitude, timezone}
93
+ * @returns {array} List of durmuhurtam periods
94
+ */
95
+ function durmuhurtam(jd, place) {
96
+ const rise = sunriseSunset.sunrise(jd, place);
97
+ const set = sunriseSunset.sunset(jd, place);
98
+ const dayDur = sunriseSunset.dayLength(jd, place);
99
+ const nightDur = sunriseSunset.nightLength(jd, place);
100
+ const weekday = vaaraModule.vaara(jd).number;
101
+
102
+ if (!rise || !set) return [];
103
+
104
+ const offsets = DURMUHURTAM_OFFSETS[weekday];
105
+ const results = [];
106
+
107
+ // Duration of one durmuhurtam = 0.8/12 of day
108
+ const durDuration = dayDur * 0.8 / 12;
109
+
110
+ for (let i = 0; i < offsets.length; i++) {
111
+ if (offsets[i] === 0.0) continue;
112
+
113
+ let base, dur;
114
+ // Tuesday's second durmuhurtam uses night duration
115
+ if (weekday === 2 && i === 1) {
116
+ base = set.hours;
117
+ dur = nightDur;
118
+ } else {
119
+ base = rise.hours;
120
+ dur = dayDur;
121
+ }
122
+
123
+ const startTime = base + dur * offsets[i] / 12;
124
+ const endTime = startTime + durDuration;
125
+
126
+ results.push({
127
+ startTime,
128
+ endTime,
129
+ startString: utils.formatTime(startTime),
130
+ endString: utils.formatTime(endTime)
131
+ });
132
+ }
133
+
134
+ return results;
135
+ }
136
+
137
+ /**
138
+ * Calculate Brahma Muhurta (pre-dawn auspicious period)
139
+ * 2 muhurtas before sunrise
140
+ * @param {number} jd - Julian Day Number
141
+ * @param {object} place - {latitude, longitude, timezone}
142
+ * @returns {object} {startTime, endTime}
143
+ */
144
+ function brahmaMuhurta(jd, place) {
145
+ const nightDur = sunriseSunset.nightLength(jd - 1, place);
146
+ const rise = sunriseSunset.sunrise(jd, place);
147
+
148
+ if (!rise) return null;
149
+
150
+ const muhurtaDur = nightDur / 15; // One night muhurta
151
+ const startTime = rise.hours - 2 * muhurtaDur;
152
+ const endTime = rise.hours - muhurtaDur;
153
+
154
+ return {
155
+ startTime: startTime < 0 ? startTime + 24 : startTime,
156
+ endTime: endTime < 0 ? endTime + 24 : endTime,
157
+ startString: utils.formatTime(startTime < 0 ? startTime + 24 : startTime),
158
+ endString: utils.formatTime(endTime < 0 ? endTime + 24 : endTime)
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Calculate Godhuli Muhurta (cow-dust time around sunset)
164
+ * @param {number} jd - Julian Day Number
165
+ * @param {object} place - {latitude, longitude, timezone}
166
+ * @returns {object} {startTime, endTime}
167
+ */
168
+ function godhuliMuhurta(jd, place) {
169
+ const set = sunriseSunset.sunset(jd, place);
170
+ const dayDur = sunriseSunset.dayLength(jd, place);
171
+ const nightDur = sunriseSunset.nightLength(jd, place);
172
+
173
+ if (!set) return null;
174
+
175
+ const dayMuhurta = dayDur / 15;
176
+ const nightMuhurta = nightDur / 15;
177
+
178
+ const startTime = set.hours - 0.25 * dayMuhurta;
179
+ const endTime = set.hours + 0.25 * nightMuhurta;
180
+
181
+ return {
182
+ startTime,
183
+ endTime,
184
+ startString: utils.formatTime(startTime),
185
+ endString: utils.formatTime(endTime)
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Calculate Vijaya Muhurta (victory time - around noon and midnight)
191
+ * @param {number} jd - Julian Day Number
192
+ * @param {object} place - {latitude, longitude, timezone}
193
+ * @returns {object} {day, night} - both periods
194
+ */
195
+ function vijayaMuhurta(jd, place) {
196
+ const rise = sunriseSunset.sunrise(jd, place);
197
+ const set = sunriseSunset.sunset(jd, place);
198
+ const dayDur = sunriseSunset.dayLength(jd, place);
199
+ const nightDur = sunriseSunset.nightLength(jd, place);
200
+
201
+ if (!rise || !set) return null;
202
+
203
+ const dayGhati = dayDur / 30;
204
+ const nightGhati = nightDur / 30;
205
+
206
+ const noon = rise.hours + dayDur / 2;
207
+ const midnight = set.hours + nightDur / 2;
208
+
209
+ return {
210
+ day: {
211
+ startTime: noon - dayGhati,
212
+ endTime: noon + dayGhati,
213
+ startString: utils.formatTime(noon - dayGhati),
214
+ endString: utils.formatTime(noon + dayGhati)
215
+ },
216
+ night: {
217
+ startTime: midnight - nightGhati,
218
+ endTime: midnight + nightGhati,
219
+ startString: utils.formatTime((midnight - nightGhati) % 24),
220
+ endString: utils.formatTime((midnight + nightGhati) % 24)
221
+ }
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Calculate Nishita Kaala (8th muhurta of night)
227
+ * @param {number} jd - Julian Day Number
228
+ * @param {object} place - {latitude, longitude, timezone}
229
+ * @returns {object} {startTime, endTime}
230
+ */
231
+ function nishitaKaala(jd, place) {
232
+ const set = sunriseSunset.sunset(jd, place);
233
+ const nightDur = sunriseSunset.nightLength(jd, place);
234
+
235
+ if (!set) return null;
236
+
237
+ const nightGhati = nightDur / 30;
238
+ const startTime = set.hours + 7 * nightGhati;
239
+ const endTime = set.hours + 8 * nightGhati;
240
+
241
+ return {
242
+ startTime: startTime % 24,
243
+ endTime: endTime % 24,
244
+ startString: utils.formatTime(startTime % 24),
245
+ endString: utils.formatTime(endTime % 24)
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Calculate Gauri Choghadiya (North Indian time system)
251
+ * 8 periods during day, 8 during night
252
+ * @param {number} jd - Julian Day Number
253
+ * @param {object} place - {latitude, longitude, timezone}
254
+ * @returns {array} List of choghadiya periods
255
+ */
256
+ function gauriChoghadiya(jd, place) {
257
+ const rise = sunriseSunset.sunrise(jd, place);
258
+ const set = sunriseSunset.sunset(jd, place);
259
+ const nextRise = sunriseSunset.sunrise(jd + 1, place);
260
+ const weekday = vaaraModule.vaara(jd).number;
261
+
262
+ if (!rise || !set || !nextRise) return [];
263
+
264
+ const dayDur = (set.hours - rise.hours) / 8;
265
+ const nightDur = (24 + nextRise.hours - set.hours) / 8;
266
+
267
+ const periods = [];
268
+
269
+ // Day periods
270
+ for (let i = 0; i < 8; i++) {
271
+ const typeIndex = GAURI_CHOGHADIYA_DAY[weekday][i];
272
+ const startTime = rise.hours + i * dayDur;
273
+ const endTime = rise.hours + (i + 1) * dayDur;
274
+
275
+ periods.push({
276
+ period: i + 1,
277
+ type: CHOGHADIYA_NAMES[typeIndex],
278
+ typeIndex,
279
+ startTime,
280
+ endTime,
281
+ startString: utils.formatTime(startTime),
282
+ endString: utils.formatTime(endTime),
283
+ isDay: true,
284
+ isAuspicious: [2, 3, 5].includes(typeIndex) // Labh, Amrit, Shubh
285
+ });
286
+ }
287
+
288
+ // Night periods
289
+ for (let i = 0; i < 8; i++) {
290
+ const typeIndex = GAURI_CHOGHADIYA_NIGHT[weekday][i];
291
+ const startTime = set.hours + i * nightDur;
292
+ const endTime = set.hours + (i + 1) * nightDur;
293
+
294
+ periods.push({
295
+ period: i + 9,
296
+ type: CHOGHADIYA_NAMES[typeIndex],
297
+ typeIndex,
298
+ startTime: startTime % 24,
299
+ endTime: endTime % 24,
300
+ startString: utils.formatTime(startTime % 24),
301
+ endString: utils.formatTime(endTime % 24),
302
+ isDay: false,
303
+ isAuspicious: [2, 3, 5].includes(typeIndex)
304
+ });
305
+ }
306
+
307
+ return periods;
308
+ }
309
+
310
+ /**
311
+ * Get Amrit Kaalam (most auspicious choghadiya period)
312
+ * @param {number} jd - Julian Day Number
313
+ * @param {object} place - {latitude, longitude, timezone}
314
+ * @returns {array} List of Amrit periods
315
+ */
316
+ function amritKaalam(jd, place) {
317
+ const choghadiyas = gauriChoghadiya(jd, place);
318
+ return choghadiyas.filter(c => c.type === 'Amrit');
319
+ }
320
+
321
+ /**
322
+ * Calculate Sandhya periods (twilight - important for prayers)
323
+ * @param {number} jd - Julian Day Number
324
+ * @param {object} place - {latitude, longitude, timezone}
325
+ * @returns {object} {pratah, madhyahna, sayam} - three sandhya periods
326
+ */
327
+ function sandhyaPeriods(jd, place) {
328
+ const rise = sunriseSunset.sunrise(jd, place);
329
+ const set = sunriseSunset.sunset(jd, place);
330
+ const dayDur = sunriseSunset.dayLength(jd, place);
331
+
332
+ if (!rise || !set) return null;
333
+
334
+ const ghati = dayDur / 30;
335
+ const noon = rise.hours + dayDur / 2;
336
+
337
+ return {
338
+ pratah: {
339
+ startTime: rise.hours - 2 * ghati,
340
+ endTime: rise.hours + ghati,
341
+ description: '2 ghatis before and 1 ghati after sunrise'
342
+ },
343
+ madhyahna: {
344
+ startTime: noon - 1.5 * ghati,
345
+ endTime: noon + 1.5 * ghati,
346
+ description: '1.5 ghatis before and after noon'
347
+ },
348
+ sayam: {
349
+ startTime: set.hours - ghati,
350
+ endTime: set.hours + 2 * ghati,
351
+ description: '1 ghati before and 2 ghatis after sunset'
352
+ }
353
+ };
354
+ }
355
+
356
+ module.exports = {
357
+ trikalam,
358
+ raahuKaalam,
359
+ yamagandaKaalam,
360
+ gulikaiKaalam,
361
+ abhijitMuhurta,
362
+ durmuhurtam,
363
+ brahmaMuhurta,
364
+ godhuliMuhurta,
365
+ vijayaMuhurta,
366
+ nishitaKaala,
367
+ gauriChoghadiya,
368
+ amritKaalam,
369
+ sandhyaPeriods
370
+ };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Nakshatra (Lunar Mansion) Calculations
3
+ * Nakshatra = floor(Moon longitude / 13.333) + 1
4
+ * 27 nakshatras, each with 4 padas (quarters)
5
+ */
6
+
7
+ const sunriseSunset = require('./sunrise-sunset');
8
+ const utils = require('./utils');
9
+ const { NAKSHATRA_NAMES, ONE_NAKSHATRA, ONE_PADA } = require('./constants');
10
+
11
+ /**
12
+ * Get nakshatra and pada from longitude
13
+ * @param {number} longitude - Ecliptic longitude (0-360)
14
+ * @returns {object} {nakshatra, pada, remainder}
15
+ */
16
+ function nakshatraPada(longitude) {
17
+ const normalizedLong = utils.norm360(longitude);
18
+ const nakshatraIndex = Math.floor(normalizedLong / ONE_NAKSHATRA);
19
+ const remainder = normalizedLong % ONE_NAKSHATRA;
20
+ const padaIndex = Math.floor(remainder / ONE_PADA);
21
+
22
+ return {
23
+ nakshatra: nakshatraIndex + 1, // 1-27
24
+ pada: padaIndex + 1, // 1-4
25
+ remainder: remainder, // Degrees within nakshatra
26
+ degreesInPada: remainder % ONE_PADA
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Get nakshatra details for given date/time
32
+ * @param {number} jd - Julian Day Number (local)
33
+ * @param {object} place - {latitude, longitude, timezone}
34
+ * @returns {object} Nakshatra details
35
+ */
36
+ function nakshatra(jd, place) {
37
+ const jdUTC = utils.toUTC(jd, place.timezone);
38
+ const rise = sunriseSunset.sunrise(jd, place);
39
+
40
+ if (!rise) return null;
41
+
42
+ const riseJDUTC = utils.toUTC(rise.jd, place.timezone);
43
+ const moonLong = sunriseSunset.lunarLongitude(jdUTC);
44
+ const { nakshatra: nakNo, pada: padaNo, remainder } = nakshatraPada(moonLong);
45
+
46
+ // Calculate nakshatra end time
47
+ const targetDegree = nakNo * ONE_NAKSHATRA;
48
+ const degreesLeft = targetDegree - moonLong;
49
+
50
+ // Get moon positions at intervals
51
+ const offsets = [0.0, 0.25, 0.5, 0.75, 1.0];
52
+ const moonLongs = offsets.map(t => sunriseSunset.lunarLongitude(riseJDUTC + t));
53
+ const unwrapped = utils.unwrapAngles(moonLongs);
54
+
55
+ // Find when nakshatra ends
56
+ let endTime;
57
+ try {
58
+ const jdAtStart = utils.dateToJD(
59
+ utils.jdToDate(jd).year,
60
+ utils.jdToDate(jd).month,
61
+ utils.jdToDate(jd).day
62
+ );
63
+ const approxEnd = utils.inverseLagrange(offsets, unwrapped, targetDegree);
64
+ endTime = (rise.jd + approxEnd - jdAtStart) * 24;
65
+ } catch {
66
+ // Fallback using average moon speed
67
+ const avgMoonSpeed = 13.17; // degrees per day
68
+ endTime = rise.hours + (degreesLeft / avgMoonSpeed) * 24;
69
+ }
70
+
71
+ // Get start time from previous day
72
+ const prevNak = _getNakshatraAtSunrise(jd - 1, place);
73
+ let startTime = prevNak ? prevNak.endTime : rise.hours;
74
+ if (startTime > 24) startTime -= 24;
75
+ if (startTime < 0) startTime = -startTime;
76
+
77
+ // Check for nakshatra change during day
78
+ let nextNakshatra = null;
79
+ if (endTime < 24) {
80
+ const nextNakNo = (nakNo % 27) + 1;
81
+ nextNakshatra = {
82
+ number: nextNakNo,
83
+ name: NAKSHATRA_NAMES[nextNakNo - 1],
84
+ pada: 1,
85
+ startTime: endTime
86
+ };
87
+ }
88
+
89
+ const result = {
90
+ number: nakNo,
91
+ name: NAKSHATRA_NAMES[nakNo - 1],
92
+ pada: padaNo,
93
+ startTime,
94
+ endTime,
95
+ startTimeString: utils.formatTime(startTime < 0 ? startTime + 24 : startTime),
96
+ endTimeString: utils.formatTime(endTime > 24 ? endTime - 24 : endTime) + (endTime > 24 ? ' (+1)' : ''),
97
+ remainder,
98
+ moonLongitude: moonLong
99
+ };
100
+
101
+ if (nextNakshatra) {
102
+ result.nextNakshatra = nextNakshatra;
103
+ }
104
+
105
+ return result;
106
+ }
107
+
108
+ /**
109
+ * Internal helper to get nakshatra at sunrise
110
+ */
111
+ function _getNakshatraAtSunrise(jd, place) {
112
+ const rise = sunriseSunset.sunrise(jd, place);
113
+ if (!rise) return null;
114
+
115
+ const riseJDUTC = utils.toUTC(rise.jd, place.timezone);
116
+ const moonLong = sunriseSunset.lunarLongitude(riseJDUTC);
117
+ const nakNo = Math.floor(moonLong / ONE_NAKSHATRA) + 1;
118
+ const targetDegree = nakNo * ONE_NAKSHATRA;
119
+
120
+ const offsets = [0.0, 0.25, 0.5, 0.75, 1.0];
121
+ const moonLongs = offsets.map(t => sunriseSunset.lunarLongitude(riseJDUTC + t));
122
+ const unwrapped = utils.unwrapAngles(moonLongs);
123
+
124
+ try {
125
+ const jdAtStart = utils.dateToJD(
126
+ utils.jdToDate(jd).year,
127
+ utils.jdToDate(jd).month,
128
+ utils.jdToDate(jd).day
129
+ );
130
+ const approxEnd = utils.inverseLagrange(offsets, unwrapped, targetDegree);
131
+ const endTime = (rise.jd + approxEnd - jdAtStart) * 24;
132
+ return { nakNo, endTime };
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Quick nakshatra lookup (without full timing calculation)
140
+ * @param {number} jdUTC - Julian Day (UTC)
141
+ * @returns {object} {nakshatra, pada}
142
+ */
143
+ function quickNakshatra(jdUTC) {
144
+ const moonLong = sunriseSunset.lunarLongitude(jdUTC);
145
+ return nakshatraPada(moonLong);
146
+ }
147
+
148
+ /**
149
+ * Get nakshatra lord (planet ruling the nakshatra)
150
+ * @param {number} nakshatra - Nakshatra number (1-27)
151
+ * @returns {number} Planet index (0=Sun, 1=Moon, ...)
152
+ */
153
+ function nakshatraLord(nakshatra) {
154
+ // Vimshottari lords: Ketu(8), Venus(5), Sun(0), Moon(1), Mars(2), Rahu(7), Jupiter(4), Saturn(6), Mercury(3)
155
+ const lords = [8, 5, 0, 1, 2, 7, 4, 6, 3];
156
+ return lords[(nakshatra - 1) % 9];
157
+ }
158
+
159
+ /**
160
+ * Get abhijit nakshatra bounds
161
+ * Abhijit spans the last quarter of Uttara Ashadha and first 1/15th of Shravana
162
+ * @returns {object} Start and end degrees
163
+ */
164
+ function abhijitBounds() {
165
+ // Uttara Ashadha ends at 280°, Shravana starts at 280°
166
+ // Abhijit: 276°40' to 280°53'20"
167
+ return {
168
+ start: 276 + 40 / 60,
169
+ end: 280 + 53 / 60 + 20 / 3600
170
+ };
171
+ }
172
+
173
+ module.exports = {
174
+ nakshatra,
175
+ nakshatraPada,
176
+ quickNakshatra,
177
+ nakshatraLord,
178
+ abhijitBounds,
179
+ NAKSHATRA_NAMES
180
+ };