diff --git a/packages/@internationalized/date/src/CalendarDate.ts b/packages/@internationalized/date/src/CalendarDate.ts index 6eff9f68ee1..c214236ad0a 100644 --- a/packages/@internationalized/date/src/CalendarDate.ts +++ b/packages/@internationalized/date/src/CalendarDate.ts @@ -59,27 +59,27 @@ export class CalendarDate { /** The day number within the month. */ public readonly day: number; - constructor(year: number, month: number, day: number); - constructor(era: string, year: number, month: number, day: number); - constructor(calendar: Calendar, year: number, month: number, day: number); - constructor(calendar: Calendar, era: string, year: number, month: number, day: number); + constructor(year: number, month: number, day: number, constrainDay?: boolean); + constructor(era: string, year: number, month: number, day: number, constrainDay?: boolean); + constructor(calendar: Calendar, year: number, month: number, day: number, constrainDay?: boolean); + constructor(calendar: Calendar, era: string, year: number, month: number, day: number, constrainDay?: boolean); constructor(...args: any[]) { - let [calendar, era, year, month, day] = shiftArgs(args); + let [calendar, era, year, month, day, constrainDay] = shiftArgs(args); this.calendar = calendar; this.era = era; this.year = year; this.month = month; this.day = day; - constrain(this); + constrain(this, constrainDay); } /** Returns a copy of this date. */ - copy(): CalendarDate { + copy(constrainDay?: boolean): CalendarDate { if (this.era) { - return new CalendarDate(this.calendar, this.era, this.year, this.month, this.day); + return new CalendarDate(this.calendar, this.era, this.year, this.month, this.day, constrainDay); } else { - return new CalendarDate(this.calendar, this.year, this.month, this.day); + return new CalendarDate(this.calendar, this.year, this.month, this.day, constrainDay); } } @@ -94,16 +94,16 @@ export class CalendarDate { } /** Returns a new `CalendarDate` with the given fields set to the provided values. Other fields will be constrained accordingly. */ - set(fields: DateFields): CalendarDate { - return set(this, fields); + set(fields: DateFields, constrainDay?: boolean): CalendarDate { + return set(this, fields, constrainDay); } /** * Returns a new `CalendarDate` with the given field adjusted by a specified amount. * When the resulting value reaches the limits of the field, it wraps around. */ - cycle(field: DateField, amount: number, options?: CycleOptions): CalendarDate { - return cycleDate(this, field, amount, options); + cycle(field: DateField, amount: number, options?: CycleOptions, constrainDay?: boolean): CalendarDate { + return cycleDate(this, field, amount, options, constrainDay); } /** Converts the date to a native JavaScript Date object, with the time set to midnight in the given time zone. */ @@ -165,8 +165,8 @@ export class Time { } /** Returns a new `Time` with the given fields set to the provided values. Other fields will be constrained accordingly. */ - set(fields: TimeFields): Time { - return setTime(this, fields); + set(fields: TimeFields, constrainDay?: boolean): Time { + return setTime(this, fields, constrainDay); } /** @@ -216,10 +216,10 @@ export class CalendarDateTime { /** The millisecond in the second. */ public readonly millisecond: number; - constructor(year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number); - constructor(era: string, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number); - constructor(calendar: Calendar, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number); - constructor(calendar: Calendar, era: string, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number); + constructor(year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number, constrainDay?: boolean); + constructor(era: string, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number, constrainDay?: boolean); + constructor(calendar: Calendar, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number, constrainDay?: boolean); + constructor(calendar: Calendar, era: string, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number, constrainDay?: boolean); constructor(...args: any[]) { let [calendar, era, year, month, day] = shiftArgs(args); this.calendar = calendar; @@ -231,16 +231,17 @@ export class CalendarDateTime { this.minute = args.shift() || 0; this.second = args.shift() || 0; this.millisecond = args.shift() || 0; + const constrainDay = args.shift() || 0; - constrain(this); + constrain(this, constrainDay); } /** Returns a copy of this date. */ - copy(): CalendarDateTime { + copy(constrainDay?: boolean): CalendarDateTime { if (this.era) { - return new CalendarDateTime(this.calendar, this.era, this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond); + return new CalendarDateTime(this.calendar, this.era, this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond, constrainDay); } else { - return new CalendarDateTime(this.calendar, this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond); + return new CalendarDateTime(this.calendar, this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond, constrainDay); } } @@ -255,21 +256,21 @@ export class CalendarDateTime { } /** Returns a new `CalendarDateTime` with the given fields set to the provided values. Other fields will be constrained accordingly. */ - set(fields: DateFields & TimeFields): CalendarDateTime { - return set(setTime(this, fields), fields); + set(fields: DateFields & TimeFields, constrainDay?: boolean): CalendarDateTime { + return set(setTime(this, fields), fields, constrainDay); } /** * Returns a new `CalendarDateTime` with the given field adjusted by a specified amount. * When the resulting value reaches the limits of the field, it wraps around. */ - cycle(field: DateField | TimeField, amount: number, options?: CycleTimeOptions): CalendarDateTime { + cycle(field: DateField | TimeField, amount: number, options?: CycleTimeOptions, constrainDay?: boolean): CalendarDateTime { switch (field) { case 'era': case 'year': case 'month': case 'day': - return cycleDate(this, field, amount, options); + return cycleDate(this, field, amount, options, constrainDay); default: return cycleTime(this, field, amount, options); } @@ -328,10 +329,10 @@ export class ZonedDateTime { /** The UTC offset for this time, in milliseconds. */ public readonly offset: number; - constructor(year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number); - constructor(era: string, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number); - constructor(calendar: Calendar, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number); - constructor(calendar: Calendar, era: string, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number); + constructor(year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number, constrainDay?: boolean); + constructor(era: string, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number, constrainDay?: boolean); + constructor(calendar: Calendar, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number, constrainDay?: boolean); + constructor(calendar: Calendar, era: string, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number, constrainDay?: boolean); constructor(...args: any[]) { let [calendar, era, year, month, day] = shiftArgs(args); let timeZone = args.shift(); @@ -347,16 +348,17 @@ export class ZonedDateTime { this.minute = args.shift() || 0; this.second = args.shift() || 0; this.millisecond = args.shift() || 0; + const constrainDay = args.shift() || 0; - constrain(this); + constrain(this, constrainDay); } /** Returns a copy of this date. */ - copy(): ZonedDateTime { + copy(constrainDay?: boolean): ZonedDateTime { if (this.era) { - return new ZonedDateTime(this.calendar, this.era, this.year, this.month, this.day, this.timeZone, this.offset, this.hour, this.minute, this.second, this.millisecond); + return new ZonedDateTime(this.calendar, this.era, this.year, this.month, this.day, this.timeZone, this.offset, this.hour, this.minute, this.second, this.millisecond, constrainDay); } else { - return new ZonedDateTime(this.calendar, this.year, this.month, this.day, this.timeZone, this.offset, this.hour, this.minute, this.second, this.millisecond); + return new ZonedDateTime(this.calendar, this.year, this.month, this.day, this.timeZone, this.offset, this.hour, this.minute, this.second, this.millisecond, constrainDay); } } @@ -371,16 +373,23 @@ export class ZonedDateTime { } /** Returns a new `ZonedDateTime` with the given fields set to the provided values. Other fields will be constrained accordingly. */ - set(fields: DateFields & TimeFields, disambiguation?: Disambiguation): ZonedDateTime { - return setZoned(this, fields, disambiguation); + set(fields: DateFields & TimeFields, value?: Disambiguation | boolean): ZonedDateTime { + let disambiguation, constrainDay = false; + if (value && typeof value === 'string') { + disambiguation = value; + } else if (value && typeof value === 'boolean') { + constrainDay = value; + } + return setZoned(this, fields, disambiguation, constrainDay); } + /** * Returns a new `ZonedDateTime` with the given field adjusted by a specified amount. * When the resulting value reaches the limits of the field, it wraps around. */ - cycle(field: DateField | TimeField, amount: number, options?: CycleTimeOptions): ZonedDateTime { - return cycleZoned(this, field, amount, options); + cycle(field: DateField | TimeField, amount: number, options?: CycleTimeOptions, constrainDay?: boolean): ZonedDateTime { + return cycleZoned(this, field, amount, options, constrainDay); } /** Converts the date to a native JavaScript Date object. */ diff --git a/packages/@internationalized/date/src/calendars/BuddhistCalendar.ts b/packages/@internationalized/date/src/calendars/BuddhistCalendar.ts index f60c8bf1b3d..b7a60283aa6 100644 --- a/packages/@internationalized/date/src/calendars/BuddhistCalendar.ts +++ b/packages/@internationalized/date/src/calendars/BuddhistCalendar.ts @@ -49,7 +49,6 @@ export class BuddhistCalendar extends GregorianCalendar { getDaysInMonth(date: AnyCalendarDate): number { return super.getDaysInMonth(toGregorian(date)); } - balanceDate(): void {} } diff --git a/packages/@internationalized/date/src/calendars/EthiopicCalendar.ts b/packages/@internationalized/date/src/calendars/EthiopicCalendar.ts index c96be3fb827..b520a4e4be1 100644 --- a/packages/@internationalized/date/src/calendars/EthiopicCalendar.ts +++ b/packages/@internationalized/date/src/calendars/EthiopicCalendar.ts @@ -92,6 +92,10 @@ export class EthiopicCalendar implements Calendar { return getDaysInMonth(date.year, date.month); } + getMaxDays(): number { + return 30; + } + getMonthsInYear(): number { return 13; } diff --git a/packages/@internationalized/date/src/calendars/GregorianCalendar.ts b/packages/@internationalized/date/src/calendars/GregorianCalendar.ts index 31106c379fe..1db651fa9df 100644 --- a/packages/@internationalized/date/src/calendars/GregorianCalendar.ts +++ b/packages/@internationalized/date/src/calendars/GregorianCalendar.ts @@ -109,6 +109,10 @@ export class GregorianCalendar implements Calendar { return 12; } + getMaxDays(): number { + return 31; + } + getDaysInYear(date: AnyCalendarDate): number { return isLeapYear(date.year) ? 366 : 365; } diff --git a/packages/@internationalized/date/src/calendars/HebrewCalendar.ts b/packages/@internationalized/date/src/calendars/HebrewCalendar.ts index 52d3f43bc2f..f48b30ec3f6 100644 --- a/packages/@internationalized/date/src/calendars/HebrewCalendar.ts +++ b/packages/@internationalized/date/src/calendars/HebrewCalendar.ts @@ -172,6 +172,10 @@ export class HebrewCalendar implements Calendar { return getDaysInMonth(date.year, date.month); } + getMaxDays(): number { + return 30; + } + getMonthsInYear(date: AnyCalendarDate): number { return isLeapYear(date.year) ? 13 : 12; } diff --git a/packages/@internationalized/date/src/calendars/IslamicCalendar.ts b/packages/@internationalized/date/src/calendars/IslamicCalendar.ts index 7696e852224..0106a887960 100644 --- a/packages/@internationalized/date/src/calendars/IslamicCalendar.ts +++ b/packages/@internationalized/date/src/calendars/IslamicCalendar.ts @@ -69,6 +69,10 @@ export class IslamicCivilCalendar implements Calendar { return length; } + getMaxDays(): number { + return 30; + } + getMonthsInYear(): number { return 12; } diff --git a/packages/@internationalized/date/src/calendars/PersianCalendar.ts b/packages/@internationalized/date/src/calendars/PersianCalendar.ts index 0ff6c86cec5..e1408e4aaa6 100644 --- a/packages/@internationalized/date/src/calendars/PersianCalendar.ts +++ b/packages/@internationalized/date/src/calendars/PersianCalendar.ts @@ -80,6 +80,10 @@ export class PersianCalendar implements Calendar { return isLeapYear ? 30 : 29; } + getMaxDays(): number { + return 31; + } + getEras(): string[] { return ['AP']; } diff --git a/packages/@internationalized/date/src/conversion.ts b/packages/@internationalized/date/src/conversion.ts index 7b1ae398d1b..d60fb529b98 100644 --- a/packages/@internationalized/date/src/conversion.ts +++ b/packages/@internationalized/date/src/conversion.ts @@ -259,7 +259,7 @@ export function toTime(dateTime: CalendarDateTime | ZonedDateTime): Time { } /** Converts a date from one calendar system to another. */ -export function toCalendar(date: T, calendar: Calendar): T { +export function toCalendar(date: T, calendar: Calendar, constrainDay?: boolean): T { if (isEqualCalendar(date.calendar, calendar)) { return date; } @@ -271,7 +271,7 @@ export function toCalendar(date: T, calendar: Calenda copy.year = calendarDate.year; copy.month = calendarDate.month; copy.day = calendarDate.day; - constrain(copy); + constrain(copy, constrainDay); return copy; } diff --git a/packages/@internationalized/date/src/manipulation.ts b/packages/@internationalized/date/src/manipulation.ts index 87863dafaf6..0376c60507a 100644 --- a/packages/@internationalized/date/src/manipulation.ts +++ b/packages/@internationalized/date/src/manipulation.ts @@ -113,18 +113,22 @@ function balanceDay(date: Mutable) { } } -function constrainMonthDay(date: Mutable) { +function constrainMonthDay(date: Mutable, constrainDay: boolean = true) { date.month = Math.max(1, Math.min(date.calendar.getMonthsInYear(date), date.month)); - date.day = Math.max(1, Math.min(date.calendar.getDaysInMonth(date), date.day)); + if (constrainDay) { + date.day = Math.max(1, Math.min(date.calendar.getDaysInMonth(date), date.day)); + } else { + date.day = Math.max(1, Math.min(date.calendar.getMaxDays(), date.day)); + } } -export function constrain(date: Mutable): void { +export function constrain(date: Mutable, constrainDay?: boolean): void { if (date.calendar.constrainDate) { - date.calendar.constrainDate(date); + date.calendar.constrainDate(date); } date.year = Math.max(1, Math.min(date.calendar.getYearsInEra(date), date.year)); - constrainMonthDay(date); + constrainMonthDay(date, constrainDay); } export function invertDuration(duration: DateTimeDuration): DateTimeDuration { @@ -144,10 +148,10 @@ export function subtract(date: CalendarDate | CalendarDateTime, duration: DateTi return add(date, invertDuration(duration)); } -export function set(date: CalendarDateTime, fields: DateFields): CalendarDateTime; -export function set(date: CalendarDate, fields: DateFields): CalendarDate; -export function set(date: CalendarDate | CalendarDateTime, fields: DateFields): Mutable { - let mutableDate: Mutable = date.copy(); +export function set(date: CalendarDateTime, fields: DateFields, constrainDay?: boolean): CalendarDateTime; +export function set(date: CalendarDate, fields: DateFields, constrainDay?: boolean): CalendarDate; +export function set(date: CalendarDate | CalendarDateTime, fields: DateFields, constrainDay?: boolean): Mutable { + let mutableDate: Mutable = date.copy(constrainDay); if (fields.era != null) { mutableDate.era = fields.era; @@ -165,14 +169,14 @@ export function set(date: CalendarDate | CalendarDateTime, fields: DateFields): mutableDate.day = fields.day; } - constrain(mutableDate); + constrain(mutableDate, constrainDay); return mutableDate; } -export function setTime(value: CalendarDateTime, fields: TimeFields): CalendarDateTime; -export function setTime(value: Time, fields: TimeFields): Time; -export function setTime(value: Time | CalendarDateTime, fields: TimeFields): Mutable