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,301 @@
1
+ /**
2
+ * Solar Calendar Functions
3
+ * Solar month, Sankranti, Tamil calendar, Samvatsara
4
+ */
5
+
6
+ const sunriseSunset = require('./sunrise-sunset');
7
+ const utils = require('./utils');
8
+ const { SOLAR_MONTH_NAMES, TAMIL_MONTH_NAMES, SAMVATSARA_NAMES, SIGN_NAMES } = require('./constants');
9
+
10
+ /**
11
+ * Get solar month (Raasi) for given date
12
+ * @param {number} jd - Julian Day Number
13
+ * @param {object} place - {latitude, longitude, timezone}
14
+ * @returns {object} Solar month details
15
+ */
16
+ function raasi(jd, place) {
17
+ const jdUTC = utils.toUTC(jd, place.timezone);
18
+ const rise = sunriseSunset.sunrise(jd, place);
19
+
20
+ if (!rise) return null;
21
+
22
+ const riseJDUTC = utils.toUTC(rise.jd, place.timezone);
23
+ const sunLong = sunriseSunset.solarLongitude(riseJDUTC);
24
+ const raasiNo = Math.floor(sunLong / 30) + 1;
25
+ const degreesInRaasi = sunLong % 30;
26
+ const degreesLeft = 30 - degreesInRaasi;
27
+
28
+ // Calculate end time using interpolation
29
+ const offsets = [0.0, 0.25, 0.5, 0.75, 1.0];
30
+ const sunLongs = offsets.map(t => sunriseSunset.solarLongitude(riseJDUTC + t));
31
+ const unwrapped = utils.unwrapAngles(sunLongs);
32
+
33
+ let endTime;
34
+ const targetDegree = raasiNo * 30;
35
+ try {
36
+ const jdAtStart = utils.dateToJD(
37
+ utils.jdToDate(jd).year,
38
+ utils.jdToDate(jd).month,
39
+ utils.jdToDate(jd).day
40
+ );
41
+ const approxEnd = utils.inverseLagrange(offsets, unwrapped, targetDegree);
42
+ endTime = (rise.jd + approxEnd - jdAtStart) * 24;
43
+ } catch {
44
+ // Sun moves ~1 degree per day
45
+ endTime = rise.hours + degreesLeft * 24;
46
+ }
47
+
48
+ return {
49
+ number: raasiNo,
50
+ name: SOLAR_MONTH_NAMES[raasiNo - 1],
51
+ signName: SIGN_NAMES[raasiNo - 1],
52
+ sunLongitude: sunLong,
53
+ degreesInRaasi,
54
+ degreesLeft,
55
+ endTime,
56
+ endTimeString: utils.formatTime(endTime > 24 ? endTime - 24 : endTime)
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Get Tamil solar month and date
62
+ * @param {number} jd - Julian Day Number
63
+ * @param {object} place - {latitude, longitude, timezone}
64
+ * @returns {object} Tamil month and date
65
+ */
66
+ function tamilSolarMonthAndDate(jd, place) {
67
+ const set = sunriseSunset.sunset(jd, place);
68
+ if (!set) return null;
69
+
70
+ const setJDUTC = utils.toUTC(set.jd, place.timezone);
71
+ const sunLong = sunriseSunset.solarLongitude(setJDUTC);
72
+ const tamilMonth = Math.floor(sunLong / 30);
73
+
74
+ // Count days since sun entered this raasi
75
+ let dayCount = 1;
76
+ let searchJD = set.jd;
77
+ let searchLong = sunLong;
78
+
79
+ while (true) {
80
+ const remainder = searchLong % 30;
81
+ if (remainder < 1 && remainder > 0) {
82
+ break;
83
+ }
84
+ searchJD -= 1;
85
+ const searchSetJDUTC = utils.toUTC(searchJD, place.timezone);
86
+ searchLong = sunriseSunset.solarLongitude(searchSetJDUTC);
87
+ dayCount++;
88
+
89
+ // Safety limit
90
+ if (dayCount > 32) break;
91
+ }
92
+
93
+ return {
94
+ month: tamilMonth,
95
+ monthNumber: tamilMonth + 1,
96
+ monthName: TAMIL_MONTH_NAMES[tamilMonth],
97
+ date: dayCount,
98
+ sunLongitude: sunLong
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Get Samvatsara (60-year cycle) for given date
104
+ * @param {number} jd - Julian Day Number
105
+ * @param {object} place - {latitude, longitude, timezone}
106
+ * @returns {object} Samvatsara details
107
+ */
108
+ function samvatsara(jd, place) {
109
+ const tamilDate = tamilSolarMonthAndDate(jd, place);
110
+ if (!tamilDate) return null;
111
+
112
+ // Find previous Mesha Sankranti (start of year)
113
+ const prevSankranti = previousSankranti(jd, place, 0); // 0 = Mesha
114
+ const year = prevSankranti ? prevSankranti.date.year : utils.jdToDate(jd).year;
115
+
116
+ // Calculate samvatsara index
117
+ // Using the traditional formula
118
+ const adjustedYear = year > 0 ? year - 1 : year;
119
+ const samvatsaraIndex = ((adjustedYear - 1926) % 60 + 60) % 60;
120
+
121
+ return {
122
+ index: samvatsaraIndex,
123
+ number: samvatsaraIndex + 1,
124
+ name: SAMVATSARA_NAMES[samvatsaraIndex],
125
+ year
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Get previous Sankranti (sun entering a sign)
131
+ * @param {number} jd - Julian Day Number
132
+ * @param {object} place - {latitude, longitude, timezone}
133
+ * @param {number} targetSign - Target sign (0-11), null for any
134
+ * @returns {object} Sankranti details
135
+ */
136
+ function previousSankranti(jd, place, targetSign = null) {
137
+ const set = sunriseSunset.sunset(jd, place);
138
+ if (!set) return null;
139
+
140
+ let searchJD = set.jd;
141
+ let searchJDUTC = utils.toUTC(searchJD, place.timezone);
142
+ let sunLong = sunriseSunset.solarLongitude(searchJDUTC);
143
+ let currentSign = Math.floor(sunLong / 30);
144
+
145
+ // Search backwards for sign change
146
+ let maxDays = 35;
147
+ while (maxDays > 0) {
148
+ searchJD -= 1;
149
+ searchJDUTC = utils.toUTC(searchJD, place.timezone);
150
+ const newSunLong = sunriseSunset.solarLongitude(searchJDUTC);
151
+ const newSign = Math.floor(newSunLong / 30);
152
+
153
+ if (newSign !== currentSign) {
154
+ // Found a sign change
155
+ if (targetSign === null || currentSign === targetSign) {
156
+ // Refine the time
157
+ const riseJD = sunriseSunset.sunrise(searchJD + 1, place).jd;
158
+ const riseJDUTC = utils.toUTC(riseJD, place.timezone);
159
+ const offsets = [0.0, 0.25, 0.5, 0.75, 1.0];
160
+ const sunLongs = offsets.map(t => sunriseSunset.solarLongitude(riseJDUTC + t));
161
+
162
+ const sankrantiDegree = currentSign * 30;
163
+ let sankrantiTime;
164
+ try {
165
+ sankrantiTime = utils.inverseLagrange(offsets, sunLongs, sankrantiDegree);
166
+ } catch {
167
+ sankrantiTime = 0;
168
+ }
169
+
170
+ const date = utils.jdToDate(searchJD + 1);
171
+ return {
172
+ sign: currentSign,
173
+ signName: SIGN_NAMES[currentSign],
174
+ solarMonth: SOLAR_MONTH_NAMES[currentSign],
175
+ tamilMonth: TAMIL_MONTH_NAMES[currentSign],
176
+ date: {
177
+ year: date.year,
178
+ month: date.month,
179
+ day: Math.floor(date.day)
180
+ },
181
+ time: sankrantiTime,
182
+ jd: searchJD + 1 + sankrantiTime
183
+ };
184
+ }
185
+ }
186
+
187
+ currentSign = newSign;
188
+ sunLong = newSunLong;
189
+ maxDays--;
190
+ }
191
+
192
+ return null;
193
+ }
194
+
195
+ /**
196
+ * Get next Sankranti
197
+ * @param {number} jd - Julian Day Number
198
+ * @param {object} place - {latitude, longitude, timezone}
199
+ * @param {number} targetSign - Target sign (0-11), null for any
200
+ * @returns {object} Sankranti details
201
+ */
202
+ function nextSankranti(jd, place, targetSign = null) {
203
+ const set = sunriseSunset.sunset(jd, place);
204
+ if (!set) return null;
205
+
206
+ let searchJD = set.jd;
207
+ let searchJDUTC = utils.toUTC(searchJD, place.timezone);
208
+ let sunLong = sunriseSunset.solarLongitude(searchJDUTC);
209
+ let currentSign = Math.floor(sunLong / 30);
210
+
211
+ // Search forwards for sign change
212
+ let maxDays = 35;
213
+ while (maxDays > 0) {
214
+ searchJD += 1;
215
+ searchJDUTC = utils.toUTC(searchJD, place.timezone);
216
+ const newSunLong = sunriseSunset.solarLongitude(searchJDUTC);
217
+ const newSign = Math.floor(newSunLong / 30);
218
+
219
+ if (newSign !== currentSign) {
220
+ if (targetSign === null || newSign === targetSign) {
221
+ const riseJD = sunriseSunset.sunrise(searchJD, place).jd;
222
+ const riseJDUTC = utils.toUTC(riseJD, place.timezone);
223
+ const offsets = [0.0, 0.25, 0.5, 0.75, 1.0];
224
+ const sunLongs = offsets.map(t => sunriseSunset.solarLongitude(riseJDUTC + t));
225
+
226
+ const sankrantiDegree = newSign * 30;
227
+ let sankrantiTime;
228
+ try {
229
+ sankrantiTime = utils.inverseLagrange(offsets, sunLongs, sankrantiDegree);
230
+ } catch {
231
+ sankrantiTime = 0;
232
+ }
233
+
234
+ const date = utils.jdToDate(searchJD);
235
+ return {
236
+ sign: newSign,
237
+ signName: SIGN_NAMES[newSign],
238
+ solarMonth: SOLAR_MONTH_NAMES[newSign],
239
+ tamilMonth: TAMIL_MONTH_NAMES[newSign],
240
+ date: {
241
+ year: date.year,
242
+ month: date.month,
243
+ day: Math.floor(date.day)
244
+ },
245
+ time: sankrantiTime,
246
+ jd: searchJD + sankrantiTime
247
+ };
248
+ }
249
+ }
250
+
251
+ currentSign = newSign;
252
+ sunLong = newSunLong;
253
+ maxDays--;
254
+ }
255
+
256
+ return null;
257
+ }
258
+
259
+ /**
260
+ * Get number of days in Tamil month
261
+ * @param {number} jd - Julian Day Number
262
+ * @param {object} place - {latitude, longitude, timezone}
263
+ * @returns {number} Days in the month
264
+ */
265
+ function daysInTamilMonth(jd, place) {
266
+ const current = tamilSolarMonthAndDate(jd, place);
267
+ if (!current) return 30;
268
+
269
+ // Find next sankranti
270
+ const next = nextSankranti(jd, place);
271
+ if (!next) return 30;
272
+
273
+ // Count days
274
+ const daysPassed = current.date;
275
+ let searchJD = jd;
276
+ let daysRemaining = 0;
277
+
278
+ while (true) {
279
+ searchJD += 1;
280
+ const checkDate = tamilSolarMonthAndDate(searchJD, place);
281
+ if (!checkDate || checkDate.month !== current.month) {
282
+ break;
283
+ }
284
+ daysRemaining++;
285
+ if (daysRemaining > 35) break;
286
+ }
287
+
288
+ return daysPassed + daysRemaining;
289
+ }
290
+
291
+ module.exports = {
292
+ raasi,
293
+ tamilSolarMonthAndDate,
294
+ samvatsara,
295
+ previousSankranti,
296
+ nextSankranti,
297
+ daysInTamilMonth,
298
+ SOLAR_MONTH_NAMES,
299
+ TAMIL_MONTH_NAMES,
300
+ SAMVATSARA_NAMES
301
+ };
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Sunrise/Sunset Calculations
3
+ * Calculate sun and moon rise/set times using Swiss Ephemeris
4
+ * With simplified astronomical formulas for reliability
5
+ */
6
+
7
+ const swe = require('swisseph-v2');
8
+ const utils = require('./utils');
9
+
10
+ // Set ephemeris path
11
+ swe.swe_set_ephe_path('./node_modules/swisseph-v2/ephe');
12
+
13
+ /**
14
+ * Calculate sunrise for given date and place
15
+ * @param {number} jd - Julian Day Number (local time)
16
+ * @param {object} place - {latitude, longitude, timezone}
17
+ * @returns {object} {hours, string, jd} - sunrise time
18
+ */
19
+ function sunrise(jd, place) {
20
+ const { year, month, day } = utils.jdToDate(jd);
21
+
22
+ // Use simple sunrise formula
23
+ const result = calculateSunTime(year, month, day, place.latitude, place.longitude, place.timezone, true);
24
+
25
+ if (result === null) {
26
+ return null;
27
+ }
28
+
29
+ const jdAtStart = utils.dateToJD(year, month, day);
30
+
31
+ return {
32
+ hours: result,
33
+ string: utils.formatTime(result),
34
+ string24: utils.formatTime(result, true),
35
+ jd: jdAtStart + result / 24
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Calculate sunset for given date and place
41
+ * @param {number} jd - Julian Day Number (local time)
42
+ * @param {object} place - {latitude, longitude, timezone}
43
+ * @returns {object} {hours, string, jd} - sunset time
44
+ */
45
+ function sunset(jd, place) {
46
+ const { year, month, day } = utils.jdToDate(jd);
47
+
48
+ const result = calculateSunTime(year, month, day, place.latitude, place.longitude, place.timezone, false);
49
+
50
+ if (result === null) {
51
+ return null;
52
+ }
53
+
54
+ const jdAtStart = utils.dateToJD(year, month, day);
55
+
56
+ return {
57
+ hours: result,
58
+ string: utils.formatTime(result),
59
+ string24: utils.formatTime(result, true),
60
+ jd: jdAtStart + result / 24
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Simple and reliable sunrise/sunset calculation
66
+ * Based on Meeus "Astronomical Algorithms"
67
+ */
68
+ function calculateSunTime(year, month, day, lat, lng, tz, isSunrise) {
69
+ // Day of year
70
+ const N1 = Math.floor(275 * month / 9);
71
+ const N2 = Math.floor((month + 9) / 12);
72
+ const N3 = 1 + Math.floor((year - 4 * Math.floor(year / 4) + 2) / 3);
73
+ const N = N1 - (N2 * N3) + day - 30;
74
+
75
+ // Convert longitude to hour value
76
+ const lngHour = lng / 15;
77
+
78
+ // Approximate time
79
+ let t;
80
+ if (isSunrise) {
81
+ t = N + (6 - lngHour) / 24;
82
+ } else {
83
+ t = N + (18 - lngHour) / 24;
84
+ }
85
+
86
+ // Sun's mean anomaly
87
+ const M = (0.9856 * t) - 3.289;
88
+
89
+ // Sun's true longitude
90
+ let L = M + (1.916 * Math.sin(utils.toRadians(M))) +
91
+ (0.020 * Math.sin(utils.toRadians(2 * M))) + 282.634;
92
+ L = ((L % 360) + 360) % 360;
93
+
94
+ // Sun's right ascension
95
+ let RA = utils.toDegrees(Math.atan(0.91764 * Math.tan(utils.toRadians(L))));
96
+ RA = ((RA % 360) + 360) % 360;
97
+
98
+ // RA in same quadrant as L
99
+ const Lquadrant = Math.floor(L / 90) * 90;
100
+ const RAquadrant = Math.floor(RA / 90) * 90;
101
+ RA = RA + (Lquadrant - RAquadrant);
102
+
103
+ // RA to hours
104
+ RA = RA / 15;
105
+
106
+ // Sun's declination
107
+ const sinDec = 0.39782 * Math.sin(utils.toRadians(L));
108
+ const cosDec = Math.cos(Math.asin(sinDec));
109
+
110
+ // Sun's local hour angle
111
+ const zenith = 90.833; // Official zenith with refraction correction
112
+ const cosH = (Math.cos(utils.toRadians(zenith)) -
113
+ (sinDec * Math.sin(utils.toRadians(lat)))) /
114
+ (cosDec * Math.cos(utils.toRadians(lat)));
115
+
116
+ // Check if sun rises/sets
117
+ if (cosH > 1) {
118
+ return null; // Sun never rises
119
+ }
120
+ if (cosH < -1) {
121
+ return null; // Sun never sets
122
+ }
123
+
124
+ // Hour angle
125
+ let H;
126
+ if (isSunrise) {
127
+ H = 360 - utils.toDegrees(Math.acos(cosH));
128
+ } else {
129
+ H = utils.toDegrees(Math.acos(cosH));
130
+ }
131
+ H = H / 15; // Convert to hours
132
+
133
+ // Local mean time
134
+ const T = H + RA - (0.06571 * t) - 6.622;
135
+
136
+ // UTC time
137
+ let UT = T - lngHour;
138
+ UT = ((UT % 24) + 24) % 24;
139
+
140
+ // Local time
141
+ const localTime = UT + tz;
142
+
143
+ return ((localTime % 24) + 24) % 24;
144
+ }
145
+
146
+ /**
147
+ * Calculate moonrise for given date and place
148
+ * @param {number} jd - Julian Day Number (local time)
149
+ * @param {object} place - {latitude, longitude, timezone}
150
+ * @returns {object} {hours, string, jd} - moonrise time
151
+ */
152
+ function moonrise(jd, place) {
153
+ const { year, month, day } = utils.jdToDate(jd);
154
+ const jdNoon = utils.dateToJD(year, month, day, 12);
155
+ const jdNoonUTC = jdNoon - place.timezone / 24;
156
+
157
+ let moonriseTime = null;
158
+
159
+ for (let hour = 0; hour < 24; hour += 0.5) {
160
+ const testJD = jdNoonUTC - 12 / 24 + hour / 24;
161
+ const alt = getMoonAltitude(testJD, place.latitude, place.longitude);
162
+ const nextAlt = getMoonAltitude(testJD + 0.5 / 24, place.latitude, place.longitude);
163
+
164
+ if (alt < 0 && nextAlt >= 0) {
165
+ moonriseTime = hour;
166
+ break;
167
+ }
168
+ }
169
+
170
+ if (moonriseTime === null) {
171
+ return null;
172
+ }
173
+
174
+ const jdAtStart = utils.dateToJD(year, month, day);
175
+
176
+ return {
177
+ hours: moonriseTime,
178
+ string: utils.formatTime(moonriseTime),
179
+ string24: utils.formatTime(moonriseTime, true),
180
+ jd: jdAtStart + moonriseTime / 24
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Calculate moonset for given date and place
186
+ */
187
+ function moonset(jd, place) {
188
+ const { year, month, day } = utils.jdToDate(jd);
189
+ const jdNoon = utils.dateToJD(year, month, day, 12);
190
+ const jdNoonUTC = jdNoon - place.timezone / 24;
191
+
192
+ let moonsetTime = null;
193
+
194
+ for (let hour = 0; hour < 24; hour += 0.5) {
195
+ const testJD = jdNoonUTC - 12 / 24 + hour / 24;
196
+ const alt = getMoonAltitude(testJD, place.latitude, place.longitude);
197
+ const nextAlt = getMoonAltitude(testJD + 0.5 / 24, place.latitude, place.longitude);
198
+
199
+ if (alt >= 0 && nextAlt < 0) {
200
+ moonsetTime = hour;
201
+ break;
202
+ }
203
+ }
204
+
205
+ if (moonsetTime === null) {
206
+ return null;
207
+ }
208
+
209
+ const jdAtStart = utils.dateToJD(year, month, day);
210
+
211
+ return {
212
+ hours: moonsetTime,
213
+ string: utils.formatTime(moonsetTime),
214
+ string24: utils.formatTime(moonsetTime, true),
215
+ jd: jdAtStart + moonsetTime / 24
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Get approximate moon altitude
221
+ */
222
+ function getMoonAltitude(jdUTC, lat, lng) {
223
+ const flags = swe.SEFLG_SWIEPH;
224
+ const result = swe.swe_calc_ut(jdUTC, swe.SE_MOON, flags);
225
+
226
+ if (result.error) {
227
+ return 0;
228
+ }
229
+
230
+ const moonLong = result.longitude;
231
+ const moonLat = result.latitude;
232
+
233
+ const obliquity = 23.44;
234
+ const oblRad = utils.toRadians(obliquity);
235
+ const longRad = utils.toRadians(moonLong);
236
+ const latRad = utils.toRadians(moonLat);
237
+
238
+ const sinRA = Math.sin(longRad) * Math.cos(oblRad) - Math.tan(latRad) * Math.sin(oblRad);
239
+ const cosRA = Math.cos(longRad);
240
+ const RA = Math.atan2(sinRA, cosRA);
241
+
242
+ const sinDec = Math.sin(latRad) * Math.cos(oblRad) +
243
+ Math.cos(latRad) * Math.sin(oblRad) * Math.sin(longRad);
244
+ const dec = Math.asin(sinDec);
245
+
246
+ const LST = getLST(jdUTC, lng);
247
+ const LSTrad = utils.toRadians(LST);
248
+
249
+ const HA = LSTrad - RA;
250
+
251
+ const latRadObs = utils.toRadians(lat);
252
+ const sinAlt = Math.sin(dec) * Math.sin(latRadObs) +
253
+ Math.cos(dec) * Math.cos(latRadObs) * Math.cos(HA);
254
+ const altitude = utils.toDegrees(Math.asin(sinAlt));
255
+
256
+ return altitude;
257
+ }
258
+
259
+ /**
260
+ * Get Local Sidereal Time
261
+ */
262
+ function getLST(jdUTC, lng) {
263
+ const T = (jdUTC - 2451545.0) / 36525.0;
264
+ let GMST = 280.46061837 + 360.98564736629 * (jdUTC - 2451545.0) +
265
+ 0.000387933 * T * T - T * T * T / 38710000.0;
266
+ GMST = ((GMST % 360) + 360) % 360;
267
+
268
+ const LST = GMST + lng;
269
+ return ((LST % 360) + 360) % 360;
270
+ }
271
+
272
+ /**
273
+ * Calculate midday time (sun at highest point)
274
+ */
275
+ function midday(jd, place) {
276
+ const rise = sunrise(jd, place);
277
+ const set = sunset(jd, place);
278
+
279
+ if (!rise || !set) return null;
280
+
281
+ const hours = (rise.hours + set.hours) / 2;
282
+ const middayJD = (rise.jd + set.jd) / 2;
283
+
284
+ return {
285
+ hours,
286
+ string: utils.formatTime(hours),
287
+ string24: utils.formatTime(hours, true),
288
+ jd: middayJD
289
+ };
290
+ }
291
+
292
+ /**
293
+ * Calculate midnight time (sun at lowest point)
294
+ */
295
+ function midnight(jd, place) {
296
+ const set = sunset(jd, place);
297
+ const nextRise = sunrise(jd + 1, place);
298
+
299
+ if (!set || !nextRise) return null;
300
+
301
+ let hours = (set.hours + 24 + nextRise.hours) / 2;
302
+ if (hours > 24) hours -= 24;
303
+
304
+ return {
305
+ hours,
306
+ string: utils.formatTime(hours),
307
+ string24: utils.formatTime(hours, true)
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Calculate day length (sunrise to sunset)
313
+ */
314
+ function dayLength(jd, place) {
315
+ const rise = sunrise(jd, place);
316
+ const set = sunset(jd, place);
317
+
318
+ if (!rise || !set) return 12;
319
+
320
+ let length = set.hours - rise.hours;
321
+ if (length < 0) length += 24;
322
+ return length;
323
+ }
324
+
325
+ /**
326
+ * Calculate night length (sunset to next sunrise)
327
+ */
328
+ function nightLength(jd, place) {
329
+ const day = dayLength(jd, place);
330
+ return 24 - day;
331
+ }
332
+
333
+ /**
334
+ * Get sidereal longitude of a planet/point
335
+ */
336
+ function siderealLongitude(jdUTC, planet, ayanamsaMode = 'LAHIRI') {
337
+ swe.swe_set_sid_mode(swe.SE_SIDM_LAHIRI, 0, 0);
338
+
339
+ const flags = swe.SEFLG_SWIEPH | swe.SEFLG_SIDEREAL | swe.SEFLG_SPEED;
340
+ const result = swe.swe_calc_ut(jdUTC, planet, flags);
341
+
342
+ if (result.error) {
343
+ console.warn('Planet calculation error:', result.error);
344
+ return 0;
345
+ }
346
+
347
+ return utils.norm360(result.longitude);
348
+ }
349
+
350
+ /**
351
+ * Get solar longitude (sidereal)
352
+ */
353
+ function solarLongitude(jdUTC) {
354
+ return siderealLongitude(jdUTC, swe.SE_SUN);
355
+ }
356
+
357
+ /**
358
+ * Get lunar longitude (sidereal)
359
+ */
360
+ function lunarLongitude(jdUTC) {
361
+ return siderealLongitude(jdUTC, swe.SE_MOON);
362
+ }
363
+
364
+ /**
365
+ * Get daily motion of Moon
366
+ */
367
+ function lunarDailyMotion(jdUTC) {
368
+ const today = lunarLongitude(jdUTC);
369
+ const tomorrow = lunarLongitude(jdUTC + 1);
370
+ let motion = tomorrow - today;
371
+ if (motion < 0) motion += 360;
372
+ return motion;
373
+ }
374
+
375
+ /**
376
+ * Get daily motion of Sun
377
+ */
378
+ function solarDailyMotion(jdUTC) {
379
+ const today = solarLongitude(jdUTC);
380
+ const tomorrow = solarLongitude(jdUTC + 1);
381
+ let motion = tomorrow - today;
382
+ if (motion < 0) motion += 360;
383
+ return motion;
384
+ }
385
+
386
+ module.exports = {
387
+ sunrise,
388
+ sunset,
389
+ moonrise,
390
+ moonset,
391
+ midday,
392
+ midnight,
393
+ dayLength,
394
+ nightLength,
395
+ siderealLongitude,
396
+ solarLongitude,
397
+ lunarLongitude,
398
+ lunarDailyMotion,
399
+ solarDailyMotion
400
+ };