Skip to content

Keep polar sunrises and sunsets near the date line on their own date - #296

Merged
KosherJava merged 3 commits into
KosherJava:masterfrom
dickermoshe:fix-date-line-sunrise
Oct 23, 2026
Merged

KosherJava merged 3 commits into
KosherJava:masterfrom
dickermoshe:fix-date-line-sunrise

Conversation

@dickermoshe

@dickermoshe dickermoshe commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

The bug

Near the date line, in polar winter or summer, getSunrise and getSunset can return a time on the wrong day, and every zman built on them goes wrong with it.

At Cape Schmidt, Chukotka (68.88, -179.37, Asia/Anadyr) on 10 January 2021, the sun is up for about ten minutes around noon. All three calculators return a sea-level sunrise of 2021-01-09 12:00:23 local time, a day early. Sunset correctly comes back as 2021-01-10 12:09:57, so the "day" runs for more than 24 hours. getSofZmanShmaGRA then comes out as 2021-01-09 18:02:47 instead of about 12:02 on the 10th.

Why

getInstantFromTime gets the event's UTC time folded into [0, 24) and picks the date from the local mean time. It moves a sunrise after 18:00 back a day and a sunset before 06:00 forward a day, but it never handles the opposite wrap.

Just west of the date line, a sunrise near local noon has a true UTC time just past 24:00. It folds to just past 00:00 and lands a day early. Just east of the line, a sunset near local noon can fold the other way and land a day late.

The fix

A sunrise at -6 hours local mean time or earlier now moves forward a day, and a sunset at +30 hours or later moves back a day. The mapping then matches the window each date's events belong to. A sunrise belongs from 18:00 the evening before through 18:00, and a sunset from 06:00 through 06:00 the next morning. Ordinary days never reach those values, so their times don't change.

The comment above the checks now explains each one in plain words, with an example of the wrap just west of the date line.

Testing

  • AstronomicalCalendarTest.polarSunriseNearTheDateLineIsOnItsOwnDate checks Cape Schmidt on 10 January 2021 with the NOAA, Meeus and SPA calculators. It fails without the fix ("expected 2021-01-10 but was 2021-01-09") and passes with it.
  • Every date adjustment in getInstantFromTime now has its own test, each with a real place and date that takes it. There's a sunrise after 18:00 local mean time, a sunset before 06:00 or at +30 hours or later, solar midnight before noon, and solar noon before 0 or after 24 hours. The date-line sunrise test above covers a sunrise at -6 hours or earlier. Each test pins the instant to within a second, so a result on the wrong day fails. Disabling any one branch makes its own test fail.
  • All 139 tests pass.
  • The same change in a port of this library was checked on 759,577 sunrises and sunsets within 4° of the date line above 60° north and south, with all three calculators. Before the change 10 fell outside their date's window, and after it none do.

getInstantFromTime receives the event's UTC time folded into [0, 24) and
picks its date from the local mean time. It moved a sunrise after 18:00
back a day and a sunset before 06:00 forward a day, but not the other way.

Just west of the date line, a sunrise near local noon has a UTC time just
past 24:00. It folds to just past 00:00 and was placed a day early. East of
the date line a sunset near local noon could fold the other way and land a
day late. This happens in polar winter and summer, when sunrise and sunset
are close to noon.

A sunrise at -6 hours local mean time or earlier now moves forward a day,
and a sunset at +30 hours or later moves back a day. The date mapping then
matches the window each date's sunrise (18:00 the evening before through
18:00) and sunset (06:00 through 06:00 the next morning) belong to.
One test per branch, each with a real place and date that takes it: a
sunrise after 18:00 local mean time, a sunset before 06:00 or at +30 hours
or later, solar midnight before noon, and solar noon before 0 or after 24
hours local mean time. The existing date-line sunrise test covers a sunrise
at -6 hours or earlier.

Each test pins the instant to within a second, so a result on the wrong day
fails. Disabling any one branch makes its own test fail.
The comment now says why the UTC time of day alone doesn't give the date, what window of local mean time each event belongs to, and why each check moves the date, with an example of the wrap just west of the date line.
@KosherJava
KosherJava merged commit a86bc6f into KosherJava:master Oct 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants