diff --git a/src/moon.ts b/src/moon.ts index 1479d01..11d8ca7 100644 --- a/src/moon.ts +++ b/src/moon.ts @@ -918,8 +918,11 @@ export const isNewMoon = (datetime: Date): boolean => getLunarPhase(datetime) == * @returns The date of the next new Moon. */ export const getNextNewMoon = (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 new 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 new Moon, we will eventually reach @@ -967,6 +970,14 @@ export const getNextNewMoon = (datetime: Date): Date => { } } + // The Moon reads as new over a window of ~22 hours centred on the syzygy, and so the datetime + // given may lie within it but beyond the syzygy itself, e.g., the new Moon resolved from it has + // already passed. The search resumes two days on, which clears the remainder of that window, for + // the new Moon that follows it: + if (newMoon <= datetime) { + return getNextNewMoon(new Date(newMoon.getTime() + 2 * 24 * 60 * 60 * 1000)) + } + return newMoon } diff --git a/tests/moon.spec.ts b/tests/moon.spec.ts index f86948d..ca8ed80 100644 --- a/tests/moon.spec.ts +++ b/tests/moon.spec.ts @@ -475,6 +475,24 @@ describe('getNextNewMoon', () => { const nextNewMoon = getNextNewMoon(datetime) expect(nextNewMoon.toISOString()).toBe('2021-06-10T10:54:20.000Z') }) + + it('should return a new Moon that is still to come for a datetime just after one', () => { + // The Moon reads as new for ~22 hours about the syzygy, and so a datetime within that window + // but beyond the syzygy resolved the new Moon that had already passed: + const passed = new Date('2024-09-03T02:35:30.000Z') + + for (const hours of [1, 6, 12, 18]) { + const datetime = new Date(passed.getTime() + hours * 60 * 60 * 1000) + + expect(getNextNewMoon(datetime).getTime()).toBeGreaterThan(datetime.getTime()) + } + }) + + it('should return the imminent new Moon for a datetime just before one', () => { + const datetime = new Date('2024-09-02T23:00:00.000Z') + + expect(getNextNewMoon(datetime).toISOString()).toBe('2024-09-03T02:35:30.000Z') + }) }) /*****************************************************************************************************************/