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.
- package/README.md +25 -0
- package/index.js +4 -1
- package/package.json +2 -2
- package/src/panchanga/constants.js +266 -0
- package/src/panchanga/index.js +330 -0
- package/src/panchanga/karana.js +185 -0
- package/src/panchanga/lunar-calendar.js +207 -0
- package/src/panchanga/muhurtas.js +370 -0
- package/src/panchanga/nakshatra.js +180 -0
- package/src/panchanga/solar-calendar.js +301 -0
- package/src/panchanga/sunrise-sunset.js +400 -0
- package/src/panchanga/tithi.js +234 -0
- package/src/panchanga/utils.js +320 -0
- package/src/panchanga/vaara.js +182 -0
- package/src/panchanga/yogam.js +196 -0
- package/get-full-results.js +0 -140
- package/test-chennai-1998.js +0 -162
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Karana (Half-Tithi) Calculations
|
|
3
|
+
* Karana = half of a tithi (6 degrees of moon-sun separation)
|
|
4
|
+
* 60 karanas in a lunar month (11 types cycling through fixed positions)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const tithiModule = require('./tithi');
|
|
8
|
+
const utils = require('./utils');
|
|
9
|
+
const { KARANA_NAMES, ONE_TITHI } = require('./constants');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get karana number and name
|
|
13
|
+
* There are 11 types of karanas:
|
|
14
|
+
* - 4 Fixed (Shakuni, Chatushpada, Naga, Kimstughna) - occur once each
|
|
15
|
+
* - 7 Repeating (Bava, Balava, Kaulava, Taitila, Garaja, Vanija, Vishti) - repeat 8 times each
|
|
16
|
+
*
|
|
17
|
+
* Distribution in 60 karanas:
|
|
18
|
+
* K1=Kimstughna (1st half of Shukla Pratipada)
|
|
19
|
+
* K2-K57: Bava to Vishti repeating 8 times
|
|
20
|
+
* K58=Shakuni, K59=Chatushpada, K60=Naga
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get karana type (fixed or repeating) and name
|
|
25
|
+
* @param {number} karanaNo - Karana number (1-60)
|
|
26
|
+
* @returns {object} {number, name, type}
|
|
27
|
+
*/
|
|
28
|
+
function getKaranaInfo(karanaNo) {
|
|
29
|
+
// Normalize to 1-60
|
|
30
|
+
const k = ((karanaNo - 1) % 60) + 1;
|
|
31
|
+
|
|
32
|
+
let name, type;
|
|
33
|
+
|
|
34
|
+
if (k === 1) {
|
|
35
|
+
name = 'Kimstughna';
|
|
36
|
+
type = 'fixed';
|
|
37
|
+
} else if (k === 58) {
|
|
38
|
+
name = 'Shakuni';
|
|
39
|
+
type = 'fixed';
|
|
40
|
+
} else if (k === 59) {
|
|
41
|
+
name = 'Chatushpada';
|
|
42
|
+
type = 'fixed';
|
|
43
|
+
} else if (k === 60) {
|
|
44
|
+
name = 'Naga';
|
|
45
|
+
type = 'fixed';
|
|
46
|
+
} else {
|
|
47
|
+
// Repeating karanas (k=2 to k=57)
|
|
48
|
+
// Bava(0), Balava(1), Kaulava(2), Taitila(3), Garaja(4), Vanija(5), Vishti(6)
|
|
49
|
+
const repeatIndex = (k - 2) % 7;
|
|
50
|
+
const repeatNames = ['Bava', 'Balava', 'Kaulava', 'Taitila', 'Garaja', 'Vanija', 'Vishti'];
|
|
51
|
+
name = repeatNames[repeatIndex];
|
|
52
|
+
type = 'repeating';
|
|
53
|
+
|
|
54
|
+
// Vishti (Bhadra) is considered inauspicious
|
|
55
|
+
if (name === 'Vishti') {
|
|
56
|
+
type = 'inauspicious';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { number: k, name, type };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get karana from tithi
|
|
65
|
+
* @param {number} tithiNo - Tithi number (1-30)
|
|
66
|
+
* @param {boolean} firstHalf - True for first half of tithi
|
|
67
|
+
* @returns {number} Karana number (1-60)
|
|
68
|
+
*/
|
|
69
|
+
function karanaFromTithi(tithiNo, firstHalf = true) {
|
|
70
|
+
// Each tithi has 2 karanas
|
|
71
|
+
const baseKarana = (tithiNo - 1) * 2 + 1;
|
|
72
|
+
return firstHalf ? baseKarana : baseKarana + 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get karana details for given date/time
|
|
77
|
+
* @param {number} jd - Julian Day Number (local)
|
|
78
|
+
* @param {object} place - {latitude, longitude, timezone}
|
|
79
|
+
* @returns {object} Karana details
|
|
80
|
+
*/
|
|
81
|
+
function karana(jd, place) {
|
|
82
|
+
// Get tithi info
|
|
83
|
+
const tithiInfo = tithiModule.tithi(jd, place);
|
|
84
|
+
if (!tithiInfo) return null;
|
|
85
|
+
|
|
86
|
+
const { year, month, day, hour } = utils.jdToDate(jd);
|
|
87
|
+
const currentHours = hour;
|
|
88
|
+
|
|
89
|
+
// Calculate midpoint of tithi (when karana changes)
|
|
90
|
+
const tithiMid = (tithiInfo.startTime + tithiInfo.endTime) / 2;
|
|
91
|
+
|
|
92
|
+
// Determine which half of tithi we're in
|
|
93
|
+
const isFirstHalf = currentHours <= tithiMid || tithiInfo.startTime < 0;
|
|
94
|
+
const karanaNo = karanaFromTithi(tithiInfo.number, isFirstHalf);
|
|
95
|
+
const karanaInfo = getKaranaInfo(karanaNo);
|
|
96
|
+
|
|
97
|
+
// Calculate karana start and end times
|
|
98
|
+
let startTime, endTime;
|
|
99
|
+
if (isFirstHalf) {
|
|
100
|
+
startTime = tithiInfo.startTime;
|
|
101
|
+
endTime = tithiMid;
|
|
102
|
+
} else {
|
|
103
|
+
startTime = tithiMid;
|
|
104
|
+
endTime = tithiInfo.endTime;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle negative start time (from previous day)
|
|
108
|
+
if (startTime < 0) {
|
|
109
|
+
startTime = startTime + 24;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
number: karanaNo,
|
|
114
|
+
name: karanaInfo.name,
|
|
115
|
+
type: karanaInfo.type,
|
|
116
|
+
startTime,
|
|
117
|
+
endTime,
|
|
118
|
+
startTimeString: utils.formatTime(startTime),
|
|
119
|
+
endTimeString: utils.formatTime(endTime > 24 ? endTime - 24 : endTime) + (endTime > 24 ? ' (+1)' : ''),
|
|
120
|
+
isVishti: karanaInfo.name === 'Vishti',
|
|
121
|
+
tithi: tithiInfo.number,
|
|
122
|
+
halfOfTithi: isFirstHalf ? 'first' : 'second'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if karana is Vishti (Bhadra) - inauspicious
|
|
128
|
+
* @param {number} karanaNo - Karana number
|
|
129
|
+
* @returns {boolean} True if Vishti
|
|
130
|
+
*/
|
|
131
|
+
function isVishti(karanaNo) {
|
|
132
|
+
const info = getKaranaInfo(karanaNo);
|
|
133
|
+
return info.name === 'Vishti';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get all karanas for a day
|
|
138
|
+
* @param {number} jd - Julian Day Number
|
|
139
|
+
* @param {object} place - {latitude, longitude, timezone}
|
|
140
|
+
* @returns {array} List of karanas for the day
|
|
141
|
+
*/
|
|
142
|
+
function karanasForDay(jd, place) {
|
|
143
|
+
const tithiInfo = tithiModule.tithi(jd, place);
|
|
144
|
+
if (!tithiInfo) return [];
|
|
145
|
+
|
|
146
|
+
const karanas = [];
|
|
147
|
+
|
|
148
|
+
// First karana (first half of current tithi)
|
|
149
|
+
const mid = (tithiInfo.startTime + tithiInfo.endTime) / 2;
|
|
150
|
+
const k1 = karanaFromTithi(tithiInfo.number, true);
|
|
151
|
+
const k1Info = getKaranaInfo(k1);
|
|
152
|
+
|
|
153
|
+
if (tithiInfo.startTime < mid && mid < 24) {
|
|
154
|
+
karanas.push({
|
|
155
|
+
number: k1,
|
|
156
|
+
name: k1Info.name,
|
|
157
|
+
startTime: tithiInfo.startTime < 0 ? tithiInfo.startTime + 24 : tithiInfo.startTime,
|
|
158
|
+
endTime: mid
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Second karana (second half of current tithi)
|
|
163
|
+
const k2 = karanaFromTithi(tithiInfo.number, false);
|
|
164
|
+
const k2Info = getKaranaInfo(k2);
|
|
165
|
+
|
|
166
|
+
if (mid < tithiInfo.endTime) {
|
|
167
|
+
karanas.push({
|
|
168
|
+
number: k2,
|
|
169
|
+
name: k2Info.name,
|
|
170
|
+
startTime: mid,
|
|
171
|
+
endTime: tithiInfo.endTime > 24 ? tithiInfo.endTime - 24 : tithiInfo.endTime
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return karanas;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
karana,
|
|
180
|
+
karanaFromTithi,
|
|
181
|
+
getKaranaInfo,
|
|
182
|
+
isVishti,
|
|
183
|
+
karanasForDay,
|
|
184
|
+
KARANA_NAMES
|
|
185
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lunar Calendar Functions
|
|
3
|
+
* Lunar month, adhika detection, new/full moon, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const sunriseSunset = require('./sunrise-sunset');
|
|
7
|
+
const tithiModule = require('./tithi');
|
|
8
|
+
const utils = require('./utils');
|
|
9
|
+
const { LUNAR_MONTH_NAMES, SIDEREAL_YEAR, KALI_YUGA_JD } = require('./constants');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get lunar month for given date
|
|
13
|
+
* @param {number} jd - Julian Day Number
|
|
14
|
+
* @param {object} place - {latitude, longitude, timezone}
|
|
15
|
+
* @returns {object} Lunar month details
|
|
16
|
+
*/
|
|
17
|
+
function lunarMonth(jd, place) {
|
|
18
|
+
const rise = sunriseSunset.sunrise(jd, place);
|
|
19
|
+
if (!rise) return null;
|
|
20
|
+
|
|
21
|
+
const jdUTC = utils.toUTC(rise.jd, place.timezone);
|
|
22
|
+
const currentTithi = tithiModule.quickTithi(jdUTC);
|
|
23
|
+
|
|
24
|
+
// Find last and next new moons
|
|
25
|
+
const lastNewMoon = tithiModule.newMoon(jdUTC, currentTithi, -1);
|
|
26
|
+
const nextNewMoon = tithiModule.newMoon(jdUTC, currentTithi, +1);
|
|
27
|
+
|
|
28
|
+
// Get solar month (raasi) at each new moon
|
|
29
|
+
const thisSolarMonth = Math.floor(sunriseSunset.solarLongitude(lastNewMoon) / 30);
|
|
30
|
+
const nextSolarMonth = Math.floor(sunriseSunset.solarLongitude(nextNewMoon) / 30);
|
|
31
|
+
|
|
32
|
+
// If both new moons are in same solar month, it's an adhika (leap) month
|
|
33
|
+
const isAdhika = (thisSolarMonth === nextSolarMonth);
|
|
34
|
+
|
|
35
|
+
// Lunar month is named after the solar month at the start
|
|
36
|
+
const lunarMonthIndex = (thisSolarMonth + 1) % 12;
|
|
37
|
+
|
|
38
|
+
// Check for nija (second occurrence after adhika)
|
|
39
|
+
let isNija = false;
|
|
40
|
+
if (!isAdhika) {
|
|
41
|
+
const prevMonthData = lunarMonth(jd - 30, place);
|
|
42
|
+
if (prevMonthData && prevMonthData.index === lunarMonthIndex && prevMonthData.isAdhika) {
|
|
43
|
+
isNija = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
index: lunarMonthIndex,
|
|
49
|
+
number: lunarMonthIndex + 1, // 1-12
|
|
50
|
+
name: LUNAR_MONTH_NAMES[lunarMonthIndex],
|
|
51
|
+
isAdhika,
|
|
52
|
+
isNija,
|
|
53
|
+
prefix: isAdhika ? 'Adhika ' : (isNija ? 'Nija ' : ''),
|
|
54
|
+
fullName: (isAdhika ? 'Adhika ' : (isNija ? 'Nija ' : '')) + LUNAR_MONTH_NAMES[lunarMonthIndex],
|
|
55
|
+
lastNewMoon: utils.jdToDate(lastNewMoon),
|
|
56
|
+
nextNewMoon: utils.jdToDate(nextNewMoon)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get complete lunar date (month + day + year)
|
|
62
|
+
* @param {number} jd - Julian Day Number
|
|
63
|
+
* @param {object} place - {latitude, longitude, timezone}
|
|
64
|
+
* @param {boolean} usePurnimanta - Use Purnimanta system (month ends at full moon)
|
|
65
|
+
* @returns {object} Complete lunar date
|
|
66
|
+
*/
|
|
67
|
+
function lunarDate(jd, place, usePurnimanta = false) {
|
|
68
|
+
const month = lunarMonth(jd, place);
|
|
69
|
+
const tithiInfo = tithiModule.tithi(jd, place);
|
|
70
|
+
|
|
71
|
+
if (!month || !tithiInfo) return null;
|
|
72
|
+
|
|
73
|
+
let lunarDay = tithiInfo.number;
|
|
74
|
+
let monthIndex = month.index;
|
|
75
|
+
|
|
76
|
+
// In Purnimanta system, month changes at full moon (tithi 15)
|
|
77
|
+
if (usePurnimanta) {
|
|
78
|
+
if (lunarDay > 15) {
|
|
79
|
+
monthIndex = (monthIndex + 1) % 12;
|
|
80
|
+
lunarDay = (lunarDay - 16) % 30 + 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Calculate lunar year (Samvatsara)
|
|
85
|
+
const yearIndex = lunarYearIndex(jd, monthIndex + 1);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
month: monthIndex + 1,
|
|
89
|
+
monthName: LUNAR_MONTH_NAMES[monthIndex],
|
|
90
|
+
day: lunarDay,
|
|
91
|
+
tithiName: tithiInfo.name,
|
|
92
|
+
paksha: tithiInfo.paksha,
|
|
93
|
+
year: yearIndex,
|
|
94
|
+
isAdhika: month.isAdhika,
|
|
95
|
+
isNija: month.isNija,
|
|
96
|
+
system: usePurnimanta ? 'Purnimanta' : 'Amanta'
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Calculate lunar year index (0-59 Samvatsara cycle)
|
|
102
|
+
* @param {number} jd - Julian Day Number
|
|
103
|
+
* @param {number} maasaIndex - Lunar month (1-12)
|
|
104
|
+
* @returns {number} Year index in 60-year cycle
|
|
105
|
+
*/
|
|
106
|
+
function lunarYearIndex(jd, maasaIndex) {
|
|
107
|
+
const kali = elapsedKaliYears(jd, maasaIndex);
|
|
108
|
+
const kaliBase = 14;
|
|
109
|
+
const kaliStart = 27; // Pramaadhi
|
|
110
|
+
|
|
111
|
+
let adjustedKali = kali;
|
|
112
|
+
if (kali >= 4009) {
|
|
113
|
+
adjustedKali = (kali - kaliBase) % 60;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const samvatIndex = (adjustedKali + kaliStart + Math.floor((adjustedKali * 211 - 108) / 18000)) % 60;
|
|
117
|
+
return samvatIndex - 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Calculate elapsed Kali Yuga years
|
|
122
|
+
* @param {number} jd - Julian Day Number
|
|
123
|
+
* @param {number} maasaIndex - Month index (1-12)
|
|
124
|
+
* @returns {number} Kali year number
|
|
125
|
+
*/
|
|
126
|
+
function elapsedKaliYears(jd, maasaIndex) {
|
|
127
|
+
const ahar = jd - KALI_YUGA_JD;
|
|
128
|
+
return Math.floor((ahar + (4 - maasaIndex) * 30) / SIDEREAL_YEAR);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Calculate era years (Kali, Vikrama, Saka)
|
|
133
|
+
* @param {number} jd - Julian Day Number
|
|
134
|
+
* @param {number} maasaIndex - Month index
|
|
135
|
+
* @returns {object} {kali, vikrama, saka}
|
|
136
|
+
*/
|
|
137
|
+
function eraYears(jd, maasaIndex) {
|
|
138
|
+
const kali = elapsedKaliYears(jd, maasaIndex);
|
|
139
|
+
const saka = kali - 3179;
|
|
140
|
+
const vikrama = saka + 135;
|
|
141
|
+
return { kali, vikrama, saka };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get next lunar month start date
|
|
146
|
+
* @param {number} jd - Julian Day Number
|
|
147
|
+
* @param {object} place - {latitude, longitude, timezone}
|
|
148
|
+
* @param {number} direction - 1 for next, -1 for previous
|
|
149
|
+
* @returns {object} {date, time}
|
|
150
|
+
*/
|
|
151
|
+
function nextLunarMonthStart(jd, place, direction = 1) {
|
|
152
|
+
const currentTithi = tithiModule.quickTithi(utils.toUTC(jd, place.timezone));
|
|
153
|
+
const newMoonJD = tithiModule.newMoon(jd, currentTithi, direction);
|
|
154
|
+
|
|
155
|
+
// Get tithi at new moon to find exact start
|
|
156
|
+
const tithiAtNewMoon = tithiModule.tithi(newMoonJD + place.timezone / 24, place);
|
|
157
|
+
|
|
158
|
+
if (!tithiAtNewMoon) return null;
|
|
159
|
+
|
|
160
|
+
const date = utils.jdToDate(newMoonJD + place.timezone / 24);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
year: date.year,
|
|
164
|
+
month: date.month,
|
|
165
|
+
day: Math.floor(date.day),
|
|
166
|
+
hours: tithiAtNewMoon.endTime,
|
|
167
|
+
jd: newMoonJD
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Calculate Ritu (season) from lunar month
|
|
173
|
+
* @param {number} monthIndex - Lunar month (0-11 or 1-12)
|
|
174
|
+
* @returns {object} Ritu details
|
|
175
|
+
*/
|
|
176
|
+
function ritu(monthIndex) {
|
|
177
|
+
// Normalize to 0-11
|
|
178
|
+
const idx = monthIndex > 0 && monthIndex <= 12 ? monthIndex - 1 : monthIndex;
|
|
179
|
+
const rituIndex = Math.floor(idx / 2);
|
|
180
|
+
|
|
181
|
+
const rituNames = ['Vasanta', 'Grishma', 'Varsha', 'Sharad', 'Hemanta', 'Shishira'];
|
|
182
|
+
const rituDescriptions = [
|
|
183
|
+
'Spring (Chaitra-Vaishakha)',
|
|
184
|
+
'Summer (Jyeshtha-Ashadha)',
|
|
185
|
+
'Monsoon (Shravana-Bhadrapada)',
|
|
186
|
+
'Autumn (Ashwin-Kartik)',
|
|
187
|
+
'Pre-winter (Margashirsha-Pausha)',
|
|
188
|
+
'Winter (Magha-Phalguna)'
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
index: rituIndex,
|
|
193
|
+
name: rituNames[rituIndex],
|
|
194
|
+
description: rituDescriptions[rituIndex]
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
lunarMonth,
|
|
200
|
+
lunarDate,
|
|
201
|
+
lunarYearIndex,
|
|
202
|
+
elapsedKaliYears,
|
|
203
|
+
eraYears,
|
|
204
|
+
nextLunarMonthStart,
|
|
205
|
+
ritu,
|
|
206
|
+
LUNAR_MONTH_NAMES
|
|
207
|
+
};
|