-
-
Notifications
You must be signed in to change notification settings - Fork 580
fix: resolve DST bug in sunrise/sunset trigger timing (#3737) #3754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8df6e81
e0ce6e6
daefec1
07662fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -258,16 +258,22 @@ export class TriggersEventTimer { | |||||||||||||||||||
|
|
||||||||||||||||||||
| // Modified function to calculate the sunrise/set time by adam-carter-fms | ||||||||||||||||||||
| // https://gist.github.com/adam-carter-fms/a44a14c0a8cdacbbc38276f6d553e024#file-sunriseset-js-L12 | ||||||||||||||||||||
| // | ||||||||||||||||||||
| // FIX for issue #3737 (DST sunset/sunrise timing bug): | ||||||||||||||||||||
| // The original code used: `const diff = now - start + (start.getTimezoneOffset() - now.getTimezoneOffset()) * 60 * 1000` | ||||||||||||||||||||
| // This caused sunset times to jump 60+ minutes across DST boundaries because getTimezoneOffset() | ||||||||||||||||||||
| // changes during DST transitions. The fix: calculate day of year using UTC dates to eliminate | ||||||||||||||||||||
| // timezone offset issues entirely. UTC never observes DST, so there's no offset change. | ||||||||||||||||||||
| function getSunEvent(sunset: boolean, latitude: number, longitude: number, offset: number, nextDay: number): Date { | ||||||||||||||||||||
| const res = new Date() | ||||||||||||||||||||
| res.setDate(res.getDate() + nextDay) | ||||||||||||||||||||
| const now = res | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const start = new Date(now.getFullYear(), 0, 0) | ||||||||||||||||||||
| // @ts-expect-error TS claims dates can't be subtracted, this should be revisited but I don't want to touch what works | ||||||||||||||||||||
| const diff = now - start + (start.getTimezoneOffset() - now.getTimezoneOffset()) * 60 * 1000 | ||||||||||||||||||||
| const oneDay = 1000 * 60 * 60 * 24 | ||||||||||||||||||||
| const day = Math.floor(diff / oneDay) | ||||||||||||||||||||
| // Calculate day of year using dayjs to avoid any DST/timezone issues | ||||||||||||||||||||
| // dayjs handles calendar calculations correctly regardless of DST | ||||||||||||||||||||
| const jan1 = dayjs(new Date(now.getFullYear(), 0, 1)) | ||||||||||||||||||||
| const today = dayjs(new Date(now.getFullYear(), now.getMonth(), now.getDate())) | ||||||||||||||||||||
| const day = today.diff(jan1, 'day') | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const zenith = 90.83333333333333 | ||||||||||||||||||||
| const D2R = Math.PI / 180 | ||||||||||||||||||||
|
|
@@ -333,17 +339,22 @@ export class TriggersEventTimer { | |||||||||||||||||||
| UT = UT + 24 | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const ms = UT * 60 * 60 * 1000 | ||||||||||||||||||||
| // UT is the UTC time as decimal hours when sunset occurs | ||||||||||||||||||||
| const utHours = Math.floor(UT) | ||||||||||||||||||||
| const utMinutes = Math.floor((UT - utHours) * 60) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const sunEventTime = new Date(ms) | ||||||||||||||||||||
| sunEventTime.setFullYear(now.getFullYear()) | ||||||||||||||||||||
| sunEventTime.setMonth(now.getMonth()) | ||||||||||||||||||||
| sunEventTime.setDate(now.getDate()) | ||||||||||||||||||||
| // Get the UTC date that corresponds to this local date | ||||||||||||||||||||
| const localDateAtMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()) | ||||||||||||||||||||
| const utcYear = localDateAtMidnight.getUTCFullYear() | ||||||||||||||||||||
| const utcMonth = localDateAtMidnight.getUTCMonth() | ||||||||||||||||||||
| const utcDate = localDateAtMidnight.getUTCDate() | ||||||||||||||||||||
|
Comment on lines
+346
to
+350
|
||||||||||||||||||||
| // Get the UTC date that corresponds to this local date | |
| const localDateAtMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()) | |
| const utcYear = localDateAtMidnight.getUTCFullYear() | |
| const utcMonth = localDateAtMidnight.getUTCMonth() | |
| const utcDate = localDateAtMidnight.getUTCDate() | |
| // Get the UTC date components directly from 'now' | |
| const utcYear = now.getUTCFullYear() | |
| const utcMonth = now.getUTCMonth() | |
| const utcDate = now.getUTCDate() |
Copilot
AI
Nov 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] There's a subtle issue with the seconds component in the UTC time calculation. Line 353 sets seconds to 0, but the original calculation produces a fractional number of minutes in UT. When converting UT to hours and minutes on lines 343-344, the fractional seconds are lost due to Math.floor().
For more precise timing, consider:
const utSeconds = Math.floor(((UT - utHours) * 60 - utMinutes) * 60)
const sunEventTime = new Date(Date.UTC(utcYear, utcMonth, utcDate, utHours, utMinutes, utSeconds))This preserves the precision of the astronomical calculation rather than truncating to the nearest minute. While the difference is small (up to 59 seconds), for a sun event trigger this could matter for user expectations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a potential issue with the day-of-year calculation using dayjs. On lines 274-276, you're creating new Date objects with local timezone for both dates before passing them to dayjs. This means if DST occurs on January 1st or the current date (unlikely but theoretically possible in some locales), or if the system timezone differs from the user's intent, this could still introduce timezone-related issues.
Consider using dayjs with UTC mode consistently:
This would ensure the day-of-year calculation is purely in UTC, completely eliminating any DST or timezone concerns.