From 11258c8c5da3153411fc2f17a3be5811df9eae18 Mon Sep 17 00:00:00 2001 From: Danni Date: Mon, 5 Jan 2026 11:56:50 -0800 Subject: [PATCH 1/3] chore: add better labels in accordion and dropzone stories for sev1 fixes (#9434) * Add better labels to accordion and dropzone stories * Add better labels to accordion stories * Add better labels to dropzone stories * Add better labels to dropzone stories * Add better labels to dropzone stories --- .../@react-spectrum/s2/chromatic/Accordion.stories.tsx | 4 ++-- .../@react-spectrum/s2/chromatic/DropZone.stories.tsx | 8 ++++---- packages/@react-spectrum/s2/stories/Accordion.stories.tsx | 4 ++-- packages/@react-spectrum/s2/stories/DropZone.stories.tsx | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/@react-spectrum/s2/chromatic/Accordion.stories.tsx b/packages/@react-spectrum/s2/chromatic/Accordion.stories.tsx index 34feeb88b57..cc3f85a6447 100644 --- a/packages/@react-spectrum/s2/chromatic/Accordion.stories.tsx +++ b/packages/@react-spectrum/s2/chromatic/Accordion.stories.tsx @@ -138,7 +138,7 @@ export const WithActionButton: Story = { Files - + Files content @@ -149,7 +149,7 @@ export const WithActionButton: Story = { People - + diff --git a/packages/@react-spectrum/s2/chromatic/DropZone.stories.tsx b/packages/@react-spectrum/s2/chromatic/DropZone.stories.tsx index 14a13f73f36..d9df5f5c865 100644 --- a/packages/@react-spectrum/s2/chromatic/DropZone.stories.tsx +++ b/packages/@react-spectrum/s2/chromatic/DropZone.stories.tsx @@ -43,7 +43,7 @@ const ExampleRender = (args: DropZoneProps): ReactElement => { - Drag and drop your file + Drag or paste your file Or, select a file from your computer @@ -72,7 +72,7 @@ const ExampleWithFileTriggerRender = (args: DropZoneProps): ReactElement => { - Drag and drop your file + Drag or paste your file Or, select a file from your computer @@ -108,7 +108,7 @@ const LongBannerRender = (args: DropZoneProps): ReactElement => { - Drag and drop your file + Drag or paste your file Or, select a file from your computer @@ -172,7 +172,7 @@ const GradientExample = (args: DropZoneProps): ReactElement => { - Drag and drop your file + Drag or paste your file Or, select a file from your computer diff --git a/packages/@react-spectrum/s2/stories/Accordion.stories.tsx b/packages/@react-spectrum/s2/stories/Accordion.stories.tsx index 2d38fedbec3..e39dbe0ffd0 100644 --- a/packages/@react-spectrum/s2/stories/Accordion.stories.tsx +++ b/packages/@react-spectrum/s2/stories/Accordion.stories.tsx @@ -217,7 +217,7 @@ export const WithActionButton: Story = { Files - + Files content @@ -228,7 +228,7 @@ export const WithActionButton: Story = { People - + diff --git a/packages/@react-spectrum/s2/stories/DropZone.stories.tsx b/packages/@react-spectrum/s2/stories/DropZone.stories.tsx index 5c22ff85c27..cd7fa767637 100644 --- a/packages/@react-spectrum/s2/stories/DropZone.stories.tsx +++ b/packages/@react-spectrum/s2/stories/DropZone.stories.tsx @@ -52,7 +52,7 @@ const ExampleRender = (args: DropZoneProps): ReactElement => { - Drag and drop your file + Drag or paste your file Or, select a file from your computer @@ -81,7 +81,7 @@ const ExampleWithFileTriggerRender = (args: DropZoneProps): ReactElement => { - Drag and drop your file + Drag or paste your file Or, select a file from your computer @@ -117,7 +117,7 @@ const LongBannerRender = (args: DropZoneProps): ReactElement => { - Drag and drop your file + Drag or paste your file Or, select a file from your computer @@ -146,7 +146,7 @@ const GradientRender = (args: DropZoneProps): ReactElement => { - Drag and drop your file + Drag or paste your file Or, select a file from your computer From b71dd5f47f6159478ad2a4c7520ef28acdb01b5a Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Mon, 5 Jan 2026 17:51:05 -0600 Subject: [PATCH 2/3] fix content-type for *.md and *.txt files in docs deploy (#9438) --- .circleci/config.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ac4c872bfbe..1f65571d3b7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -77,11 +77,25 @@ commands: --cache-control "public,max-age=31536000,immutable" \ --exclude "*.html" --exclude "*.rsc" --exclude "*.txt" --exclude "*.json" --exclude "*.md" --exclude "*.xml" - run: - name: Copy HTML and text files to S3 + name: Copy HTML and data files to S3 command: | aws s3 cp /tmp/dist/s2-docs/<< parameters.dir >> << parameters.bucket >> --recursive \ --cache-control "public,max-age=300,stale-while-revalidate=300" \ - --exclude "*" --include "*.html" --include "*.txt" --include "*.json" --include "*.md" --include "*.xml" + --exclude "*" --include "*.html" --include "*.json" --include "*.xml" + - run: + name: Copy Markdown files to S3 + command: | + aws s3 cp /tmp/dist/s2-docs/<< parameters.dir >> << parameters.bucket >> --recursive \ + --cache-control "public,max-age=300,stale-while-revalidate=300" \ + --content-type "text/markdown;charset=utf-8" \ + --exclude "*" --include "*.md" + - run: + name: Copy text files to S3 + command: | + aws s3 cp /tmp/dist/s2-docs/<< parameters.dir >> << parameters.bucket >> --recursive \ + --cache-control "public,max-age=300,stale-while-revalidate=300" \ + --content-type "text/plain;charset=utf-8" \ + --exclude "*" --include "*.txt" - run: name: Copy RSC files to S3 command: | From 7fa820644f2aa22cd3b50cdf9805fc934de0310c Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 5 Jan 2026 19:14:11 -0500 Subject: [PATCH 3/3] fix: Custom454 calendar system example broke in December (#9439) --- .../dev/s2-docs/pages/react-aria/Calendar.mdx | 61 +++++++++++++------ .../pages/react-aria/RangeCalendar.mdx | 61 +++++++++++++------ .../internationalized/date/Calendar.mdx | 59 +++++++++++++----- packages/dev/s2-docs/pages/s2/Calendar.mdx | 61 +++++++++++++------ .../dev/s2-docs/pages/s2/RangeCalendar.mdx | 61 +++++++++++++------ 5 files changed, 207 insertions(+), 96 deletions(-) diff --git a/packages/dev/s2-docs/pages/react-aria/Calendar.mdx b/packages/dev/s2-docs/pages/react-aria/Calendar.mdx index e0fd2497e3f..3941ebaa1d4 100644 --- a/packages/dev/s2-docs/pages/react-aria/Calendar.mdx +++ b/packages/dev/s2-docs/pages/react-aria/Calendar.mdx @@ -83,8 +83,8 @@ import {Calendar} from 'vanilla-starter/Calendar'; ```tsx render "use client"; -import type {AnyCalendarDate} from '@internationalized/date'; -import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date'; +import type {AnyCalendarDate, Calendar as ICalendar} from '@internationalized/date'; +import {CalendarDate, startOfWeek, GregorianCalendar} from '@internationalized/date'; import {Calendar} from 'vanilla-starter/Calendar'; export default function Example() { @@ -100,45 +100,66 @@ export default function Example() { // See @internationalized/date docs linked above. ///- begin collapse -/// class Custom454 extends GregorianCalendar { - weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4]; - getDaysInMonth(date) { - return this.weekPattern[date.month - 1] * 7; + // The anchor date, in Gregorian calendar. + // The anchor date is a date that occurs in the first week of the first month of every fiscal year. + anchorDate = new CalendarDate(2001, 2, 4); + + private getYear(year: number): [CalendarDate, number[]] { + let anchor = this.anchorDate.set({year}); + let startOfYear = startOfWeek(anchor, 'en', 'sun'); + let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1})); + let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4]; + return [startOfYear, weekPattern]; + } + + getDaysInMonth(date: AnyCalendarDate): number { + let [, weekPattern] = this.getYear(date.year); + return weekPattern[date.month - 1] * 7; } fromJulianDay(jd: number): CalendarDate { let gregorian = super.fromJulianDay(jd); + let year = gregorian.year; + + let [monthStart, weekPattern] = this.getYear(year); + if (gregorian.compare(monthStart) < 0) { + year--; + [monthStart, weekPattern] = this.getYear(year); + } - let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en'); - for (let months = 0; months < this.weekPattern.length; months++) { - let weeksInMonth = this.weekPattern[months]; - let monthEnd = monthStart.add({weeks: weeksInMonth}); - if (monthEnd.compare(gregorian) > 0) { + for (let month = 1; month <= 12; month++) { + let weeks = weekPattern[month - 1]; + let nextMonth = monthStart.add({weeks}); + if (nextMonth.compare(gregorian) > 0) { let days = gregorian.compare(monthStart); - return new CalendarDate(this, monthStart.year, months + 1, days + 1); + return new CalendarDate(this, year, month, days + 1); } - monthStart = monthEnd; + monthStart = nextMonth; } - throw Error('Date is not in any month somehow!'); + throw new Error('date not found'); } toJulianDay(date: AnyCalendarDate): number { - let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en'); + let [monthStart, weekPattern] = this.getYear(date.year); for (let month = 1; month < date.month; month++) { - monthStart = monthStart.add({weeks: this.weekPattern[month - 1]}); + monthStart = monthStart.add({weeks: weekPattern[month - 1]}); } let gregorian = monthStart.add({days: date.day - 1}); return super.toJulianDay(gregorian); } - getFormattableMonth(date) { - let gregorian = toCalendar(date, new GregorianCalendar()); - return gregorian.set({month: date.month, day: 1}); + getFormattableMonth(date: AnyCalendarDate): CalendarDate { + let anchorMonth = this.anchorDate.month - 1; + let dateMonth = date.month - 1; + let month = ((anchorMonth + dateMonth) % 12) + 1; + let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year; + return new CalendarDate(year, month, 1); } - isEqual(other) { - return other instanceof Custom454; + isEqual(other: ICalendar): boolean { + return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0; } } ///- end collapse -/// diff --git a/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx b/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx index bead2451c0d..a5cc8075980 100644 --- a/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx +++ b/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx @@ -93,8 +93,8 @@ import {RangeCalendar} from 'vanilla-starter/RangeCalendar'; ```tsx render "use client"; -import type {AnyCalendarDate} from '@internationalized/date'; -import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date'; +import type {AnyCalendarDate, Calendar} from '@internationalized/date'; +import {CalendarDate, startOfWeek, GregorianCalendar} from '@internationalized/date'; import {RangeCalendar} from 'vanilla-starter/RangeCalendar'; export default function Example() { @@ -110,45 +110,66 @@ export default function Example() { // See @internationalized/date docs linked above. ///- begin collapse -/// class Custom454 extends GregorianCalendar { - weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4]; - getDaysInMonth(date) { - return this.weekPattern[date.month - 1] * 7; + // The anchor date, in Gregorian calendar. + // The anchor date is a date that occurs in the first week of the first month of every fiscal year. + anchorDate = new CalendarDate(2001, 2, 4); + + private getYear(year: number): [CalendarDate, number[]] { + let anchor = this.anchorDate.set({year}); + let startOfYear = startOfWeek(anchor, 'en', 'sun'); + let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1})); + let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4]; + return [startOfYear, weekPattern]; + } + + getDaysInMonth(date: AnyCalendarDate): number { + let [, weekPattern] = this.getYear(date.year); + return weekPattern[date.month - 1] * 7; } fromJulianDay(jd: number): CalendarDate { let gregorian = super.fromJulianDay(jd); + let year = gregorian.year; + + let [monthStart, weekPattern] = this.getYear(year); + if (gregorian.compare(monthStart) < 0) { + year--; + [monthStart, weekPattern] = this.getYear(year); + } - let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en'); - for (let months = 0; months < this.weekPattern.length; months++) { - let weeksInMonth = this.weekPattern[months]; - let monthEnd = monthStart.add({weeks: weeksInMonth}); - if (monthEnd.compare(gregorian) > 0) { + for (let month = 1; month <= 12; month++) { + let weeks = weekPattern[month - 1]; + let nextMonth = monthStart.add({weeks}); + if (nextMonth.compare(gregorian) > 0) { let days = gregorian.compare(monthStart); - return new CalendarDate(this, monthStart.year, months + 1, days + 1); + return new CalendarDate(this, year, month, days + 1); } - monthStart = monthEnd; + monthStart = nextMonth; } - throw Error('Date is not in any month somehow!'); + throw new Error('date not found'); } toJulianDay(date: AnyCalendarDate): number { - let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en'); + let [monthStart, weekPattern] = this.getYear(date.year); for (let month = 1; month < date.month; month++) { - monthStart = monthStart.add({weeks: this.weekPattern[month - 1]}); + monthStart = monthStart.add({weeks: weekPattern[month - 1]}); } let gregorian = monthStart.add({days: date.day - 1}); return super.toJulianDay(gregorian); } - getFormattableMonth(date) { - let gregorian = toCalendar(date, new GregorianCalendar()); - return gregorian.set({month: date.month, day: 1}); + getFormattableMonth(date: AnyCalendarDate): CalendarDate { + let anchorMonth = this.anchorDate.month - 1; + let dateMonth = date.month - 1; + let month = ((anchorMonth + dateMonth) % 12) + 1; + let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year; + return new CalendarDate(year, month, 1); } - isEqual(other) { - return other instanceof Custom454; + isEqual(other: Calendar): boolean { + return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0; } } ///- end collapse -/// diff --git a/packages/dev/s2-docs/pages/react-aria/internationalized/date/Calendar.mdx b/packages/dev/s2-docs/pages/react-aria/internationalized/date/Calendar.mdx index b057698d818..fae8a638eec 100644 --- a/packages/dev/s2-docs/pages/react-aria/internationalized/date/Calendar.mdx +++ b/packages/dev/s2-docs/pages/react-aria/internationalized/date/Calendar.mdx @@ -125,36 +125,55 @@ The following code is an example of how you might implement a custom 4-5-4 calen import type {AnyCalendarDate, Calendar} from '@internationalized/date'; import {CalendarDate, GregorianCalendar, startOfWeek} from '@internationalized/date'; -const weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4]; - +// This calendar gives each month a 4-5-4 week pattern, with February as the first month of the year. +// This means that in this calendar, 2024-01-01 translates to 2024-02-04 in the Gregorian calendar. +// Months begin on day 1, and go through 7*weeksInMonth days, ending on either the 28th or 35th day of the month. class Custom454 extends GregorianCalendar { - // Months always have either 4 or 5 full weeks. - getDaysInMonth(date) { + // The anchor date, in Gregorian calendar. + // The anchor date is a date that occurs in the first week of the first month of every fiscal year. + anchorDate = new CalendarDate(2001, 2, 4); + + private getYear(year: number): [CalendarDate, number[]] { + let anchor = this.anchorDate.set({year}); + let startOfYear = startOfWeek(anchor, 'en', 'sun'); + let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1})); + let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4]; + return [startOfYear, weekPattern]; + } + + getDaysInMonth(date: AnyCalendarDate): number { + // Months always have either 4 or 5 full weeks. + let [, weekPattern] = this.getYear(date.year); return weekPattern[date.month - 1] * 7; } - // Enable conversion between calendar systems. fromJulianDay(jd: number): CalendarDate { let gregorian = super.fromJulianDay(jd); + let year = gregorian.year; + + let [monthStart, weekPattern] = this.getYear(year); + if (gregorian.compare(monthStart) < 0) { + year--; + [monthStart, weekPattern] = this.getYear(year); + } // Start from the beginning of the first week of the gregorian year // and add weeks until we find the month. - let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en'); - for (let months = 0; months < weekPattern.length; months++) { - let weeksInMonth = weekPattern[months]; - let monthEnd = monthStart.add({weeks: weeksInMonth}); - if (monthEnd.compare(gregorian) > 0) { + for (let month = 1; month <= 12; month++) { + let weeks = weekPattern[month - 1]; + let nextMonth = monthStart.add({weeks}); + if (nextMonth.compare(gregorian) > 0) { let days = gregorian.compare(monthStart); - return new CalendarDate(this, monthStart.year, months + 1, days + 1); + return new CalendarDate(this, year, month, days + 1); } - monthStart = monthEnd; + monthStart = nextMonth; } - throw Error('Date is not in any month somehow!'); + throw new Error('date not found'); } toJulianDay(date: AnyCalendarDate): number { - let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en'); + let [monthStart, weekPattern] = this.getYear(date.year); for (let month = 1; month < date.month; month++) { monthStart = monthStart.add({weeks: weekPattern[month - 1]}); } @@ -163,8 +182,16 @@ class Custom454 extends GregorianCalendar { return super.toJulianDay(gregorian); } - isEqual(other: Calendar) { - return other instanceof Custom454; + getFormattableMonth(date: AnyCalendarDate): CalendarDate { + let anchorMonth = this.anchorDate.month - 1; + let dateMonth = date.month - 1; + let month = ((anchorMonth + dateMonth) % 12) + 1; + let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year; + return new CalendarDate(year, month, 1); + } + + isEqual(other: Calendar): boolean { + return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0; } } ``` diff --git a/packages/dev/s2-docs/pages/s2/Calendar.mdx b/packages/dev/s2-docs/pages/s2/Calendar.mdx index 00c8e6c0796..06ff974e152 100644 --- a/packages/dev/s2-docs/pages/s2/Calendar.mdx +++ b/packages/dev/s2-docs/pages/s2/Calendar.mdx @@ -74,8 +74,8 @@ import {parseDate} from '@internationalized/date'; ```tsx render "use client"; -import type {AnyCalendarDate} from '@internationalized/date'; -import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date'; +import type {AnyCalendarDate, Calendar as ICalendar} from '@internationalized/date'; +import {CalendarDate, startOfWeek, GregorianCalendar} from '@internationalized/date'; import {Calendar} from '@react-spectrum/s2'; export default function Example() { @@ -91,45 +91,66 @@ export default function Example() { // See @internationalized/date docs linked above. ///- begin collapse -/// class Custom454 extends GregorianCalendar { - weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4]; - getDaysInMonth(date) { - return this.weekPattern[date.month - 1] * 7; + // The anchor date, in Gregorian calendar. + // The anchor date is a date that occurs in the first week of the first month of every fiscal year. + anchorDate = new CalendarDate(2001, 2, 4); + + private getYear(year: number): [CalendarDate, number[]] { + let anchor = this.anchorDate.set({year}); + let startOfYear = startOfWeek(anchor, 'en', 'sun'); + let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1})); + let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4]; + return [startOfYear, weekPattern]; + } + + getDaysInMonth(date: AnyCalendarDate): number { + let [, weekPattern] = this.getYear(date.year); + return weekPattern[date.month - 1] * 7; } fromJulianDay(jd: number): CalendarDate { let gregorian = super.fromJulianDay(jd); + let year = gregorian.year; + + let [monthStart, weekPattern] = this.getYear(year); + if (gregorian.compare(monthStart) < 0) { + year--; + [monthStart, weekPattern] = this.getYear(year); + } - let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en'); - for (let months = 0; months < this.weekPattern.length; months++) { - let weeksInMonth = this.weekPattern[months]; - let monthEnd = monthStart.add({weeks: weeksInMonth}); - if (monthEnd.compare(gregorian) > 0) { + for (let month = 1; month <= 12; month++) { + let weeks = weekPattern[month - 1]; + let nextMonth = monthStart.add({weeks}); + if (nextMonth.compare(gregorian) > 0) { let days = gregorian.compare(monthStart); - return new CalendarDate(this, monthStart.year, months + 1, days + 1); + return new CalendarDate(this, year, month, days + 1); } - monthStart = monthEnd; + monthStart = nextMonth; } - throw Error('Date is not in any month somehow!'); + throw new Error('date not found'); } toJulianDay(date: AnyCalendarDate): number { - let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en'); + let [monthStart, weekPattern] = this.getYear(date.year); for (let month = 1; month < date.month; month++) { - monthStart = monthStart.add({weeks: this.weekPattern[month - 1]}); + monthStart = monthStart.add({weeks: weekPattern[month - 1]}); } let gregorian = monthStart.add({days: date.day - 1}); return super.toJulianDay(gregorian); } - getFormattableMonth(date) { - let gregorian = toCalendar(date, new GregorianCalendar()); - return gregorian.set({month: date.month, day: 1}); + getFormattableMonth(date: AnyCalendarDate): CalendarDate { + let anchorMonth = this.anchorDate.month - 1; + let dateMonth = date.month - 1; + let month = ((anchorMonth + dateMonth) % 12) + 1; + let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year; + return new CalendarDate(year, month, 1); } - isEqual(other) { - return other instanceof Custom454; + isEqual(other: ICalendar): boolean { + return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0; } } ///- end collapse -/// diff --git a/packages/dev/s2-docs/pages/s2/RangeCalendar.mdx b/packages/dev/s2-docs/pages/s2/RangeCalendar.mdx index 8e10cecc38e..e3247d9fe6b 100644 --- a/packages/dev/s2-docs/pages/s2/RangeCalendar.mdx +++ b/packages/dev/s2-docs/pages/s2/RangeCalendar.mdx @@ -86,8 +86,8 @@ import {parseDate} from '@internationalized/date'; ```tsx render "use client"; -import type {AnyCalendarDate} from '@internationalized/date'; -import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date'; +import type {AnyCalendarDate, Calendar} from '@internationalized/date'; +import {CalendarDate, startOfWeek, GregorianCalendar} from '@internationalized/date'; import {RangeCalendar} from '@react-spectrum/s2'; export default function Example() { @@ -103,45 +103,66 @@ export default function Example() { // See @internationalized/date docs linked above. ///- begin collapse -/// class Custom454 extends GregorianCalendar { - weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4]; - getDaysInMonth(date) { - return this.weekPattern[date.month - 1] * 7; + // The anchor date, in Gregorian calendar. + // The anchor date is a date that occurs in the first week of the first month of every fiscal year. + anchorDate = new CalendarDate(2001, 2, 4); + + private getYear(year: number): [CalendarDate, number[]] { + let anchor = this.anchorDate.set({year}); + let startOfYear = startOfWeek(anchor, 'en', 'sun'); + let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1})); + let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4]; + return [startOfYear, weekPattern]; + } + + getDaysInMonth(date: AnyCalendarDate): number { + let [, weekPattern] = this.getYear(date.year); + return weekPattern[date.month - 1] * 7; } fromJulianDay(jd: number): CalendarDate { let gregorian = super.fromJulianDay(jd); + let year = gregorian.year; + + let [monthStart, weekPattern] = this.getYear(year); + if (gregorian.compare(monthStart) < 0) { + year--; + [monthStart, weekPattern] = this.getYear(year); + } - let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en'); - for (let months = 0; months < this.weekPattern.length; months++) { - let weeksInMonth = this.weekPattern[months]; - let monthEnd = monthStart.add({weeks: weeksInMonth}); - if (monthEnd.compare(gregorian) > 0) { + for (let month = 1; month <= 12; month++) { + let weeks = weekPattern[month - 1]; + let nextMonth = monthStart.add({weeks}); + if (nextMonth.compare(gregorian) > 0) { let days = gregorian.compare(monthStart); - return new CalendarDate(this, monthStart.year, months + 1, days + 1); + return new CalendarDate(this, year, month, days + 1); } - monthStart = monthEnd; + monthStart = nextMonth; } - throw Error('Date is not in any month somehow!'); + throw new Error('date not found'); } toJulianDay(date: AnyCalendarDate): number { - let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en'); + let [monthStart, weekPattern] = this.getYear(date.year); for (let month = 1; month < date.month; month++) { - monthStart = monthStart.add({weeks: this.weekPattern[month - 1]}); + monthStart = monthStart.add({weeks: weekPattern[month - 1]}); } let gregorian = monthStart.add({days: date.day - 1}); return super.toJulianDay(gregorian); } - getFormattableMonth(date) { - let gregorian = toCalendar(date, new GregorianCalendar()); - return gregorian.set({month: date.month, day: 1}); + getFormattableMonth(date: AnyCalendarDate): CalendarDate { + let anchorMonth = this.anchorDate.month - 1; + let dateMonth = date.month - 1; + let month = ((anchorMonth + dateMonth) % 12) + 1; + let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year; + return new CalendarDate(year, month, 1); } - isEqual(other) { - return other instanceof Custom454; + isEqual(other: Calendar): boolean { + return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0; } } ///- end collapse -///