Skip to content

[13.x] Make between()/unlessBetween() independent of timezone() call order#60518

Open
ManicardiFrancesco wants to merge 2 commits into
laravel:13.xfrom
ManicardiFrancesco:fix-between-timezone-call-order
Open

[13.x] Make between()/unlessBetween() independent of timezone() call order#60518
ManicardiFrancesco wants to merge 2 commits into
laravel:13.xfrom
ManicardiFrancesco:fix-between-timezone-call-order

Conversation

@ManicardiFrancesco

Copy link
Copy Markdown

The problem

between() / unlessBetween() evaluate the timezone eagerly, the moment the method is called. So the result silently depends on where timezone() sits in the chain:

// ✅ uses Europe/Rome
$schedule->command('foo')->timezone('Europe/Rome')->between('10:00', '12:00');

// ❌ silently uses the previous timezone (e.g. UTC) — task runs at the wrong hour
$schedule->command('foo')->between('10:00', '12:00')->timezone('Europe/Rome');

Why this is a bug, not user error

between() / unlessBetween() are the only frequency methods whose outcome changes with call order. Every other one is order-independent: testBasicCronCompilation already asserts "chained rules should be commutative". This PR brings these two in line with that existing guarantee.

The fix

Defer the time-interval computation into the filter closure, so $this->timezone is read when the filter runs rather than when it's defined. The two forms above become equivalent.

  • No public API change, ~minimal diff.
  • No behavioral change for chains that were already in the "lucky" order.
  • The schedule is defined and evaluated within the same schedule:run, so moving Carbon::now() into the closure doesn't change timing.

Regression test (testTimeBetweenChecksTimezoneCallOrder) covers both between() and unlessBetween() in both chaining orders.

The between/unlessBetween schedule filters evaluated the timezone eagerly
when the method was called, so chaining timezone() after between() silently
used the wrong timezone. Defer the time interval computation into the filter
closure so $this->timezone is read at run time, making the chain order
commutative (consistent with other frequency methods).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@taylorotwell

Copy link
Copy Markdown
Member

Agent review:

Should fix — src/Illuminate/Console/Scheduling/ManagesFrequencies.php:60 recomputing Carbon::now() on every filter invocation changes behavior for repeatable sub-minute events because filtersPass() is called throughout the minute, so windows like everySecond()->between('10:00', '10:00') can pass only at the first second instead of remaining consistent for the schedule:run.

@taylorotwell taylorotwell marked this pull request as draft June 16, 2026 15:06
@GrahamCampbell GrahamCampbell changed the title Make between()/unlessBetween() independent of timezone() call order [13.x] Make between()/unlessBetween() independent of timezone() call order Jun 16, 2026
Deferring the time-interval computation into the filter closure also made
Carbon::now() recompute on every filtersPass() call. ScheduleRunCommand
calls filtersPass() in a loop throughout the minute for repeatable
sub-minute events, so the window result could flip mid-run.

Capture the comparison instant once (when the filter is defined, i.e. once
per schedule:run) while still reading $this->timezone lazily inside the
closure, restoring the pre-existing frozen-instant behavior and keeping the
call-order independence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ManicardiFrancesco

Copy link
Copy Markdown
Author

Fixed ^ : we capture the comparison instant once when the filter is defined (i.e. once per schedule:run), while still reading $this->timezone lazily inside the closure. This restores the original frozen-instant behavior for sub-minute events and keeps the call-order independence (timezone() after between()) that this PR introduced.

@ManicardiFrancesco ManicardiFrancesco marked this pull request as ready for review June 17, 2026 08:00
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