Skip to content

fix: NextTickAfter stuck on DST fall-back (#50)#68

Merged
adhocore merged 1 commit into
adhocore:mainfrom
azhang-kal:fix/dst-fall-back-bump
May 21, 2026
Merged

fix: NextTickAfter stuck on DST fall-back (#50)#68
adhocore merged 1 commit into
adhocore:mainfrom
azhang-kal:fix/dst-fall-back-bump

Conversation

@azhang-kal

Copy link
Copy Markdown
Contributor

Fixes #50.

NextTickAfter spins / returns invalid ticks during DST fall-back when the schedule skips the ambiguous wall-clock hour. Repro: "0 */8 * * *" from 2024-11-03 00:30 EDT in America/New_York returns 01:00 EDT (which IsDue then says false on), and the next call errors with "tried so hard".

bump() for the hour case reconstructs via time.Date(...), which picks the first occurrence of an ambiguous wall hour — same hour as ref, so the bump advances 0s in UTC and bumpUntilDue spins. Same shape in the minute case.

Fix asserts the bump moved forward and corrects with +1h if not.

Scoped to hour + minute only since those are the cases that fire in any DST zone. Day/month/year reconstruct at midnight and would only be affected in midnight-DST zones — happy to follow up with a defense-in-depth pass if you want.

Test added covering the ambiguous-hour skip and the original "tried so hard" repro from #50.

bump() for hour/minute reconstructs via time.Date, which picks the
first occurrence of an ambiguous wall-clock hour. The bump advances
0s in UTC and bumpUntilDue spins until its iter limit.

Assert the bump moved forward; correct with +1h if not.

Fixes adhocore#50.
@azhang-kal azhang-kal requested a review from adhocore as a code owner May 19, 2026 05:59
@azhang-kal

azhang-kal commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

Hi @adhocore ! Would appreciate any feedback and a release after fix if possible - this gets a nasty bug around DST, added some tests you can run to prove correctness as well or come up with a good suggestion yourself

@adhocore

Copy link
Copy Markdown
Owner

thank you very much. checking this shortly, for now just triggered the github action pipelines.

Comment thread next.go
ref = time.Date(minTime.Year(), minTime.Month(), minTime.Day(), minTime.Hour(), minTime.Minute(), 0, 0, loc)
next := time.Date(minTime.Year(), minTime.Month(), minTime.Day(), minTime.Hour(), minTime.Minute(), 0, 0, loc)
if !next.After(ref) {
next = next.Add(time.Hour)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it to be advanced by a minute or hour?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be an hour. For minutely advance on the DST boundary, when we advance from 1:59 back to 1:00 on day of US DST, we need to advance a full hour to be correct

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @adhocore! Did you get the chance to take a look?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hello, is there an issue if we advance by 15min? I think timezones are in gaps of 15min, 30min and 1 or more hours. so 15min seem to cover them all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the issue is this line

ref = time.Date(minTime.Year(), minTime.Month(), minTime.Day(), minTime.Hour(), minTime.Minute(), 0, 0, loc)

shifts the UTC instant ~60min backwards around DST. Example:

  From ref = 01:59 EDT (UTC 05:59Z):
  ref + 1m              → UTC 06:00Z = 01:00 EST          (real next minute)
  time.Date(...1, 0, …) → UTC 05:00Z = 01:00 EDT          (first occurrence wins → -60min in UTC)

With +15min: next = UTC 05:15Z = 01:15 EDT. Still 44min before ref, but bump returns it. bumpUntilDue then walks 01:16 → 01:17 → … → 01:59 EDT, hits the same boundary, collapses again, bounces to 01:15, and keeps looping

With +1h: next = UTC 06:00Z = 01:00 EST — past the ambiguous region, walker proceeds normally.

The correction has to be ≥ the shift, open to some other ideas though

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok then

@jgustie

jgustie commented May 20, 2026

Copy link
Copy Markdown

I just ran into this as well, looking at the implementation of bump I was actually wondering if the hour and minute cases should just be using Truncate instead of time.Date (e.g. ref = ref.Add(time.Hour).Tuncate(time.Hour)); that seems accomplish the same thing while still preserving the correct offset at the same location.

@azhang-kal

Copy link
Copy Markdown
Contributor Author

I think that might break non whole-hour-offset zones (take nepal time for example) since it's rounding the UTC instant

@adhocore adhocore merged commit 5068277 into adhocore:main May 21, 2026
10 checks passed
@adhocore

Copy link
Copy Markdown
Owner

hi guys thanks for participation, and special thanks to @azhang-kal for pr. it's been merged and released in v1.20.0

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.

tried so hard

3 participants