@alliumlabs/panchanga-ts
Version:
A TypeScript library for Hindu calendar (Panchanga) calculations including tithis, nakshatras, and other astronomical elements
172 lines (157 loc) • 4.64 kB
text/typescript
import {
AstroTime,
Observer,
Body,
Vector,
Equator,
Ecliptic,
} from "astronomy-engine";
import moment from "moment-timezone";
export function getJulianDay(date: Date): number {
return date.getTime() / 86400000 + 2440587.5;
}
export function mod360(angle: number): number {
return ((angle % 360) + 360) % 360;
}
export function getAngleDifference(angle1: number, angle2: number) {
// Calculate the absolute difference and normalize it within 0-360 degrees
let diff = Math.abs(angle1 - angle2) % 360;
// If the difference is more than 180, take the complementary angle
if (diff > 180) {
diff = 360 - diff;
}
return diff;
}
export function formatTimeFromDate(date: Date): string {
const hh = date.getHours().toString().padStart(2, "0");
const mm = date.getMinutes().toString().padStart(2, "0");
const ss = date.getSeconds().toString().padStart(2, "0");
return `${hh}:${mm}:${ss}`;
}
export function adjustTimeByTimezone(
date: Date,
timezone: string | number
): Date {
if (typeof timezone === "string" && isNaN(Number(timezone))) {
try {
const formatter = new Intl.DateTimeFormat("en-US", {
timeZone: timezone,
timeZoneName: "short",
});
return new Date(formatter.format(date));
} catch (e) {
console.warn(
`Invalid timezone string: ${timezone}. Using numeric offset.`
);
return new Date(date.getTime() + Number(timezone) * 3600000);
}
} else {
return new Date(date.getTime() + Number(timezone) * 3600000);
}
}
export function adjustToLocalTime(date: Date, tz: string): Date {
// Create a moment object from the UTC date, then convert to the specified timezone.
return moment.utc(date).tz(tz).toDate();
}
export function astroTimeToISOString(
time: AstroTime,
timezone: string | number
): string {
const localDate = adjustTimeByTimezone(time.date, timezone);
return localDate.toISOString();
}
export function astroTimeToLocalTimeString(
time: AstroTime,
timezone: string | number
): string {
const localDate = adjustTimeByTimezone(time.date, timezone);
return formatTimeFromDate(localDate);
}
export function addDays(time: AstroTime, days: number): AstroTime {
const d = new Date(time.date.getTime() + days * 86400000);
return new AstroTime(d);
}
export function inverseLagrange(
x: number[],
y: number[],
target: number
): number {
let total = 0;
for (let i = 0; i < x.length; i++) {
let numer = 1;
let denom = 1;
for (let j = 0; j < x.length; j++) {
if (j !== i) {
numer *= target - y[j];
denom *= y[i] - y[j];
}
}
total += (numer * x[i]) / denom;
}
return total;
}
export async function interpolateTime(
startTime: AstroTime,
observer: Observer,
target: number,
f: (t: AstroTime) => number,
offsets: number[] = [0, 0.25, 0.5, 0.75, 1.0]
): Promise<AstroTime> {
const samples: number[] = [];
for (const offset of offsets) {
const sampleTime = addDays(startTime, offset);
let value = f(sampleTime);
value = mod360(value);
samples.push(value);
}
const frac = inverseLagrange(offsets, samples, target);
return addDays(startTime, frac);
}
// Get tropical ecliptic longitude of a body.
export function tropicalLongitude(
body: Body,
time: AstroTime,
observer: Observer
): number {
const eq = Equator(body, time, observer, true, false);
// Convert equatorial coordinates (ra, dec in degrees) to a unit vector.
const raRad = (eq.ra * Math.PI) / 180;
const decRad = (eq.dec * Math.PI) / 180;
const vector = new Vector(
Math.cos(decRad) * Math.cos(raRad),
Math.cos(decRad) * Math.sin(raRad),
Math.sin(decRad),
time
);
// Pass the vector to Ecliptic.
const ec = Ecliptic(vector);
return mod360(ec.elon);
}
// Approximate Lahiri Ayanamsa.
export function computeAyanamsa(time: AstroTime): number {
const year = time.date.getUTCFullYear();
return 24.07 + 0.014 * (year - 2000);
}
// Compute sidereal longitude: tropical minus ayanamsa.
export function siderealLongitude(
body: Body,
time: AstroTime,
observer: Observer
): number {
const trop = tropicalLongitude(body, time, observer);
const ayan = computeAyanamsa(time);
return mod360(trop - ayan);
}
export function parseInputDate(dateStr: string): Date {
let inputDate = new Date(dateStr);
if (isNaN(inputDate.getTime())) {
const parts = dateStr.split("/");
if (parts.length === 3) {
const [d, m, y] = parts.map(Number);
inputDate = new Date(Date.UTC(y, m - 1, d));
} else {
throw new Error("Invalid date format.");
}
}
return inputDate;
}