@ishubhamx/panchangam-js
Version:
Indian Panchangam (Hindu Calendar) library for calculating Tithi, Nakshatra, Yoga, Karana, and Vara using Swiss Ephemeris
448 lines • 22.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.nakshatraNames = exports.tithiNames = exports.yogaNames = exports.karanaNames = void 0;
exports.getPanchangam = getPanchangam;
exports.getPanchangamDetails = getPanchangamDetails;
const astronomy_engine_1 = require("astronomy-engine");
const repeatingKaranaNames = [
"Bava", "Balava", "Kaulava", "Taitila", "Gara", "Vanija", "Vishti"
];
const fixedKaranaNames = [
"Shakuni", "Chatushpada", "Naga", "Kimstughna"
];
exports.karanaNames = [...repeatingKaranaNames, ...fixedKaranaNames];
exports.yogaNames = [
"Vishkambha", "Priti", "Ayushman", "Saubhagya", "Shobhana", "Atiganda",
"Sukarman", "Dhriti", "Shula", "Ganda", "Vriddhi", "Dhruva", "Vyaghata",
"Harshana", "Vajra", "Siddhi", "Vyatipata", "Variyana", "Parigha",
"Shiva", "Siddha", "Sadhya", "Shubha", "Shukla", "Brahma", "Indra", "Vaidhriti"
];
exports.tithiNames = [
"Prathama", "Dwitiya", "Tritiya", "Chaturthi", "Panchami",
"Shashthi", "Saptami", "Ashtami", "Navami", "Dashami",
"Ekadashi", "Dwadashi", "Trayodashi", "Chaturdashi", "Purnima",
"Prathama", "Dwitiya", "Tritiya", "Chaturthi", "Panchami",
"Shashthi", "Saptami", "Ashtami", "Navami", "Dashami",
"Ekadashi", "Dwadashi", "Trayodashi", "Chaturdashi", "Amavasya",
];
exports.nakshatraNames = [
"Ashwini", "Bharani", "Krittika", "Rohini", "Mrigashira", "Ardra",
"Punarvasu", "Pushya", "Ashlesha", "Magha", "Purva Phalguni", "Uttara Phalguni",
"Hasta", "Chitra", "Swati", "Vishakha", "Anuradha", "Jyeshtha",
"Mula", "Purva Ashadha", "Uttara Ashadha", "Shravana", "Dhanishta",
"Shatabhisha", "Purva Bhadrapada", "Uttara Bhadrapada", "Revati"
];
function getTithi(sunLon, moonLon) {
let longitudeDifference = moonLon - sunLon;
if (longitudeDifference < 0) {
longitudeDifference += 360;
}
return Math.floor(longitudeDifference / 12);
}
function getNakshatra(moonLon) {
return Math.floor(moonLon / (13 + 1 / 3));
}
function getYoga(sunLon, moonLon) {
const totalLongitude = sunLon + moonLon;
return Math.floor(totalLongitude / (13 + 1 / 3)) % 27;
}
function getKarana(sunLon, moonLon) {
let longitudeDifference = moonLon - sunLon;
if (longitudeDifference < 0) {
longitudeDifference += 360;
}
const karanaIndexAbs = Math.floor(longitudeDifference / 6);
if (karanaIndexAbs === 0) {
return "Kimstughna";
}
if (karanaIndexAbs === 57) {
return "Shakuni";
}
if (karanaIndexAbs === 58) {
return "Chatushpada";
}
if (karanaIndexAbs === 59) {
return "Naga";
}
const repeatingIndex = (karanaIndexAbs - 1) % 7;
return repeatingKaranaNames[repeatingIndex];
}
function getVara(date) {
return date.getDay();
}
function getSunrise(date, observer) {
const time = (0, astronomy_engine_1.SearchRiseSet)(astronomy_engine_1.Body.Sun, observer, 1, date, 1);
return time?.date || null;
}
function getSunset(date, observer) {
const time = (0, astronomy_engine_1.SearchRiseSet)(astronomy_engine_1.Body.Sun, observer, -1, date, 1);
return time?.date || null;
}
function getMoonrise(date, observer) {
const time = (0, astronomy_engine_1.SearchRiseSet)(astronomy_engine_1.Body.Moon, observer, 1, date, 1);
return time?.date || null;
}
function getMoonset(date, observer) {
const time = (0, astronomy_engine_1.SearchRiseSet)(astronomy_engine_1.Body.Moon, observer, -1, date, 1);
return time?.date || null;
}
/**
* A generic search function to find the time when a function f(t) crosses zero.
* It uses a binary search approach.
*/
function search(f, startDate) {
let a = startDate;
let b = new Date(startDate.getTime() + 2 * 24 * 60 * 60 * 1000); // Look ahead 2 days
let fa = f(a);
let fb = f(b);
if (fa * fb >= 0) {
// We need the function to cross zero in the interval.
// If not, we might not find a root.
// This can happen if a tithi/nakshatra doesn't end within the next 2 days, which is rare.
return null;
}
for (let i = 0; i < 20; i++) { // 20 iterations are enough for high precision
const m = new Date((a.getTime() + b.getTime()) / 2);
const fm = f(m);
if (fm * fa < 0) {
b = m;
fb = fm;
}
else {
a = m;
fa = fm;
}
}
return a;
}
function findNakshatraStart(date) {
const moonLonInitial = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, date, true)).elon;
const currentNakshatraIndex = Math.floor(moonLonInitial / (13 + 1 / 3));
const startNakshatraLongitude = currentNakshatraIndex * (13 + 1 / 3);
const targetLon = startNakshatraLongitude % 360;
const nakshatraFunc = (d) => {
let moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
// Handle the 360->0 wrap-around for the search.
if (moonLon > targetLon + 180) {
moonLon -= 360;
}
return moonLon - targetLon;
};
// A nakshatra lasts about a day. Searching from 25 hours before should be safe.
const searchStartDate = new Date(date.getTime() - 25 * 60 * 60 * 1000);
return search(nakshatraFunc, searchStartDate);
}
function findNakshatraEnd(date) {
const moonLonInitial = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, date, true)).elon;
const currentNakshatra = Math.floor(moonLonInitial / (13 + 1 / 3));
const nextNakshatraLongitude = (currentNakshatra + 1) * (13 + 1 / 3);
const targetLon = nextNakshatraLongitude % 360;
const nakshatraFunc = (d) => {
let moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
// Handle the 360->0 wrap-around
if (moonLon < targetLon - 180) {
moonLon += 360;
}
return moonLon - targetLon;
};
return search(nakshatraFunc, date);
}
function findTithiStart(date) {
const sunLonInitial = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, date, true)).elon;
const moonLonInitial = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, date, true)).elon;
let diffInitial = moonLonInitial - sunLonInitial;
if (diffInitial < 0)
diffInitial += 360;
const currentTithi = Math.floor(diffInitial / 12);
const startTithiAngle = currentTithi * 12;
const targetAngle = startTithiAngle % 360;
const tithiFunc = (d) => {
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, d, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
let diff = moonLon - sunLon;
if (diff < 0)
diff += 360;
// Handle the 360->0 wrap-around for search.
if (diff > targetAngle + 180) {
diff -= 360;
}
return diff - targetAngle;
};
// A tithi is slightly less than a day. Searching from 25h before is safe.
const searchStartDate = new Date(date.getTime() - 25 * 60 * 60 * 1000);
return search(tithiFunc, searchStartDate);
}
function findTithiEnd(date) {
const sunLonInitial = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, date, true)).elon;
const moonLonInitial = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, date, true)).elon;
let diffInitial = moonLonInitial - sunLonInitial;
if (diffInitial < 0)
diffInitial += 360;
const currentTithi = Math.floor(diffInitial / 12);
const nextTithiAngle = (currentTithi + 1) * 12;
const targetAngle = nextTithiAngle % 360;
const tithiFunc = (d) => {
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, d, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
let diff = moonLon - sunLon;
if (diff < 0)
diff += 360;
if (diff < targetAngle - 180) {
diff += 360;
}
return diff - targetAngle;
};
return search(tithiFunc, date);
}
function findYogaEnd(date) {
const sunLonInitial = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, date, true)).elon;
const moonLonInitial = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, date, true)).elon;
const totalLongitudeInitial = sunLonInitial + moonLonInitial;
const yogaWidth = 360 / 27; // 13 degrees 20 minutes
const currentYogaTotalIndex = Math.floor(totalLongitudeInitial / yogaWidth);
const nextYogaBoundary = (currentYogaTotalIndex + 1) * yogaWidth;
const yogaFunc = (d) => {
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, d, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
let totalLon = sunLon + moonLon;
// If totalLon is much smaller than our target, it means one of the
// components (likely the moon) has wrapped around from 360 to 0.
// We add 360 to make the value monotonic for the search function.
if (totalLon < nextYogaBoundary - 270) {
totalLon += 360;
}
return totalLon - nextYogaBoundary;
};
return search(yogaFunc, date);
}
function calculateRahuKalam(sunrise, sunset, vara) {
if (!sunrise || !sunset) {
return null;
}
const daylightMillis = sunset.getTime() - sunrise.getTime();
const portionMillis = daylightMillis / 8;
const rahuKalamPortionIndex = [8, 2, 7, 5, 6, 4, 3]; // Sun, Mon, Tue, Wed, Thu, Fri, Sat
const portionIndex = rahuKalamPortionIndex[vara];
const startMillis = sunrise.getTime() + (portionIndex - 1) * portionMillis;
const endMillis = sunrise.getTime() + portionIndex * portionMillis;
return {
start: new Date(startMillis),
end: new Date(endMillis)
};
}
/**
* Find all Karana transitions (end times and names) between startDate and endDate (typically sunrise to next sunrise)
*/
function findKaranaTransitions(startDate, endDate) {
const transitions = [];
let current = new Date(startDate);
let lastKarana = getKarana((0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon, (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon);
while (current < endDate) {
// Find next Karana end
const nextKaranaEnd = (() => {
// Karana changes every 6 degrees of moon-sun difference
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon;
let diff = moonLon - sunLon;
if (diff < 0)
diff += 360;
const karanaIndexAbs = Math.floor(diff / 6);
const nextKaranaAngle = (karanaIndexAbs + 1) * 6;
const targetAngle = nextKaranaAngle % 360;
const karanaFunc = (d) => {
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, d, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
let diff = moonLon - sunLon;
if (diff < 0)
diff += 360;
if (diff < targetAngle - 180)
diff += 360;
return diff - targetAngle;
};
return search(karanaFunc, current);
})();
if (!nextKaranaEnd || nextKaranaEnd > endDate) {
// Last Karana for the day
transitions.push({ name: lastKarana, endTime: endDate });
break;
}
else {
transitions.push({ name: lastKarana, endTime: nextKaranaEnd });
current = new Date(nextKaranaEnd.getTime() + 60 * 1000); // move 1 min ahead to avoid infinite loop
lastKarana = getKarana((0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon, (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon);
}
}
return transitions;
}
function findTithiTransitions(startDate, endDate) {
const transitions = [];
let current = new Date(startDate);
let lastTithi = getTithi((0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon, (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon);
while (current < endDate) {
const nextTithiEnd = (() => {
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon;
let diff = moonLon - sunLon;
if (diff < 0)
diff += 360;
const tithiIndex = Math.floor(diff / 12);
const nextTithiAngle = (tithiIndex + 1) * 12;
const targetAngle = nextTithiAngle % 360;
const tithiFunc = (d) => {
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, d, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
let diff = moonLon - sunLon;
if (diff < 0)
diff += 360;
if (diff < targetAngle - 180)
diff += 360;
return diff - targetAngle;
};
return search(tithiFunc, current);
})();
if (!nextTithiEnd || nextTithiEnd > endDate) {
transitions.push({ index: lastTithi, name: exports.tithiNames[lastTithi] || String(lastTithi), endTime: endDate });
break;
}
else {
transitions.push({ index: lastTithi, name: exports.tithiNames[lastTithi] || String(lastTithi), endTime: nextTithiEnd });
current = new Date(nextTithiEnd.getTime() + 60 * 1000);
lastTithi = getTithi((0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon, (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon);
}
}
return transitions;
}
function findNakshatraTransitions(startDate, endDate) {
const transitions = [];
let current = new Date(startDate);
let lastNakshatra = getNakshatra((0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon);
while (current < endDate) {
const nextNakshatraEnd = (() => {
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon;
const nakshatraIndex = Math.floor(moonLon / (13 + 1 / 3));
const nextNakshatraLongitude = (nakshatraIndex + 1) * (13 + 1 / 3);
const targetLon = nextNakshatraLongitude % 360;
const nakshatraFunc = (d) => {
let moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
if (moonLon < targetLon - 180)
moonLon += 360;
return moonLon - targetLon;
};
return search(nakshatraFunc, current);
})();
if (!nextNakshatraEnd || nextNakshatraEnd > endDate) {
transitions.push({ index: lastNakshatra, name: exports.nakshatraNames[lastNakshatra] || String(lastNakshatra), endTime: endDate });
break;
}
else {
transitions.push({ index: lastNakshatra, name: exports.nakshatraNames[lastNakshatra] || String(lastNakshatra), endTime: nextNakshatraEnd });
current = new Date(nextNakshatraEnd.getTime() + 60 * 1000);
lastNakshatra = getNakshatra((0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon);
}
}
return transitions;
}
function findYogaTransitions(startDate, endDate) {
const transitions = [];
let current = new Date(startDate);
let lastYoga = getYoga((0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon, (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon);
while (current < endDate) {
const nextYogaEnd = (() => {
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon;
const totalLongitude = sunLon + moonLon;
const yogaWidth = 360 / 27;
const yogaIndex = Math.floor(totalLongitude / yogaWidth);
const nextYogaBoundary = (yogaIndex + 1) * yogaWidth;
const yogaFunc = (d) => {
const sunLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, d, true)).elon;
const moonLon = (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, d, true)).elon;
let totalLon = sunLon + moonLon;
if (totalLon < nextYogaBoundary - 270)
totalLon += 360;
return totalLon - nextYogaBoundary;
};
return search(yogaFunc, current);
})();
if (!nextYogaEnd || nextYogaEnd > endDate) {
transitions.push({ index: lastYoga, name: exports.yogaNames[lastYoga] || String(lastYoga), endTime: endDate });
break;
}
else {
transitions.push({ index: lastYoga, name: exports.yogaNames[lastYoga] || String(lastYoga), endTime: nextYogaEnd });
current = new Date(nextYogaEnd.getTime() + 60 * 1000);
lastYoga = getYoga((0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, current, true)).elon, (0, astronomy_engine_1.Ecliptic)((0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, current, true)).elon);
}
}
return transitions;
}
function getPanchangam(date, observer) {
const sunVector = (0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Sun, date, true);
const moonVector = (0, astronomy_engine_1.GeoVector)(astronomy_engine_1.Body.Moon, date, true);
const sunEcliptic = (0, astronomy_engine_1.Ecliptic)(sunVector);
const moonEcliptic = (0, astronomy_engine_1.Ecliptic)(moonVector);
const sunrise = getSunrise(date, observer);
const sunset = getSunset(date, observer);
const moonrise = getMoonrise(date, observer);
const moonset = getMoonset(date, observer);
const nakshatraStartTime = findNakshatraStart(date);
const nakshatraEndTime = findNakshatraEnd(date);
const tithiStartTime = findTithiStart(date);
const tithiEndTime = findTithiEnd(date);
const yogaEndTime = findYogaEnd(date);
const rahuKalam = (sunrise && sunset) ? calculateRahuKalam(sunrise, sunset, getVara(date)) : null;
// For Karana transitions, use sunrise to next day's sunrise
let nextSunrise = null;
if (sunrise) {
const nextDay = new Date(sunrise.getTime());
nextDay.setDate(nextDay.getDate() + 1);
nextSunrise = getSunrise(nextDay, observer);
}
const karanaTransitions = (sunrise && nextSunrise)
? findKaranaTransitions(sunrise, nextSunrise)
: [];
const tithiTransitions = (sunrise && nextSunrise)
? findTithiTransitions(sunrise, nextSunrise)
: [];
const nakshatraTransitions = (sunrise && nextSunrise)
? findNakshatraTransitions(sunrise, nextSunrise)
: [];
const yogaTransitions = (sunrise && nextSunrise)
? findYogaTransitions(sunrise, nextSunrise)
: [];
return {
tithi: getTithi(sunEcliptic.elon, moonEcliptic.elon),
nakshatra: getNakshatra(moonEcliptic.elon),
yoga: getYoga(sunEcliptic.elon, moonEcliptic.elon),
karana: getKarana(sunEcliptic.elon, moonEcliptic.elon),
vara: getVara(date),
sunrise,
sunset,
moonrise,
moonset,
nakshatraStartTime,
nakshatraEndTime,
tithiStartTime,
tithiEndTime,
yogaEndTime,
rahuKalamStart: rahuKalam?.start || null,
rahuKalamEnd: rahuKalam?.end || null,
karanaTransitions,
tithiTransitions,
nakshatraTransitions,
yogaTransitions,
};
}
function getPanchangamDetails(date, observer) {
const panchangam = getPanchangam(date, observer);
const sunrise = getSunrise(date, observer);
const sunset = getSunset(date, observer);
const nakshatraEndTime = findNakshatraEnd(date);
return {
...panchangam,
sunrise,
sunset,
nakshatraEndTime,
};
}
//# sourceMappingURL=panchangam.js.map