164 lines
5.1 KiB
JavaScript
164 lines
5.1 KiB
JavaScript
(function () {
|
|
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
|
const START = ["sol", "luna", "mars", "mercury", "jupiter", "venus", "saturn"];
|
|
const CHALDEAN = ["saturn", "jupiter", "mars", "sol", "venus", "mercury", "luna"];
|
|
|
|
function toTitleCase(value) {
|
|
if (!value) return "";
|
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
}
|
|
|
|
function getDateKey(date) {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
function getCenteredWeekStartDay(date) {
|
|
return (date.getDay() + 4) % 7;
|
|
}
|
|
|
|
function minutesBetween(a, b) {
|
|
return (a.getTime() - b.getTime()) / 60000;
|
|
}
|
|
|
|
function getMoonPhaseName(phase) {
|
|
if (phase < 0.03 || phase > 0.97) return "New Moon";
|
|
if (phase < 0.22) return "Waxing Crescent";
|
|
if (phase < 0.28) return "First Quarter";
|
|
if (phase < 0.47) return "Waxing Gibbous";
|
|
if (phase < 0.53) return "Full Moon";
|
|
if (phase < 0.72) return "Waning Gibbous";
|
|
if (phase < 0.78) return "Last Quarter";
|
|
return "Waning Crescent";
|
|
}
|
|
|
|
function parseMonthDay(monthDay) {
|
|
const [month, day] = monthDay.split("-").map(Number);
|
|
return { month, day };
|
|
}
|
|
|
|
function isDateInSign(date, sign) {
|
|
const { month: startMonth, day: startDay } = parseMonthDay(sign.start);
|
|
const { month: endMonth, day: endDay } = parseMonthDay(sign.end);
|
|
const month = date.getMonth() + 1;
|
|
const day = date.getDate();
|
|
const wrapsYear = startMonth > endMonth;
|
|
|
|
if (!wrapsYear) {
|
|
const afterStart = month > startMonth || (month === startMonth && day >= startDay);
|
|
const beforeEnd = month < endMonth || (month === endMonth && day <= endDay);
|
|
return afterStart && beforeEnd;
|
|
}
|
|
|
|
const afterStart = month > startMonth || (month === startMonth && day >= startDay);
|
|
const beforeEnd = month < endMonth || (month === endMonth && day <= endDay);
|
|
return afterStart || beforeEnd;
|
|
}
|
|
|
|
function getSignStartDate(date, sign) {
|
|
const { month: startMonth, day: startDay } = parseMonthDay(sign.start);
|
|
const month = date.getMonth() + 1;
|
|
const day = date.getDate();
|
|
const wrapsYear = startMonth > parseMonthDay(sign.end).month;
|
|
|
|
let year = date.getFullYear();
|
|
if (wrapsYear && (month < startMonth || (month === startMonth && day < startDay))) {
|
|
year -= 1;
|
|
}
|
|
|
|
return new Date(year, startMonth - 1, startDay);
|
|
}
|
|
|
|
function getSignForDate(date, signs) {
|
|
return signs.find((sign) => isDateInSign(date, sign)) || null;
|
|
}
|
|
|
|
function groupDecansBySign(decans) {
|
|
const map = {};
|
|
for (const decan of decans) {
|
|
if (!map[decan.signId]) {
|
|
map[decan.signId] = [];
|
|
}
|
|
map[decan.signId].push(decan);
|
|
}
|
|
|
|
for (const signId of Object.keys(map)) {
|
|
map[signId].sort((a, b) => a.index - b.index);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
function getDecanForDate(date, signs, decansBySign) {
|
|
const sign = getSignForDate(date, signs);
|
|
if (!sign) return null;
|
|
|
|
const signDecans = decansBySign[sign.id] || [];
|
|
if (!signDecans.length) {
|
|
return { sign, decan: null };
|
|
}
|
|
|
|
const signStartDate = getSignStartDate(date, sign);
|
|
const daysSinceSignStart = Math.floor((date.getTime() - signStartDate.getTime()) / DAY_IN_MS);
|
|
let index = Math.floor(daysSinceSignStart / 10) + 1;
|
|
if (index < 1) index = 1;
|
|
if (index > 3) index = 3;
|
|
|
|
const decan = signDecans.find((entry) => entry.index === index) || signDecans[0];
|
|
return { sign, decan };
|
|
}
|
|
|
|
function calcPlanetaryHoursForDayAndLocation(date, geo) {
|
|
const sunCalc = window.SunCalc;
|
|
if (!sunCalc) {
|
|
throw new Error("SunCalc library is not loaded.");
|
|
}
|
|
|
|
const solar = sunCalc.getTimes(date, geo.latitude, geo.longitude);
|
|
const nextDay = new Date(date.getTime() + DAY_IN_MS);
|
|
const solarNext = sunCalc.getTimes(nextDay, geo.latitude, geo.longitude);
|
|
const dayOfWeek = date.getDay();
|
|
const chaldeanStartPos = CHALDEAN.indexOf(START[dayOfWeek]);
|
|
|
|
const dayHourInMinutes = minutesBetween(solar.sunset, solar.sunrise) / 12;
|
|
const nightHourInMinutes = minutesBetween(solarNext.sunrise, solar.sunset) / 12;
|
|
|
|
const hours = [];
|
|
for (let hour = 0; hour < 12; hour += 1) {
|
|
const start = new Date(solar.sunrise.getTime() + dayHourInMinutes * hour * 60_000);
|
|
const end = new Date(start.getTime() + dayHourInMinutes * 60_000);
|
|
hours.push({
|
|
start,
|
|
end,
|
|
planetId: CHALDEAN[(chaldeanStartPos + hour) % 7],
|
|
isDaylight: true
|
|
});
|
|
}
|
|
|
|
for (let hour = 12; hour < 24; hour += 1) {
|
|
const start = new Date(solar.sunset.getTime() + nightHourInMinutes * (hour - 12) * 60_000);
|
|
const end = new Date(start.getTime() + nightHourInMinutes * 60_000);
|
|
hours.push({
|
|
start,
|
|
end,
|
|
planetId: CHALDEAN[(chaldeanStartPos + hour) % 7],
|
|
isDaylight: false
|
|
});
|
|
}
|
|
|
|
return hours;
|
|
}
|
|
|
|
window.TarotCalc = {
|
|
DAY_IN_MS,
|
|
toTitleCase,
|
|
getDateKey,
|
|
getCenteredWeekStartDay,
|
|
getMoonPhaseName,
|
|
groupDecansBySign,
|
|
getDecanForDate,
|
|
calcPlanetaryHoursForDayAndLocation
|
|
};
|
|
})();
|