Skip to content

[Bug] Incorrect sunrise / sunset returned for America/Denver, always off by 1730904ms (5330904ms DST) #185

@EagleLizard

Description

@EagleLizard

I have a job that runs each day to schedule events for sunrise and sunset, and ran into an issue where the sunrise and sunset calculations return the incorrect day for the event given a date.

The API for getTimes doesn't specify any special formatting or timezone considerations, so I assumed these would be handled in-library; this is not the case for all locales.

Below is a binary search for times.sunrise that I wrote for the America/Denver timezone for the coordinates of Salt Lake City, Utah, USA. I tried to do the same for Australia, and while it's clear there's a difference, the exact behavior eludes me and didn't fit into my test handle.

SunCalc computes sunrise in a way that is always off by 1730904ms / 5330904ms (DST). This presents an issue for writing a workaround, since it's unclear how each coordinate+TZ combination will be affected. Each dev with issues must figure out what they need for their locale, with and without DST (as applicable).

I made an attempt to investigate in more detail but did not have time to dig deeper; my thinking at this point is that the calculations in the library have errors related to:

  1. Floating point precision/Rounding from numerical constants (e.g. to/from Julian dates)
  2. Inherent differences between Julian Date calculations and modern calendars (or JS-specific handling)
  3. Perhaps timezones and locales need be considered as primary parameters. For example, in Northern European countries there are many days where no sunrise or sunset occurs. I wonder what this library returns there...

I believe this is the case for several other reported issues in this repository, including those for Australia and DST. Some of those include:

  1. getTimes returns a sunrise/sunset on the next day for certain lat/long #174
  2. Sunset times are off for Australia #172
  3. Wrong sunset calculation near a midnight #149

‼️ For those looking for a working solution

You can see if sunrise-sunset-js works for you; in my testing so far, the underlying NREL calculation appears robust and reliable for local midnight (and other times).

Snippet to Reproduce:

Environment:

  • NodeJS: v24.12.0
  • suncalc: ^1.9.0
// TypeScript
import SunCalc from 'suncalc';
import assert from 'node:assert';

process.env.TZ = 'America/Denver';

const latitude = 40.7606;
const longitude = -111.8881;

(async () => {
  try {
    await suncalcTestMain();
  } catch(e) {
    console.error(e);
    throw e;
  }
})();

async function suncalcTestMain() {
  console.log('suncalcTestMain() ~');
  let startDate = new Date('2025-12-31');
  let years = 10;
  let days = years * 365;
  let d = new Date(startDate.valueOf());
  for(let i = 0; i < days; i++) {
    /*
      expected behavior is getTimes() at local midnight should return
        sunrise and sunset on the same day, ahead in time
    _*/
    d.setDate(d.getDate() + 1);
    d.setHours(0,0,0,0);
    let sc = getSc(d);

    if(sc.sunrise < d) {
      /*
        binary search to find instant where sunrise returns expected value
      _*/
      let scMidnightMs = sunriseSearch(d);
      let scMidnight = new Date(scMidnightMs);
      let midnightDiffMs = (scMidnight.valueOf() - d.valueOf());
      assert(
        midnightDiffMs === 1730904
        || midnightDiffMs === 5330904 // DST
      );
    }
  }
}

/* binary search to find instant where sunrise returns expected value _*/
function sunriseSearch(_d: Date): number {
  let d = new Date(_d.valueOf());
  d.setHours(0,0,0,0);
  let sc = getSc(d);
  let lowerMs = d.valueOf();
  let upperMs = lowerMs + (1000 * 60 * 60 * 12);
  while(lowerMs <= upperMs) {
    let mid = lowerMs + Math.floor((upperMs - lowerMs) / 2);
    sc = getSc(new Date(mid));
    if(sc.sunrise < d) {
      lowerMs = mid + 1;
    } else {
      upperMs = mid - 1;
    }
  }
  let dl = new Date(lowerMs);
  let du = new Date(upperMs);
  let scl = getSc(dl);
  let scu = getSc(du);
  assert(scu.sunrise < scl.sunrise);
  return lowerMs;
}

function getSc(date: Date) {
  return SunCalc.getTimes(date, latitude, longitude);
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions