Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 33 additions & 1 deletion packages/time/src/calendar/date-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,25 @@ export interface DateCoreOptions {
viewMode: ViewMode
/** Optional locale for date formatting. Uses a BCP 47 language tag. */
locale?: Intl.UnicodeBCP47LocaleIdentifier
/**
* Optional first day of the week (ISO: 1=Mon … 7=Sun). Overrides the
* locale-derived first day across all views when provided.
*/
weekStartsOn?: number
/** Optional time zone specification. */
timeZone?: Temporal.TimeZoneLike
/** Optional calendar system to be used. */
calendar?: Temporal.CalendarLike
/** Optional range of dates to be used. */
range?: DateRange
/**
* When `true`, the month view always spans a fixed 6 week-rows (the maximum
* any month can occupy) instead of the natural 4/5/6, padding with leading
* days of the following month. Keeps the grid height stable across months.
* Only affects the `month` view mode; ignored for week/day/workWeek.
* @default false
*/
fixedWeeks?: boolean
/** Optional date formatter. */
dateFormatter?: Intl.DateTimeFormat
/** Optional time formatter. */
Expand All @@ -70,9 +83,12 @@ export interface DateCoreOptions {

export interface ParsedDateCoreOptions extends Omit<
Required<DateCoreOptions>,
'range' | 'dateFormatter' | 'timeFormatter' | 'dateTimeFormatter'
'range' | 'fixedWeeks' | 'weekStartsOn' | 'dateFormatter' | 'timeFormatter' | 'dateTimeFormatter'
> {
range: ParsedDateRange
fixedWeeks?: boolean
/** ISO 1=Mon … 7=Sun, or undefined to derive from the locale. */
weekStartsOn?: number
}

export abstract class DateCore {
Expand Down Expand Up @@ -153,6 +169,7 @@ export abstract class DateCore {
return getFirstDayOfWeek(
this.store.state.currentPeriod.toString(),
this.options.locale,
this.options.weekStartsOn,
)
}

Expand Down Expand Up @@ -194,6 +211,21 @@ export abstract class DateCore {
7) %
7
end = lastDayOfMonth.add({ days: 6 - lastDayOfMonthWeekDay })
// Pad short months up to a fixed 6 week-rows so the grid never shifts.
// Only extends (never truncates), so multi-month spans (>6 weeks) are
// left untouched.
if (this.options.fixedWeeks) {
const FIXED_WEEKS = 6
// start and end are both week-boundary aligned, so the span is a whole
// number of weeks; round defensively so a fractional value can never
// reach Temporal's integer-only `add`.
const spanWeeks = Math.round(
(start.until(end, { largestUnit: 'day' }).days + 1) / 7,
)
if (spanWeeks < FIXED_WEEKS) {
end = end.add({ weeks: FIXED_WEEKS - spanWeeks })
}
}
break
}
case 'week': {
Expand Down
9 changes: 6 additions & 3 deletions packages/time/src/utils/weekUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@ export function getFirstDayOfMonth(yearMonth: string): Temporal.PlainDate {
}

/**
* Get the first day of the week for a given date string and locale
* Get the first day of the week for a given date string and locale.
*
* `weekStartsOn` (ISO: 1=Mon … 7=Sun) overrides the locale-derived first day
* when provided; otherwise the locale's own convention is used.
*/
export function getFirstDayOfWeek(
dateString: string,
locale: string,
weekStartsOn?: number,
): Temporal.PlainDate {
const date = Temporal.PlainDate.from(dateString)
const weekInfo = getWeekInfo(locale)
const firstDayOfWeek = weekInfo.firstDay
const firstDayOfWeek = weekStartsOn ?? getWeekInfo(locale).firstDay
const dayOfWeek = date.dayOfWeek
const daysToSubtract = (dayOfWeek - firstDayOfWeek + 7) % 7

Expand Down