Skip to content

Commit 90bdb8b

Browse files
committed
Add startOfWeek and endOfWeek function
1 parent b9c9cbe commit 90bdb8b

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed

src/datetime/endOfWeek.test.ts

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Temporal } from "@js-temporal/polyfill";
2+
import { expect, test } from "vitest";
3+
4+
import { endOfWeek } from "./endOfWeek.js";
5+
6+
test("PlainDateTime", () => {
7+
expect(
8+
endOfWeek(Temporal.PlainDateTime.from("2024-01-01T12:34:56.789123456"), {
9+
firstDayOfWeek: 1,
10+
}),
11+
).toEqual(Temporal.PlainDateTime.from("2024-01-07T23:59:59.999999999"));
12+
expect(
13+
endOfWeek(Temporal.PlainDateTime.from("2024-01-01T12:34:56.789123456"), {
14+
firstDayOfWeek: 2,
15+
}),
16+
).toEqual(Temporal.PlainDateTime.from("2024-01-01T23:59:59.999999999"));
17+
expect(
18+
endOfWeek(Temporal.PlainDateTime.from("2024-01-02T12:34:56.789123456"), {
19+
firstDayOfWeek: 2,
20+
}),
21+
).toEqual(Temporal.PlainDateTime.from("2024-01-08T23:59:59.999999999"));
22+
expect(
23+
endOfWeek(Temporal.PlainDateTime.from("2024-01-01T12:34:56.789123456"), {
24+
firstDayOfWeek: 7,
25+
}),
26+
).toEqual(Temporal.PlainDateTime.from("2024-01-06T23:59:59.999999999"));
27+
expect(
28+
endOfWeek(Temporal.PlainDateTime.from("2024-01-07T12:34:56.789123456"), {
29+
firstDayOfWeek: 7,
30+
}),
31+
).toEqual(Temporal.PlainDateTime.from("2024-01-13T23:59:59.999999999"));
32+
});
33+
34+
test("PlainDate", () => {
35+
expect(
36+
endOfWeek(Temporal.PlainDate.from("2024-01-01"), {
37+
firstDayOfWeek: 1,
38+
}),
39+
).toEqual(Temporal.PlainDate.from("2024-01-07"));
40+
expect(
41+
endOfWeek(Temporal.PlainDate.from("2024-01-01"), {
42+
firstDayOfWeek: 2,
43+
}),
44+
).toEqual(Temporal.PlainDate.from("2024-01-01"));
45+
expect(
46+
endOfWeek(Temporal.PlainDate.from("2024-01-02"), {
47+
firstDayOfWeek: 2,
48+
}),
49+
).toEqual(Temporal.PlainDate.from("2024-01-08"));
50+
expect(
51+
endOfWeek(Temporal.PlainDate.from("2024-01-01"), {
52+
firstDayOfWeek: 7,
53+
}),
54+
).toEqual(Temporal.PlainDate.from("2024-01-06"));
55+
expect(
56+
endOfWeek(Temporal.PlainDate.from("2024-01-07"), {
57+
firstDayOfWeek: 7,
58+
}),
59+
).toEqual(Temporal.PlainDate.from("2024-01-13"));
60+
});
61+
62+
test("invalid day of week", () => {
63+
expect(() => {
64+
endOfWeek(Temporal.PlainDate.from("2024-01-01"), { firstDayOfWeek: 8 });
65+
}).toThrowError();
66+
expect(() => {
67+
endOfWeek(Temporal.PlainDate.from("2024-01-01"), { firstDayOfWeek: -1 });
68+
}).toThrowError();
69+
});

src/datetime/endOfWeek.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { isPlainDate } from "../type-utils.js";
2+
import type { Temporal } from "../types.js";
3+
4+
export interface EndOfWeekOptions {
5+
/**
6+
* First day of the week.
7+
* For example, in ISO calendar Monday is `1`, Sunday is `7`.
8+
*/
9+
firstDayOfWeek: number;
10+
}
11+
12+
/**
13+
* Returns the end of a week for the given datetime.
14+
* 'end of a week' is ambiguous and locale-dependent,
15+
* so `firstDayOfWeek` option is required.
16+
* This function supports a calendar with a fixed `daysInWeek`,
17+
* even if the week contains more or less than 7 days.
18+
* But it doesn't support a calendar which lacks a fixed number of days.
19+
*
20+
* @param dt datetime object which includes date info
21+
* @returns Temporal object which represents the end of a week
22+
*/
23+
export function endOfWeek<
24+
DateTime extends Temporal.PlainDate | Temporal.PlainDateTime,
25+
>(dt: DateTime, options: EndOfWeekOptions): DateTime {
26+
const firstDayOfWeek = options.firstDayOfWeek;
27+
if (
28+
!Number.isInteger(firstDayOfWeek) ||
29+
firstDayOfWeek < 1 ||
30+
firstDayOfWeek > dt.daysInWeek
31+
) {
32+
throw new Error(`${firstDayOfWeek} isn't a valid day of week`);
33+
}
34+
const endOfWeek = dt.add({
35+
days: (firstDayOfWeek + dt.daysInWeek - dt.dayOfWeek - 1) % dt.daysInWeek,
36+
});
37+
38+
if (isPlainDate(dt)) {
39+
return endOfWeek as DateTime;
40+
}
41+
return endOfWeek.with({
42+
hour: 23,
43+
minute: 59,
44+
second: 59,
45+
millisecond: 999,
46+
microsecond: 999,
47+
nanosecond: 999,
48+
}) as DateTime;
49+
}

src/datetime/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export { endOfHour } from "./endOfHour.js";
1414
export { endOfMinute } from "./endOfMinute.js";
1515
export { endOfMonth } from "./endOfMonth.js";
1616
export { endOfSecond } from "./endOfSecond.js";
17+
export { endOfWeek, type EndOfWeekOptions } from "./endOfWeek.js";
1718
export { endOfYear } from "./endOfYear.js";
1819
export { epochSeconds } from "./epochSeconds.js";
1920
export { formatRfc7231 } from "./formatRfc7231.js";
@@ -36,6 +37,7 @@ export { startOfHour } from "./startOfHour.js";
3637
export { startOfMinute } from "./startOfMinute.js";
3738
export { startOfMonth } from "./startOfMonth.js";
3839
export { startOfSecond } from "./startOfSecond.js";
40+
export { startOfWeek, type StartOfWeekOptions } from "./startOfWeek.js";
3941
export { startOfYear } from "./startOfYear.js";
4042
export { toDateFromClockTime } from "./toDateFromClockTime.js";
4143
export { toDateFromExactTime } from "./toDateFromExactTime.js";

src/datetime/startOfWeek.test.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Temporal } from "@js-temporal/polyfill";
2+
import { expect, test } from "vitest";
3+
4+
import { startOfWeek } from "./startOfWeek.js";
5+
6+
test("PlainDateTime", () => {
7+
expect(
8+
startOfWeek(Temporal.PlainDateTime.from("2024-01-01T12:34:56.789123456"), {
9+
firstDayOfWeek: 1,
10+
}),
11+
).toEqual(Temporal.PlainDateTime.from("2024-01-01T00:00:00"));
12+
expect(
13+
startOfWeek(Temporal.PlainDateTime.from("2024-01-01T12:34:56.789123456"), {
14+
firstDayOfWeek: 7,
15+
}),
16+
).toEqual(Temporal.PlainDateTime.from("2023-12-31T00:00:00"));
17+
expect(
18+
startOfWeek(Temporal.PlainDateTime.from("2024-01-07T12:34:56.789123456"), {
19+
firstDayOfWeek: 1,
20+
}),
21+
).toEqual(Temporal.PlainDateTime.from("2024-01-01T00:00:00"));
22+
});
23+
24+
test("PlainDate", () => {
25+
expect(
26+
startOfWeek(Temporal.PlainDate.from("2024-01-01"), { firstDayOfWeek: 1 }),
27+
).toEqual(Temporal.PlainDate.from("2024-01-01"));
28+
expect(
29+
startOfWeek(Temporal.PlainDate.from("2024-01-01"), { firstDayOfWeek: 7 }),
30+
).toEqual(Temporal.PlainDate.from("2023-12-31"));
31+
expect(
32+
startOfWeek(Temporal.PlainDate.from("2024-01-07"), {
33+
firstDayOfWeek: 1,
34+
}),
35+
).toEqual(Temporal.PlainDate.from("2024-01-01"));
36+
});
37+
38+
test("invalid day of week", () => {
39+
expect(() => {
40+
startOfWeek(Temporal.PlainDate.from("2024-01-01"), { firstDayOfWeek: 8 });
41+
}).toThrowError();
42+
expect(() => {
43+
startOfWeek(Temporal.PlainDate.from("2024-01-01"), { firstDayOfWeek: -1 });
44+
}).toThrowError();
45+
});

src/datetime/startOfWeek.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { isPlainDate } from "../type-utils.js";
2+
import type { Temporal } from "../types.js";
3+
4+
export interface StartOfWeekOptions {
5+
/**
6+
* First day of the week.
7+
* For example, in ISO calendar Monday is `1`, Sunday is `7`.
8+
*/
9+
firstDayOfWeek: number;
10+
}
11+
12+
/**
13+
* Returns the start of a week for the given datetime.
14+
* 'start of a week' is ambiguous and locale-dependent,
15+
* so `firstDayOfWeek` option is required.
16+
* This function supports a calendar with a fixed `daysInWeek`,
17+
* even if the week contains more or less than 7 days.
18+
* But it doesn't support a calendar which lacks a fixed number of days.
19+
*
20+
* @param dt datetime object which includes date info
21+
* @returns Temporal object which represents the start of a week
22+
*/
23+
export function startOfWeek<
24+
DateTime extends Temporal.PlainDate | Temporal.PlainDateTime,
25+
>(dt: DateTime, options: StartOfWeekOptions): DateTime {
26+
const firstDayOfWeek = options.firstDayOfWeek;
27+
if (
28+
!Number.isInteger(firstDayOfWeek) ||
29+
firstDayOfWeek < 1 ||
30+
firstDayOfWeek > dt.daysInWeek
31+
) {
32+
throw new Error(`${firstDayOfWeek} isn't a valid day of week`);
33+
}
34+
const startOfWeek = dt.subtract({
35+
days: (dt.dayOfWeek - firstDayOfWeek + dt.daysInWeek) % dt.daysInWeek,
36+
});
37+
38+
if (isPlainDate(dt)) {
39+
return startOfWeek as DateTime;
40+
}
41+
return startOfWeek.with({
42+
hour: 0,
43+
minute: 0,
44+
second: 0,
45+
millisecond: 0,
46+
microsecond: 0,
47+
nanosecond: 0,
48+
}) as DateTime;
49+
}

0 commit comments

Comments
 (0)