Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f305578
add timezone prop and state
rita-codes Nov 19, 2025
bfa73b1
fix jsdoc
rita-codes Nov 19, 2025
da12d37
Merge branch 'master' into 20389-scheduler-timezone-issue-1-add-timez…
rita-codes Nov 20, 2025
490f29f
fix types
rita-codes Nov 20, 2025
6452639
Merge branch 'master' into 20389-scheduler-timezone-issue-1-add-timez…
rita-codes Nov 20, 2025
d2b854b
Merge branch 'master' into 20389-scheduler-timezone-issue-1-add-timez…
rita-codes Nov 20, 2025
72cb320
fix types
rita-codes Nov 20, 2025
e39068b
Merge remote-tracking branch 'upstream/master' into 20389-scheduler-t…
rita-codes Nov 20, 2025
3a177fc
Merge branch 'master' into 20389-scheduler-timezone-issue-1-add-timez…
rita-codes Nov 20, 2025
a00421f
Merge branch '20389-scheduler-timezone-issue-1-add-timezone-prop-and-…
rita-codes Nov 20, 2025
b3cc6c5
add timezone utils + tests
rita-codes Nov 20, 2025
7391509
Merge branch 'master' into 20390-scheduler-timezone-issue-2-use-rende…
rita-codes Nov 20, 2025
f95dc67
fix
rita-codes Nov 20, 2025
738a9a3
fix
rita-codes Nov 20, 2025
97d4e8b
Merge branch 'master' into 20390-scheduler-timezone-issue-2-use-rende…
rita-codes Nov 20, 2025
cca0540
Merge remote-tracking branch 'upstream/master' into 20390-scheduler-t…
rita-codes Nov 21, 2025
24fed37
Merge branch 'master' into 20390-scheduler-timezone-issue-2-use-rende…
rita-codes Nov 21, 2025
bb62f23
Merge branch 'master' into 20390-scheduler-timezone-issue-2-use-rende…
rita-codes Nov 21, 2025
e08db6e
Merge branch 'master' into 20390-scheduler-timezone-issue-2-use-rende…
rita-codes Nov 25, 2025
c19adf1
fix tests
rita-codes Nov 25, 2025
c20b106
fix tests
rita-codes Nov 25, 2025
e41cddc
fix tests
rita-codes Nov 25, 2025
3af0250
fix
rita-codes Nov 25, 2025
4c27452
Merge branch 'master' into 20390-scheduler-timezone-issue-2-use-rende…
rita-codes Nov 25, 2025
41273d6
Merge branch 'master' into 20390-scheduler-timezone-issue-2-use-rende…
rita-codes Nov 26, 2025
c244c02
fix
rita-codes Nov 26, 2025
096d8c5
fix
rita-codes Nov 26, 2025
e1ac915
fix
rita-codes Nov 26, 2025
4d18081
fix
rita-codes Nov 26, 2025
d2d3431
fix
rita-codes Nov 26, 2025
f0e603c
fix
rita-codes Nov 26, 2025
d0777b1
Merge branch 'master' into 20390-scheduler-timezone-issue-2-use-rende…
rita-codes Nov 26, 2025
7cc5ad0
fix
rita-codes Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { createSelector } from '@base-ui-components/utils/store';
import { createSelector, createSelectorMemoized } from '@base-ui-components/utils/store';
import { SchedulerState as State } from '../utils/SchedulerStore/SchedulerStore.types';

// Warning: Only add selectors here that do not belong to any specific feature.
export const schedulerOtherSelectors = {
visibleDate: createSelector((state: State) => state.visibleDate),
visibleDate: createSelectorMemoized(
(state: State) => state.adapter,
(state: State) => state.visibleDate,
(state: State) => state.timezone,
(adapter, visibleDate, timezone) => adapter.setTimezone(visibleDate, timezone),
),
isScopeDialogOpen: createSelector(
(state: State) => state.pendingUpdateRecurringEventParameters != null,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from './SchedulerStore.utils';
import { TimeoutManager } from '../TimeoutManager';
import { DEFAULT_EVENT_COLOR } from '../../constants';
import { getNowInRenderTimezone, getStartOfTodayInRenderTimezone } from '../timezone-utils';

const ONE_MINUTE_IN_MS = 60 * 1000;

Expand Down Expand Up @@ -63,21 +64,23 @@ export class SchedulerStore<
instanceName: string,
mapper: SchedulerParametersToStateMapper<State, Parameters>,
) {
const timezone = parameters.timezone ?? 'default';

const schedulerInitialState: SchedulerState<TEvent> = {
...SchedulerStore.deriveStateFromParameters(parameters, adapter),
...buildEventsState(parameters, adapter),
...buildResourcesState(parameters),
preferences: DEFAULT_SCHEDULER_PREFERENCES,
adapter,
occurrencePlaceholder: null,
nowUpdatedEveryMinute: adapter.now('default'),
nowUpdatedEveryMinute: getNowInRenderTimezone(adapter, timezone),
pendingUpdateRecurringEventParameters: null,
timezone: parameters.timezone ?? 'default',
timezone,
visibleResources: new Map(),
visibleDate:
parameters.visibleDate ??
parameters.defaultVisibleDate ??
adapter.startOfDay(adapter.now('default')),
adapter.startOfDay(adapter.now(timezone)),
};

const initialState = mapper.getInitialState(schedulerInitialState, parameters, adapter);
Expand All @@ -92,9 +95,9 @@ export class SchedulerStore<
ONE_MINUTE_IN_MS - (currentDate.getSeconds() * 1000 + currentDate.getMilliseconds());

this.timeoutManager.startTimeout('set-now', timeUntilNextMinuteMs, () => {
this.set('nowUpdatedEveryMinute', adapter.now('default'));
this.set('nowUpdatedEveryMinute', getNowInRenderTimezone(adapter, timezone));
this.timeoutManager.startInterval('set-now', ONE_MINUTE_IN_MS, () => {
this.set('nowUpdatedEveryMinute', adapter.now('default'));
this.set('nowUpdatedEveryMinute', getNowInRenderTimezone(adapter, timezone));
});
});

Expand Down Expand Up @@ -283,7 +286,7 @@ export class SchedulerStore<
*/
public goToToday = (event: React.UIEvent) => {
const { adapter } = this.state;
this.setVisibleDate(adapter.startOfDay(adapter.now('default')), event);
this.setVisibleDate(getStartOfTodayInRenderTimezone(adapter, this.state.timezone), event);
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,8 @@ export interface SchedulerState<TEvent extends object = any> {
* The timezone used by the scheduler.
* Typically an IANA timezone name (e.g. "America/New_York", "Europe/Paris")
* or "default" to use the adapter's default timezone.
* @default "default"
*/
timezone?: TemporalTimezone;
timezone: TemporalTimezone;
}

export interface SchedulerParameters<TEvent extends object, TResource extends object> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ storeClasses.forEach((storeClass) => {
expect(schedulerEventSelectors.processedEvent(store.state, '2')!.title).to.equal('Event 2');
expect(schedulerEventSelectors.modelList(store.state)).to.equal(events);
});

it('should set visibleDate to today in the render timezone when defaultVisibleDate is not provided', () => {
const timezone = 'Pacific/Kiritimati';
const store = new storeClass.Value({ ...DEFAULT_PARAMS, timezone }, adapter);

const expectedToday = adapter.startOfDay(adapter.now(timezone));

expect(store.state.visibleDate).toEqualDateTime(expectedToday);
expect(adapter.getTimezone(store.state.visibleDate)).to.equal(timezone);
});
});

describe('updater', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,32 @@ storeClasses.forEach((storeClass) => {
expect(store.state.visibleDate).toEqualDateTime(todayStart);
expect(onVisibleDateChange.called).to.equal(false);
});

it('should use the provided timezone when going to today (uncontrolled)', () => {
const onVisibleDateChange = spy();
const timezone = 'Pacific/Kiritimati';

const yesterday = adapter.addDays(adapter.startOfDay(adapter.now('default')), -1);

const store = new storeClass.Value(
{
...DEFAULT_PARAMS,
defaultVisibleDate: yesterday,
onVisibleDateChange,
timezone,
},
adapter,
);

store.goToToday({} as any);

const expected = adapter.startOfDay(adapter.now(timezone));

expect(store.state.visibleDate).toEqualDateTime(expected);
expect(store.state.timezone).to.equal(timezone);
expect(onVisibleDateChange.calledOnce).to.equal(true);
expect(onVisibleDateChange.lastCall.firstArg).toEqualDateTime(expected);
});
});
});
});
10 changes: 10 additions & 0 deletions packages/x-scheduler-headless/src/utils/timezone-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { TemporalTimezone } from '../base-ui-copy/types';
import { Adapter } from '../use-adapter';

export function getNowInRenderTimezone(adapter: Adapter, timezone: TemporalTimezone) {
return adapter.now(timezone);
}

export function getStartOfTodayInRenderTimezone(adapter: Adapter, timezone: TemporalTimezone) {
return adapter.startOfDay(getNowInRenderTimezone(adapter, timezone));
}
Loading