Lunar phases

This commit is contained in:
Leo Vasanko 2025-08-20 19:49:24 -06:00
parent 161c7987af
commit 195520d66f
3 changed files with 45 additions and 2 deletions

View File

@ -13,6 +13,7 @@ import {
getLocalizedWeekdayNames,
getLocalizedMonthName,
formatDateRange
,lunarPhaseSymbol
} from './date-utils.js'
class InfiniteCalendar {
@ -330,8 +331,14 @@ class InfiniteCalendar {
labelYear = cur.getFullYear()
}
const day = document.createElement('h1')
day.textContent = String(cur.getDate())
const day = document.createElement('h1')
day.textContent = String(cur.getDate())
// lunar phase symbol (only for main phases)
const moon = document.createElement('span')
moon.className = 'lunar-phase'
moon.textContent = lunarPhaseSymbol(cur) || ''
if (moon.textContent) cell.appendChild(moon)
const date = toLocalString(cur)
cell.dataset.date = date

View File

@ -22,6 +22,12 @@
transition: background-color .15s ease;
font-size: 1em;
}
.cell .lunar-phase {
position: absolute;
z-index: 1;
top: .25em;
left: 50%;
}
.cell.today h1 {
border-radius: 2em;
background: var(--today);

View File

@ -121,6 +121,35 @@ function formatDateRange(startDate, endDate) {
return `${startISO}/${endISO}`
}
/**
* Compute lunar phase symbol for the four main phases on a given date.
* Returns one of: 🌑 (new), 🌓 (first quarter), 🌕 (full), 🌗 (last quarter), or '' otherwise.
* Uses an approximate algorithm with a fixed epoch.
*/
function lunarPhaseSymbol(date) {
// Reference new moon: 2000-01-06 18:14 UTC (J2000 era), often used in approximations
const ref = Date.UTC(2000, 0, 6, 18, 14, 0)
const synodic = 29.530588853 // days
// Use UTC noon of given date to reduce timezone edge effects
const dUTC = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0)
const daysSince = (dUTC - ref) / DAY_MS
const phase = ((daysSince / synodic) % 1 + 1) % 1
const phases = [
{ t: 0.0, s: '🌑' }, // New Moon
{ t: 0.25, s: '🌓' }, // First Quarter
{ t: 0.5, s: '🌕' }, // Full Moon
{ t: 0.75, s: '🌗' } // Last Quarter
]
// threshold in days from exact phase to still count for this date
const thresholdDays = 0.5 // ±12 hours
for (const p of phases) {
let delta = Math.abs(phase - p.t)
if (delta > 0.5) delta = 1 - delta
if (delta * synodic <= thresholdDays) return p.s
}
return ''
}
// Export all functions and constants
export {
monthAbbr,
@ -136,4 +165,5 @@ export {
getLocalizedWeekdayNames,
getLocalizedMonthName,
formatDateRange
,lunarPhaseSymbol
}