Skip to content

[scheduler] Normalize all-day recurring occurrences to whole days#22792

Merged
rita-codes merged 9 commits into
mui:masterfrom
Anexus5919:fix/allday-recurring-occurrence-whole-day
Jun 15, 2026
Merged

[scheduler] Normalize all-day recurring occurrences to whole days#22792
rita-codes merged 9 commits into
mui:masterfrom
Anexus5919:fix/allday-recurring-occurrence-whole-day

Conversation

@Anexus5919

@Anexus5919 Anexus5919 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Closes #22764

Summary

For an all-day event, the base event correctly ignores the time of day, so its displayTimezone is flattened to the start and end of the day. Its recurring occurrences did not do the same. When an all-day event was authored with non-midnight times (for example 09:00 to 18:00), every generated occurrence kept that raw sub-day span instead of covering the whole day. This breaks the "all-day equals whole day" convention and makes the repeats inconsistent with the first instance.

This is an edge case: it only triggers when an all-day event carries non-midnight times. Events created through the picker are already midnight-aligned, so they never hit it.

Root cause

processEvent normalizes only the base event's displayTimezone to startOfDay and endOfDay for all-day events. It leaves dataTimezone with the raw authored times. When getRecurringEventOccurrencesForVisibleDays builds each occurrence in addOccurrence, it projects the occurrence start and end into the display timezone but does not re-apply that all-day flattening, so the raw sub-day span leaks into the occurrence's displayTimezone.

Fix

In addOccurrence, when the event is all-day, wrap the display-timezone start in startOfDay and the end in endOfDay. This mirrors exactly what processEvent already does for the base event, so each occurrence's displayTimezone becomes identical in shape to the base instance (00:00:00.000 to 23:59:59.999 [minutesInDay 0 to 1439]).

dataTimezone is deliberately left untouched, for two reasons:

  1. It keeps parity with the base event, whose dataTimezone is also raw for all-day events.
  2. The recurring edit, paste, and serialization paths read dataTimezone to write values back into the user's model. Normalizing it there (the alternative considered in the issue) would silently overwrite the authored times on those flows. Keeping the change to displayTimezone only avoids that risk, and rendering reads displayTimezone exclusively, so the visible bug is fully fixed.

getOccurrenceEnd is unchanged.

Steps to reproduce

Author an all-day recurring event with non-midnight times and expand it:

EventBuilder.new()
  .span('2025-01-10T09:00:00Z', '2025-01-10T18:00:00Z', { allDay: true })
  .rrule({ freq: 'DAILY', interval: 1 })
  .toProcessed();
// then getRecurringEventOccurrencesForVisibleDays(...)

Before this change, each occurrence reports displayTimezone.start.minutesInDay = 540 and end = 1080 (09:00 to 18:00). After, it reports 0 and 1439 (whole day), matching the base instance. In EventCalendarPremium and EventTimelinePremium, views that derive an event's duration from displayTimezone showed the repeats as 9-hour blocks instead of whole days; now they match the first instance.

Testing

Added two regression tests to getRecurringEventOccurrencesForVisibleDays.test.ts, covering a single-day and a multi-day all-day series authored with sub-day times. Both assert the occurrences span the whole day on each end. They fail on the current code (expected 540 to equal 0) and pass with the fix. Full package suite stays green (445 tests), typecheck, eslint, and prettier all pass.

Changelog

Fix all-day recurring occurrences keeping a raw sub-day duration instead of spanning the whole day in @mui/x-scheduler-internals-premium.

  • I have followed (at least) the PR section of the contributing guide.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@Anexus5919

Copy link
Copy Markdown
Contributor Author

@rita-codes @zannager I'd appreciate a review on this PR. Thanks!

@code-infra-dashboard

code-infra-dashboard Bot commented Jun 11, 2026

Copy link
Copy Markdown

Deploy preview

https://deploy-preview-22792--material-ui-x.netlify.app/

Bundle size

Bundle Parsed size Gzip size
@mui/x-data-grid 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-pro 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-premium 0B(0.00%) 0B(0.00%)
@mui/x-charts 0B(0.00%) 0B(0.00%)
@mui/x-charts-pro 0B(0.00%) 0B(0.00%)
@mui/x-charts-premium 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers-pro 0B(0.00%) 0B(0.00%)
@mui/x-tree-view 0B(0.00%) 0B(0.00%)
@mui/x-tree-view-pro 0B(0.00%) 0B(0.00%)
@mui/x-license 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@zannager zannager added the scope: scheduler Changes related to the scheduler. label Jun 12, 2026
@rita-codes rita-codes added the type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. label Jun 15, 2026

@rita-codes rita-codes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The fix looks correct and the choice to mirror processEvent is the right call, occurrences should carry the same all-day shape as the base event.

One thing before merging: the all-day flattening (allDay ? startOfDay(start) : start / allDay ? endOfDay(end) : end) now lives verbatim in two places, here and in processEvent (processEvent.ts:65-70). Two copies of the same "all-day = whole day" rule can drift apart over time. Could you extract it into a small shared util?
I think that would be nice, thank u 🙌

@Anexus5919

Copy link
Copy Markdown
Contributor Author

@rita-codes Good call, thanks. Extracted the all-day normalization into a shared normalizeAllDayBounds util in x-scheduler-internals and pointed both processEvent and addOccurrence at it, so the rule now has a single home. Pushed in 1a660739f3.

@Anexus5919

Copy link
Copy Markdown
Contributor Author

@rita-codes One question before I wrap up: the new util is currently covered only indirectly through its two callers (the processEvent and recurrence test suites). Would you like me to add a small dedicated unit test for normalizeAllDayBounds itself (all-day false/undefined returns the bounds unchanged, all-day true snaps to start/end of day, including a multi-day span)? Happy to add it if you'd prefer the util tested directly, otherwise I'll leave the existing coverage as is. Thanks again 🙌

@rita-codes

Copy link
Copy Markdown
Member

@rita-codes Good call, thanks. Extracted the all-day normalization into a shared normalizeAllDayBounds util in x-scheduler-internals and pointed both processEvent and addOccurrence at it, so the rule now has a single home. Pushed in 1a660739f3.

Thanks for the quick fix 🙌 The new helper normalizeAllDayBounds is exactly what I had in mind 🚢

Non-blocking: a small unit test for normalizeAllDayBounds in date-utils.test.ts (all-day flattens / non-all-day passthrough) would document the rule directly, but happy to merge as-is 🙌

@rita-codes
rita-codes self-requested a review June 15, 2026 11:53
@Anexus5919

Copy link
Copy Markdown
Contributor Author

@rita-codes Added the dedicated unit tests for normalizeAllDayBounds in date-utils.test.ts, covering the non-all-day passthrough (false and undefined) and the all-day flattening for both single-day and multi-day spans.
Thanks again for the review 🙌

@rita-codes
rita-codes merged commit ba37c0f into mui:master Jun 15, 2026
21 checks passed
mbrookes pushed a commit to mbrookes/mui-x that referenced this pull request Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: scheduler Changes related to the scheduler. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[scheduler] All-day recurring occurrences keep raw sub-day duration (not whole-day)

3 participants