Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/moon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,8 +996,11 @@ export const isFullMoon = (datetime: Date): boolean => getLunarPhase(datetime) =
* @returns The date of the next full Moon.
*/
export const getNextFullMoon = (datetime: Date): Date => {
// Amend the date to midnight on the given date:
let date = new Date(new Date(datetime).setHours(0, 0, 0, 0))
// The search begins at the datetime given, and not at the midnight before it: the midnight of
// the host system is not the midnight of UTC, and so the search would begin at an instant that
// depends on where the host is, and it would resolve a full Moon that has already passed earlier
// on the same day as the one that follows it:
let date = new Date(datetime)

// The maximum number of days in a synodic month is 29, so if we increment the
// date by 1 hour until we reach a full Moon, we will eventually reach
Expand Down Expand Up @@ -1045,6 +1048,14 @@ export const getNextFullMoon = (datetime: Date): Date => {
}
}

// The Moon reads as full over a window of ~20 hours centred on the syzygy, and so the datetime
// given may lie within it but beyond the syzygy itself, e.g., the full Moon resolved from it has
// already passed. The search resumes two days on, which clears the remainder of that window, for
// the full Moon that follows it:
if (fullMoon <= datetime) {
return getNextFullMoon(new Date(fullMoon.getTime() + 2 * 24 * 60 * 60 * 1000))
}

return fullMoon
}

Expand Down
12 changes: 12 additions & 0 deletions tests/moon.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,18 @@ describe('getNextFullMoon', () => {
const nextFullMoon = getNextFullMoon(datetime)
expect(nextFullMoon.toISOString()).toBe('2021-05-26T11:31:30.000Z')
})

it('should return a full Moon that is still to come for a datetime just after one', () => {
// The Moon reads as full for ~20 hours about the syzygy, and so a datetime within that window
// but beyond the syzygy resolved the full Moon that had already passed:
const passed = new Date('2024-08-19T18:26:00.000Z')

for (const hours of [1, 6, 12, 18]) {
const datetime = new Date(passed.getTime() + hours * 60 * 60 * 1000)

expect(getNextFullMoon(datetime).getTime()).toBeGreaterThan(datetime.getTime())
}
})
})

/*****************************************************************************************************************/
Expand Down