Skip to content

Commit 8d48511

Browse files
committed
Support ZonedDateTime in startOfWeek and endOfWeek
1 parent 4b2f1ba commit 8d48511

File tree

4 files changed

+272
-20
lines changed

4 files changed

+272
-20
lines changed

src/datetime/endOfWeek.test.ts

+111
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,114 @@ test("invalid day of week", () => {
6767
endOfWeek(Temporal.PlainDate.from("2024-01-01"), { firstDayOfWeek: -1 });
6868
}).toThrowError();
6969
});
70+
71+
test("ZonedDateTime without offset transition", () => {
72+
expect(
73+
endOfWeek(
74+
Temporal.ZonedDateTime.from(
75+
"2024-01-01T12:34:56.789123456+09:00[Asia/Tokyo]",
76+
),
77+
{
78+
firstDayOfWeek: 1,
79+
},
80+
),
81+
).toEqual(
82+
Temporal.ZonedDateTime.from(
83+
"2024-01-07T23:59:59.999999999+09:00[Asia/Tokyo]",
84+
),
85+
);
86+
expect(
87+
endOfWeek(
88+
Temporal.ZonedDateTime.from(
89+
"2024-01-01T12:34:56.789123456+09:00[Asia/Tokyo]",
90+
),
91+
{
92+
firstDayOfWeek: 2,
93+
},
94+
),
95+
).toEqual(
96+
Temporal.ZonedDateTime.from(
97+
"2024-01-01T23:59:59.999999999+09:00[Asia/Tokyo]",
98+
),
99+
);
100+
expect(
101+
endOfWeek(
102+
Temporal.ZonedDateTime.from(
103+
"2024-01-02T12:34:56.789123456+09:00[Asia/Tokyo]",
104+
),
105+
{
106+
firstDayOfWeek: 2,
107+
},
108+
),
109+
).toEqual(
110+
Temporal.ZonedDateTime.from(
111+
"2024-01-08T23:59:59.999999999+09:00[Asia/Tokyo]",
112+
),
113+
);
114+
expect(
115+
endOfWeek(
116+
Temporal.ZonedDateTime.from(
117+
"2024-01-01T12:34:56.789123456+09:00[Asia/Tokyo]",
118+
),
119+
{
120+
firstDayOfWeek: 7,
121+
},
122+
),
123+
).toEqual(
124+
Temporal.ZonedDateTime.from(
125+
"2024-01-06T23:59:59.999999999+09:00[Asia/Tokyo]",
126+
),
127+
);
128+
expect(
129+
endOfWeek(
130+
Temporal.ZonedDateTime.from(
131+
"2024-01-07T12:34:56.789123456+09:00[Asia/Tokyo]",
132+
),
133+
{
134+
firstDayOfWeek: 7,
135+
},
136+
),
137+
).toEqual(
138+
Temporal.ZonedDateTime.from(
139+
"2024-01-13T23:59:59.999999999+09:00[Asia/Tokyo]",
140+
),
141+
);
142+
});
143+
144+
test("ZonedDateTime and forward transition", () => {
145+
expect(
146+
endOfWeek(
147+
Temporal.ZonedDateTime.from("2011-12-27T00:00:00-10:00[Pacific/Apia]"),
148+
{ firstDayOfWeek: 6 },
149+
),
150+
).toEqual(
151+
Temporal.ZonedDateTime.from(
152+
"2011-12-29T23:59:59.999999999-10:00[Pacific/Apia]",
153+
),
154+
);
155+
expect(
156+
endOfWeek(
157+
Temporal.ZonedDateTime.from("1919-03-26T00:00:00-05:00[America/Toronto]"),
158+
{ firstDayOfWeek: 1 },
159+
),
160+
).toEqual(
161+
Temporal.ZonedDateTime.from(
162+
"1919-03-30T23:29:59.999999999-05:00[America/Toronto]",
163+
),
164+
);
165+
});
166+
167+
test("ZonedDateTime and backward transition", () => {
168+
expect(
169+
endOfWeek(
170+
Temporal.ZonedDateTime.from(
171+
"2010-11-03T00:00:00-02:30[America/St_Johns]",
172+
),
173+
{ firstDayOfWeek: 7 },
174+
),
175+
).toEqual(
176+
Temporal.ZonedDateTime.from(
177+
"2010-11-06T23:59:59.999999999-03:30[America/St_Johns]",
178+
),
179+
);
180+
});

src/datetime/endOfWeek.ts

+45-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { isPlainDate } from "../type-utils.js";
1+
import { isPlainDate, isZonedDateTime } from "../type-utils.js";
22
import type { Temporal } from "../types.js";
3+
import { endOfTimeForZonedDateTime } from "./_endOfTimeForZonedDateTime.js";
34

45
export interface EndOfWeekOptions {
56
/**
@@ -9,6 +10,23 @@ export interface EndOfWeekOptions {
910
firstDayOfWeek: number;
1011
}
1112

13+
function endOfWeekWithDayPrecision(
14+
dt: Temporal.PlainDate,
15+
firstDayOfWeek: number,
16+
): Temporal.PlainDate;
17+
function endOfWeekWithDayPrecision(
18+
dt: Temporal.PlainDateTime,
19+
firstDayOfWeek: number,
20+
): Temporal.PlainDateTime;
21+
function endOfWeekWithDayPrecision(
22+
dt: Temporal.PlainDate | Temporal.PlainDateTime,
23+
firstDayOfWeek: number,
24+
) {
25+
return dt.add({
26+
days: (firstDayOfWeek + dt.daysInWeek - dt.dayOfWeek - 1) % dt.daysInWeek,
27+
});
28+
}
29+
1230
/**
1331
* Returns the end of a week for the given datetime.
1432
* 'end of a week' is ambiguous and locale-dependent,
@@ -21,7 +39,10 @@ export interface EndOfWeekOptions {
2139
* @returns Temporal object which represents the end of a week
2240
*/
2341
export function endOfWeek<
24-
DateTime extends Temporal.PlainDate | Temporal.PlainDateTime,
42+
DateTime extends
43+
| Temporal.PlainDate
44+
| Temporal.PlainDateTime
45+
| Temporal.ZonedDateTime,
2546
>(dt: DateTime, options: EndOfWeekOptions): DateTime {
2647
const firstDayOfWeek = options.firstDayOfWeek;
2748
if (
@@ -31,19 +52,33 @@ export function endOfWeek<
3152
) {
3253
throw new Error(`${firstDayOfWeek} isn't a valid day of week`);
3354
}
34-
const endOfWeek = dt.add({
35-
days: (firstDayOfWeek + dt.daysInWeek - dt.dayOfWeek - 1) % dt.daysInWeek,
36-
});
3755

38-
if (isPlainDate(dt)) {
39-
return endOfWeek as DateTime;
40-
}
41-
return endOfWeek.with({
56+
const timeArg = {
4257
hour: 23,
4358
minute: 59,
4459
second: 59,
4560
millisecond: 999,
4661
microsecond: 999,
4762
nanosecond: 999,
48-
}) as DateTime;
63+
};
64+
65+
if (isZonedDateTime(dt)) {
66+
const endOfWeek = endOfWeekWithDayPrecision(
67+
dt.toPlainDate(),
68+
firstDayOfWeek,
69+
);
70+
return endOfTimeForZonedDateTime(dt, {
71+
year: endOfWeek.year,
72+
month: endOfWeek.month,
73+
day: endOfWeek.day,
74+
...timeArg,
75+
}) as DateTime;
76+
}
77+
78+
if (isPlainDate(dt)) {
79+
return endOfWeekWithDayPrecision(dt, firstDayOfWeek) as DateTime;
80+
}
81+
return endOfWeekWithDayPrecision(dt, firstDayOfWeek).with(
82+
timeArg,
83+
) as DateTime;
4984
}

src/datetime/startOfWeek.test.ts

+71
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,74 @@ test("invalid day of week", () => {
4343
startOfWeek(Temporal.PlainDate.from("2024-01-01"), { firstDayOfWeek: -1 });
4444
}).toThrowError();
4545
});
46+
47+
test("ZonedDateTime without offset transition", () => {
48+
expect(
49+
startOfWeek(
50+
Temporal.ZonedDateTime.from(
51+
"2024-01-01T12:34:56.789123456+09:00[Asia/Tokyo]",
52+
),
53+
{
54+
firstDayOfWeek: 1,
55+
},
56+
),
57+
).toEqual(
58+
Temporal.ZonedDateTime.from("2024-01-01T00:00:00+09:00[Asia/Tokyo]"),
59+
);
60+
expect(
61+
startOfWeek(
62+
Temporal.ZonedDateTime.from(
63+
"2024-01-01T12:34:56.789123456+09:00[Asia/Tokyo]",
64+
),
65+
{
66+
firstDayOfWeek: 7,
67+
},
68+
),
69+
).toEqual(
70+
Temporal.ZonedDateTime.from("2023-12-31T00:00:00+09:00[Asia/Tokyo]"),
71+
);
72+
expect(
73+
startOfWeek(
74+
Temporal.ZonedDateTime.from(
75+
"2024-01-07T12:34:56.789123456+09:00[Asia/Tokyo]",
76+
),
77+
{
78+
firstDayOfWeek: 1,
79+
},
80+
),
81+
).toEqual(
82+
Temporal.ZonedDateTime.from("2024-01-01T00:00:00+09:00[Asia/Tokyo]"),
83+
);
84+
});
85+
86+
test("ZonedDateTime and forward transition", () => {
87+
expect(
88+
startOfWeek(
89+
Temporal.ZonedDateTime.from("2012-01-01T00:00:00+14:00[Pacific/Apia]"),
90+
{ firstDayOfWeek: 5 },
91+
),
92+
).toEqual(
93+
Temporal.ZonedDateTime.from("2011-12-31T00:00:00+14:00[Pacific/Apia]"),
94+
);
95+
expect(
96+
startOfWeek(
97+
Temporal.ZonedDateTime.from("1919-04-02T00:00:00-04:00[America/Toronto]"),
98+
{ firstDayOfWeek: 1 },
99+
),
100+
).toEqual(
101+
Temporal.ZonedDateTime.from("1919-03-31T00:30:00-04:00[America/Toronto]"),
102+
);
103+
});
104+
105+
test("ZonedDateTime and backward transition", () => {
106+
expect(
107+
startOfWeek(
108+
Temporal.ZonedDateTime.from(
109+
"2010-11-09T00:00:00-03:30[America/St_Johns]",
110+
),
111+
{ firstDayOfWeek: 7 },
112+
),
113+
).toEqual(
114+
Temporal.ZonedDateTime.from("2010-11-07T00:00:00-02:30[America/St_Johns]"),
115+
);
116+
});

src/datetime/startOfWeek.ts

+45-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { isPlainDate } from "../type-utils.js";
1+
import { isPlainDate, isZonedDateTime } from "../type-utils.js";
22
import type { Temporal } from "../types.js";
3+
import { startOfTimeForZonedDateTime } from "./_startOfTimeForZonedDateTime.js";
34

45
export interface StartOfWeekOptions {
56
/**
@@ -9,6 +10,23 @@ export interface StartOfWeekOptions {
910
firstDayOfWeek: number;
1011
}
1112

13+
function startOfWeekWithDayPrecision(
14+
dt: Temporal.PlainDate,
15+
firstDayOfWeek: number,
16+
): Temporal.PlainDate;
17+
function startOfWeekWithDayPrecision(
18+
dt: Temporal.PlainDateTime,
19+
firstDayOfWeek: number,
20+
): Temporal.PlainDateTime;
21+
function startOfWeekWithDayPrecision(
22+
dt: Temporal.PlainDate | Temporal.PlainDateTime,
23+
firstDayOfWeek: number,
24+
) {
25+
return dt.subtract({
26+
days: (dt.dayOfWeek - firstDayOfWeek + dt.daysInWeek) % dt.daysInWeek,
27+
});
28+
}
29+
1230
/**
1331
* Returns the start of a week for the given datetime.
1432
* 'start of a week' is ambiguous and locale-dependent,
@@ -21,7 +39,10 @@ export interface StartOfWeekOptions {
2139
* @returns Temporal object which represents the start of a week
2240
*/
2341
export function startOfWeek<
24-
DateTime extends Temporal.PlainDate | Temporal.PlainDateTime,
42+
DateTime extends
43+
| Temporal.PlainDate
44+
| Temporal.PlainDateTime
45+
| Temporal.ZonedDateTime,
2546
>(dt: DateTime, options: StartOfWeekOptions): DateTime {
2647
const firstDayOfWeek = options.firstDayOfWeek;
2748
if (
@@ -31,19 +52,33 @@ export function startOfWeek<
3152
) {
3253
throw new Error(`${firstDayOfWeek} isn't a valid day of week`);
3354
}
34-
const startOfWeek = dt.subtract({
35-
days: (dt.dayOfWeek - firstDayOfWeek + dt.daysInWeek) % dt.daysInWeek,
36-
});
3755

38-
if (isPlainDate(dt)) {
39-
return startOfWeek as DateTime;
40-
}
41-
return startOfWeek.with({
56+
const timeArg = {
4257
hour: 0,
4358
minute: 0,
4459
second: 0,
4560
millisecond: 0,
4661
microsecond: 0,
4762
nanosecond: 0,
48-
}) as DateTime;
63+
};
64+
65+
if (isZonedDateTime(dt)) {
66+
const startOfWeek = startOfWeekWithDayPrecision(
67+
dt.toPlainDate(),
68+
firstDayOfWeek,
69+
);
70+
return startOfTimeForZonedDateTime(dt, {
71+
year: startOfWeek.year,
72+
month: startOfWeek.month,
73+
day: startOfWeek.day,
74+
...timeArg,
75+
}) as DateTime;
76+
}
77+
78+
if (isPlainDate(dt)) {
79+
return startOfWeekWithDayPrecision(dt, firstDayOfWeek) as DateTime;
80+
}
81+
return startOfWeekWithDayPrecision(dt, firstDayOfWeek).with(
82+
timeArg,
83+
) as DateTime;
4984
}

0 commit comments

Comments
 (0)