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 @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
18 changes: 18 additions & 0 deletions tests/moon.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})

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