Skip to content

Commit 7fa8206

Browse files
authored
fix: Custom454 calendar system example broke in December (#9439)
1 parent b71dd5f commit 7fa8206

File tree

5 files changed

+207
-96
lines changed

5 files changed

+207
-96
lines changed

packages/dev/s2-docs/pages/react-aria/Calendar.mdx

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ import {Calendar} from 'vanilla-starter/Calendar';
8383

8484
```tsx render
8585
"use client";
86-
import type {AnyCalendarDate} from '@internationalized/date';
87-
import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date';
86+
import type {AnyCalendarDate, Calendar as ICalendar} from '@internationalized/date';
87+
import {CalendarDate, startOfWeek, GregorianCalendar} from '@internationalized/date';
8888
import {Calendar} from 'vanilla-starter/Calendar';
8989

9090
export default function Example() {
@@ -100,45 +100,66 @@ export default function Example() {
100100
// See @internationalized/date docs linked above.
101101
///- begin collapse -///
102102
class Custom454 extends GregorianCalendar {
103-
weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4];
104-
getDaysInMonth(date) {
105-
return this.weekPattern[date.month - 1] * 7;
103+
// The anchor date, in Gregorian calendar.
104+
// The anchor date is a date that occurs in the first week of the first month of every fiscal year.
105+
anchorDate = new CalendarDate(2001, 2, 4);
106+
107+
private getYear(year: number): [CalendarDate, number[]] {
108+
let anchor = this.anchorDate.set({year});
109+
let startOfYear = startOfWeek(anchor, 'en', 'sun');
110+
let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1}));
111+
let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4];
112+
return [startOfYear, weekPattern];
113+
}
114+
115+
getDaysInMonth(date: AnyCalendarDate): number {
116+
let [, weekPattern] = this.getYear(date.year);
117+
return weekPattern[date.month - 1] * 7;
106118
}
107119

108120
fromJulianDay(jd: number): CalendarDate {
109121
let gregorian = super.fromJulianDay(jd);
122+
let year = gregorian.year;
123+
124+
let [monthStart, weekPattern] = this.getYear(year);
125+
if (gregorian.compare(monthStart) < 0) {
126+
year--;
127+
[monthStart, weekPattern] = this.getYear(year);
128+
}
110129

111-
let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en');
112-
for (let months = 0; months < this.weekPattern.length; months++) {
113-
let weeksInMonth = this.weekPattern[months];
114-
let monthEnd = monthStart.add({weeks: weeksInMonth});
115-
if (monthEnd.compare(gregorian) > 0) {
130+
for (let month = 1; month <= 12; month++) {
131+
let weeks = weekPattern[month - 1];
132+
let nextMonth = monthStart.add({weeks});
133+
if (nextMonth.compare(gregorian) > 0) {
116134
let days = gregorian.compare(monthStart);
117-
return new CalendarDate(this, monthStart.year, months + 1, days + 1);
135+
return new CalendarDate(this, year, month, days + 1);
118136
}
119-
monthStart = monthEnd;
137+
monthStart = nextMonth;
120138
}
121139

122-
throw Error('Date is not in any month somehow!');
140+
throw new Error('date not found');
123141
}
124142

125143
toJulianDay(date: AnyCalendarDate): number {
126-
let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en');
144+
let [monthStart, weekPattern] = this.getYear(date.year);
127145
for (let month = 1; month < date.month; month++) {
128-
monthStart = monthStart.add({weeks: this.weekPattern[month - 1]});
146+
monthStart = monthStart.add({weeks: weekPattern[month - 1]});
129147
}
130148

131149
let gregorian = monthStart.add({days: date.day - 1});
132150
return super.toJulianDay(gregorian);
133151
}
134152

135-
getFormattableMonth(date) {
136-
let gregorian = toCalendar(date, new GregorianCalendar());
137-
return gregorian.set({month: date.month, day: 1});
153+
getFormattableMonth(date: AnyCalendarDate): CalendarDate {
154+
let anchorMonth = this.anchorDate.month - 1;
155+
let dateMonth = date.month - 1;
156+
let month = ((anchorMonth + dateMonth) % 12) + 1;
157+
let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year;
158+
return new CalendarDate(year, month, 1);
138159
}
139160

140-
isEqual(other) {
141-
return other instanceof Custom454;
161+
isEqual(other: ICalendar): boolean {
162+
return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0;
142163
}
143164
}
144165
///- end collapse -///

packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ import {RangeCalendar} from 'vanilla-starter/RangeCalendar';
9393

9494
```tsx render
9595
"use client";
96-
import type {AnyCalendarDate} from '@internationalized/date';
97-
import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date';
96+
import type {AnyCalendarDate, Calendar} from '@internationalized/date';
97+
import {CalendarDate, startOfWeek, GregorianCalendar} from '@internationalized/date';
9898
import {RangeCalendar} from 'vanilla-starter/RangeCalendar';
9999

100100
export default function Example() {
@@ -110,45 +110,66 @@ export default function Example() {
110110
// See @internationalized/date docs linked above.
111111
///- begin collapse -///
112112
class Custom454 extends GregorianCalendar {
113-
weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4];
114-
getDaysInMonth(date) {
115-
return this.weekPattern[date.month - 1] * 7;
113+
// The anchor date, in Gregorian calendar.
114+
// The anchor date is a date that occurs in the first week of the first month of every fiscal year.
115+
anchorDate = new CalendarDate(2001, 2, 4);
116+
117+
private getYear(year: number): [CalendarDate, number[]] {
118+
let anchor = this.anchorDate.set({year});
119+
let startOfYear = startOfWeek(anchor, 'en', 'sun');
120+
let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1}));
121+
let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4];
122+
return [startOfYear, weekPattern];
123+
}
124+
125+
getDaysInMonth(date: AnyCalendarDate): number {
126+
let [, weekPattern] = this.getYear(date.year);
127+
return weekPattern[date.month - 1] * 7;
116128
}
117129

118130
fromJulianDay(jd: number): CalendarDate {
119131
let gregorian = super.fromJulianDay(jd);
132+
let year = gregorian.year;
133+
134+
let [monthStart, weekPattern] = this.getYear(year);
135+
if (gregorian.compare(monthStart) < 0) {
136+
year--;
137+
[monthStart, weekPattern] = this.getYear(year);
138+
}
120139

121-
let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en');
122-
for (let months = 0; months < this.weekPattern.length; months++) {
123-
let weeksInMonth = this.weekPattern[months];
124-
let monthEnd = monthStart.add({weeks: weeksInMonth});
125-
if (monthEnd.compare(gregorian) > 0) {
140+
for (let month = 1; month <= 12; month++) {
141+
let weeks = weekPattern[month - 1];
142+
let nextMonth = monthStart.add({weeks});
143+
if (nextMonth.compare(gregorian) > 0) {
126144
let days = gregorian.compare(monthStart);
127-
return new CalendarDate(this, monthStart.year, months + 1, days + 1);
145+
return new CalendarDate(this, year, month, days + 1);
128146
}
129-
monthStart = monthEnd;
147+
monthStart = nextMonth;
130148
}
131149

132-
throw Error('Date is not in any month somehow!');
150+
throw new Error('date not found');
133151
}
134152

135153
toJulianDay(date: AnyCalendarDate): number {
136-
let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en');
154+
let [monthStart, weekPattern] = this.getYear(date.year);
137155
for (let month = 1; month < date.month; month++) {
138-
monthStart = monthStart.add({weeks: this.weekPattern[month - 1]});
156+
monthStart = monthStart.add({weeks: weekPattern[month - 1]});
139157
}
140158

141159
let gregorian = monthStart.add({days: date.day - 1});
142160
return super.toJulianDay(gregorian);
143161
}
144162

145-
getFormattableMonth(date) {
146-
let gregorian = toCalendar(date, new GregorianCalendar());
147-
return gregorian.set({month: date.month, day: 1});
163+
getFormattableMonth(date: AnyCalendarDate): CalendarDate {
164+
let anchorMonth = this.anchorDate.month - 1;
165+
let dateMonth = date.month - 1;
166+
let month = ((anchorMonth + dateMonth) % 12) + 1;
167+
let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year;
168+
return new CalendarDate(year, month, 1);
148169
}
149170

150-
isEqual(other) {
151-
return other instanceof Custom454;
171+
isEqual(other: Calendar): boolean {
172+
return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0;
152173
}
153174
}
154175
///- end collapse -///

packages/dev/s2-docs/pages/react-aria/internationalized/date/Calendar.mdx

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,36 +125,55 @@ The following code is an example of how you might implement a custom 4-5-4 calen
125125
import type {AnyCalendarDate, Calendar} from '@internationalized/date';
126126
import {CalendarDate, GregorianCalendar, startOfWeek} from '@internationalized/date';
127127

128-
const weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4];
129-
128+
// This calendar gives each month a 4-5-4 week pattern, with February as the first month of the year.
129+
// This means that in this calendar, 2024-01-01 translates to 2024-02-04 in the Gregorian calendar.
130+
// Months begin on day 1, and go through 7*weeksInMonth days, ending on either the 28th or 35th day of the month.
130131
class Custom454 extends GregorianCalendar {
131-
// Months always have either 4 or 5 full weeks.
132-
getDaysInMonth(date) {
132+
// The anchor date, in Gregorian calendar.
133+
// The anchor date is a date that occurs in the first week of the first month of every fiscal year.
134+
anchorDate = new CalendarDate(2001, 2, 4);
135+
136+
private getYear(year: number): [CalendarDate, number[]] {
137+
let anchor = this.anchorDate.set({year});
138+
let startOfYear = startOfWeek(anchor, 'en', 'sun');
139+
let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1}));
140+
let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4];
141+
return [startOfYear, weekPattern];
142+
}
143+
144+
getDaysInMonth(date: AnyCalendarDate): number {
145+
// Months always have either 4 or 5 full weeks.
146+
let [, weekPattern] = this.getYear(date.year);
133147
return weekPattern[date.month - 1] * 7;
134148
}
135149

136-
// Enable conversion between calendar systems.
137150
fromJulianDay(jd: number): CalendarDate {
138151
let gregorian = super.fromJulianDay(jd);
152+
let year = gregorian.year;
153+
154+
let [monthStart, weekPattern] = this.getYear(year);
155+
if (gregorian.compare(monthStart) < 0) {
156+
year--;
157+
[monthStart, weekPattern] = this.getYear(year);
158+
}
139159

140160
// Start from the beginning of the first week of the gregorian year
141161
// and add weeks until we find the month.
142-
let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en');
143-
for (let months = 0; months < weekPattern.length; months++) {
144-
let weeksInMonth = weekPattern[months];
145-
let monthEnd = monthStart.add({weeks: weeksInMonth});
146-
if (monthEnd.compare(gregorian) > 0) {
162+
for (let month = 1; month <= 12; month++) {
163+
let weeks = weekPattern[month - 1];
164+
let nextMonth = monthStart.add({weeks});
165+
if (nextMonth.compare(gregorian) > 0) {
147166
let days = gregorian.compare(monthStart);
148-
return new CalendarDate(this, monthStart.year, months + 1, days + 1);
167+
return new CalendarDate(this, year, month, days + 1);
149168
}
150-
monthStart = monthEnd;
169+
monthStart = nextMonth;
151170
}
152171

153-
throw Error('Date is not in any month somehow!');
172+
throw new Error('date not found');
154173
}
155174

156175
toJulianDay(date: AnyCalendarDate): number {
157-
let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en');
176+
let [monthStart, weekPattern] = this.getYear(date.year);
158177
for (let month = 1; month < date.month; month++) {
159178
monthStart = monthStart.add({weeks: weekPattern[month - 1]});
160179
}
@@ -163,8 +182,16 @@ class Custom454 extends GregorianCalendar {
163182
return super.toJulianDay(gregorian);
164183
}
165184

166-
isEqual(other: Calendar) {
167-
return other instanceof Custom454;
185+
getFormattableMonth(date: AnyCalendarDate): CalendarDate {
186+
let anchorMonth = this.anchorDate.month - 1;
187+
let dateMonth = date.month - 1;
188+
let month = ((anchorMonth + dateMonth) % 12) + 1;
189+
let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year;
190+
return new CalendarDate(year, month, 1);
191+
}
192+
193+
isEqual(other: Calendar): boolean {
194+
return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0;
168195
}
169196
}
170197
```

packages/dev/s2-docs/pages/s2/Calendar.mdx

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ import {parseDate} from '@internationalized/date';
7474

7575
```tsx render
7676
"use client";
77-
import type {AnyCalendarDate} from '@internationalized/date';
78-
import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date';
77+
import type {AnyCalendarDate, Calendar as ICalendar} from '@internationalized/date';
78+
import {CalendarDate, startOfWeek, GregorianCalendar} from '@internationalized/date';
7979
import {Calendar} from '@react-spectrum/s2';
8080

8181
export default function Example() {
@@ -91,45 +91,66 @@ export default function Example() {
9191
// See @internationalized/date docs linked above.
9292
///- begin collapse -///
9393
class Custom454 extends GregorianCalendar {
94-
weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4];
95-
getDaysInMonth(date) {
96-
return this.weekPattern[date.month - 1] * 7;
94+
// The anchor date, in Gregorian calendar.
95+
// The anchor date is a date that occurs in the first week of the first month of every fiscal year.
96+
anchorDate = new CalendarDate(2001, 2, 4);
97+
98+
private getYear(year: number): [CalendarDate, number[]] {
99+
let anchor = this.anchorDate.set({year});
100+
let startOfYear = startOfWeek(anchor, 'en', 'sun');
101+
let isBigYear = !startOfYear.add({weeks: 53}).compare(anchor.add({years: 1}));
102+
let weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, isBigYear ? 5 : 4];
103+
return [startOfYear, weekPattern];
104+
}
105+
106+
getDaysInMonth(date: AnyCalendarDate): number {
107+
let [, weekPattern] = this.getYear(date.year);
108+
return weekPattern[date.month - 1] * 7;
97109
}
98110

99111
fromJulianDay(jd: number): CalendarDate {
100112
let gregorian = super.fromJulianDay(jd);
113+
let year = gregorian.year;
114+
115+
let [monthStart, weekPattern] = this.getYear(year);
116+
if (gregorian.compare(monthStart) < 0) {
117+
year--;
118+
[monthStart, weekPattern] = this.getYear(year);
119+
}
101120

102-
let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en');
103-
for (let months = 0; months < this.weekPattern.length; months++) {
104-
let weeksInMonth = this.weekPattern[months];
105-
let monthEnd = monthStart.add({weeks: weeksInMonth});
106-
if (monthEnd.compare(gregorian) > 0) {
121+
for (let month = 1; month <= 12; month++) {
122+
let weeks = weekPattern[month - 1];
123+
let nextMonth = monthStart.add({weeks});
124+
if (nextMonth.compare(gregorian) > 0) {
107125
let days = gregorian.compare(monthStart);
108-
return new CalendarDate(this, monthStart.year, months + 1, days + 1);
126+
return new CalendarDate(this, year, month, days + 1);
109127
}
110-
monthStart = monthEnd;
128+
monthStart = nextMonth;
111129
}
112130

113-
throw Error('Date is not in any month somehow!');
131+
throw new Error('date not found');
114132
}
115133

116134
toJulianDay(date: AnyCalendarDate): number {
117-
let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en');
135+
let [monthStart, weekPattern] = this.getYear(date.year);
118136
for (let month = 1; month < date.month; month++) {
119-
monthStart = monthStart.add({weeks: this.weekPattern[month - 1]});
137+
monthStart = monthStart.add({weeks: weekPattern[month - 1]});
120138
}
121139

122140
let gregorian = monthStart.add({days: date.day - 1});
123141
return super.toJulianDay(gregorian);
124142
}
125143

126-
getFormattableMonth(date) {
127-
let gregorian = toCalendar(date, new GregorianCalendar());
128-
return gregorian.set({month: date.month, day: 1});
144+
getFormattableMonth(date: AnyCalendarDate): CalendarDate {
145+
let anchorMonth = this.anchorDate.month - 1;
146+
let dateMonth = date.month - 1;
147+
let month = ((anchorMonth + dateMonth) % 12) + 1;
148+
let year = anchorMonth + dateMonth >= 12 ? date.year + 1 : date.year;
149+
return new CalendarDate(year, month, 1);
129150
}
130151

131-
isEqual(other) {
132-
return other instanceof Custom454;
152+
isEqual(other: ICalendar): boolean {
153+
return other instanceof Custom454 && other.anchorDate.compare(this.anchorDate) === 0;
133154
}
134155
}
135156
///- end collapse -///

0 commit comments

Comments
 (0)