jasonsturges/lunarphase-js

Moon.lunarPhase() anticipating moon phase

sperardt opened this issue ยท 11 comments

Hi Jason! Thanks for creating this library.
I have been using it for a couple of weeks and just noticed that the method Moon.lunarPhase(date); is returning "New Moon" phase on Jan 31, 2022 at 5pm EST. However, New Moon is going to be in effect on Feb 01, 2022 at 12:46 am EST.

@sperardt Hi, thanks.

Somewhat similar issue as determining when a sunset actually occurs (civil, nautical, or astronomical twilight).

It's a rounding issue, which has confused several people. This chart might help:

Phase Start Event End
New 0 1.84566173161
Waxing Crescent 1.84566173161 3.69132346322 5.53698519483
First Quarter 5.53698519483 7.38264692644 9.22830865805
Waxing Gibbous 9.22830865805 11.07397038966 12.91963212127
Full 12.91963212127 14.76529385288 16.61095558449
Waning Gibbous 16.61095558449 18.4566173161 20.30227904771
Last Quarter 20.30227904771 22.14794077932 23.99360251093
Waning Crescent 23.99360251093 25.83926424254 27.68492597415
New 27.68492597415 29.53058770576

I think the terms "start" and "end" need clarity, but the "event" is the actual moment in time.

So, halfway through the 3.691 days of the phase, it rounds up to the next upcoming cycle.

image

Instead of starting at the moment 0 and lasting until the exact moment of the next phase, it's offset to report the closest event. Once you're closer to the New Moon phase than the previous phase, it rounds to New Moon.

Application here is like reporting current weather, but several people have expressed either confusion or desire for this to work differently. Some are planning around exact moments in time; others seem to want the phase from event to next event.

Any recommendation there as to how you'd like to use / configure this?

Design wise, I'd like to keep this simple / functional, but I think it's getting to the point there needs to be some kind of options / params config passed to the functions.

Some kind of:

Moon.function(date, { ...options })

I really like date being the only data model for this, but maybe an options parameter where you could specify:

  • cycleStart: event
  • cycleStart: round

Or something like that here...

Hi @jasonsturges !
Thanks for replying and sorry for not getting back to you yesterday. I had a quite busy day.

Yeah, I thought it could be a rounding issue.

So, I am using the library on an astrological mobile app where I display the moon phase + a little description about the moon phase. And it got my attention since my moon calendar were saying that was still waning crescent but was getting new moon from the library.

Do you know why we are having issues with rounding? I mean, the actual number of decimal places is higher than the ones you listed above? Or it is a Js function you are using that is rounding the number?

Aff.. I sent the previous comment when I wanted to add a new line haha

Moon.function(date, { ...options }) I also prefer having just the date as a parameter, but having a new parameter can be easier to implement and control what type the calculations need to be done. If !{... options} you treat as it is today

This operates like a weather app, and rounds on purpose. It will show waning crescent starting 1.85, even though that phase occurs at 3.69 - and will continue to show through 5.54 until the next phase is closer.

Several people have contacted me regarding astrology usage - I'm unsure how the phase is leveraged or calcualted, but someone also pointed out that astrological calendars bounce to a 30th lunar day.

Lunar vs lunisolar calendars align lunar months with the solar year through different intercalation - approximately 29.5 days, although it appears some calendars alternate between 29 and 30 day synodic months to complete one year of 12 lunations.

Here, using the Gregorian calendar, this fraction normalizes years (and leap years) providing accuracy for the next 31,000 years at which point this calculation will be 1-day behind.

Think it's time to alter this library to accept a config payload, and I might make the default to start on the day... I'll get back to you shortly.

Think I'm going to change the calculation to report phase at the event, then add a closestPhase() function to retain the original intent of this library - weather station graphics implementation.

Although I still like an options payload context.

Should have this updated in the next couple of days.

Still evaluating whether I like this, and need to run test cycles of the next several days, but I've landed at:

Moon options, in which cycle start at the event in the northern hemisphere:

export type MoonOptions = {
  readonly cycleStart: CycleStart;
  readonly hemisphere: Hemisphere;
};

export const defaultMoonOptionsFactory = (): MoonOptions => ({
  cycleStart: CycleStart.EVENT,
  hemisphere: Hemisphere.NORTHERN,
});

Hi, I just stumbled into the same "issue" with something executing before the actual full moon due to how the library behaves. I think your suggestion to provide an options/config param is a good approach and we would be able to switch back and forth between different behaviours (exact or rounding) based on what the needs of different applications are. Happy to help if needed (at least with testing).

@sperardt @protyze I've come to the conclusion that presentation concerns need separation from this calculation. As this was originally used to display graphics, there needs to be angle / percentage functions for visual display separate of reported phase.

This is also confounded by primary and intermediary phases.

I'm just going to shift this calculation to align with immediate needs.

Although, I do like the configuration options - that will assist implementing other feedback, but that will come later.

That makes sense and people can actually implement their own calculation / phase-mapping using .lunarAgePercent() if they need to โ€“ which is what I use now to react more accurate to a specific phase or "age" of the moon in my application. But this follows the whole cycle from new moon to new moon. Maybe a helper method that returns a value between 0 and 1 and expresses how much the moon is "lit" at a specific date. 1 = full, 0 = new, 0.5 is either first or third quarter depending on isWaning(), isWaxing() or lunarPhase().