Skip to content

Commit 4ab5e3c

Browse files
committed
Support ZonedDateTime in startOfDay, startOfHour, startOfMinute and startOfSecond
1 parent 4bc0753 commit 4ab5e3c

10 files changed

+257
-12
lines changed
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { Temporal } from "../types.js";
2+
3+
// TODO: use `Temporal.ZonedDateTime.prototype.getTimeZoneTransition` directly after polyfills are updated
4+
export function getTimeZoneTransition(
5+
zdt: Temporal.ZonedDateTime,
6+
direction: "next" | "previous",
7+
): Temporal.ZonedDateTime | null {
8+
if (zdt.getTimeZoneTransition) {
9+
return zdt.getTimeZoneTransition(direction);
10+
}
11+
// legacy polyfill support
12+
/* eslint-disable */
13+
// @ts-expect-error
14+
const timeZone = zdt.getTimeZone();
15+
const instant =
16+
direction === "next" ?
17+
timeZone.getNextTransition?.(zdt.toInstant())
18+
: timeZone.getPreviousTransition?.(zdt.toInstant());
19+
if (instant) {
20+
// @ts-expect-error
21+
return instant.toZonedDateTimeISO(timeZone).withCalendar(zdt.getCalendar());
22+
} else {
23+
return null;
24+
}
25+
/* eslint-enable */
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { Temporal } from "../types.js";
2+
import { getTimeZoneTransition } from "./_getTimeZoneTransition.js";
3+
4+
export function startOfTimeForZonedDateTime(
5+
zdt: Temporal.ZonedDateTime,
6+
withArg: Temporal.PlainDateTimeLike,
7+
): Temporal.ZonedDateTime {
8+
const [earlier, later] = (["earlier", "later"] as const).map(
9+
(disambiguation) =>
10+
zdt.with(withArg, {
11+
offset: "ignore",
12+
disambiguation,
13+
}),
14+
) as [Temporal.ZonedDateTime, Temporal.ZonedDateTime];
15+
16+
if (earlier.toPlainDateTime().equals(later.toPlainDateTime())) {
17+
// backward transition or no transition
18+
return earlier;
19+
} else {
20+
// forward transition
21+
const start = getTimeZoneTransition(earlier, "next");
22+
if (start === null) {
23+
throw new Error("Unknown error");
24+
}
25+
return start;
26+
}
27+
}

src/datetime/startOfDay.test.ts

+55
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,58 @@ test("PlainDateTime", () => {
88
startOfDay(Temporal.PlainDateTime.from("2024-01-01T12:34:56.789123456")),
99
).toEqual(Temporal.PlainDateTime.from("2024-01-01T00:00:00"));
1010
});
11+
12+
test("ZonedDateTime without offset transition", () => {
13+
expect(
14+
startOfDay(
15+
Temporal.ZonedDateTime.from(
16+
"2024-01-01T12:34:56.789123456+09:00[Asia/Tokyo]",
17+
),
18+
),
19+
).toEqual(
20+
Temporal.ZonedDateTime.from("2024-01-01T00:00:00+09:00[Asia/Tokyo]"),
21+
);
22+
});
23+
24+
test("ZonedDateTime and backward transition", () => {
25+
expect(
26+
startOfDay(
27+
Temporal.ZonedDateTime.from("2024-10-27T23:00:00+00:00[Europe/London]"),
28+
),
29+
).toEqual(
30+
Temporal.ZonedDateTime.from("2024-10-27T00:00:00+01:00[Europe/London]"),
31+
);
32+
expect(
33+
startOfDay(
34+
Temporal.ZonedDateTime.from(
35+
"2010-11-07T23:00:00-03:30[America/St_Johns]",
36+
),
37+
),
38+
).toEqual(
39+
Temporal.ZonedDateTime.from("2010-11-07T00:00:00-02:30[America/St_Johns]"),
40+
);
41+
});
42+
43+
test("ZonedDateTime and forward transition", () => {
44+
expect(
45+
startOfDay(
46+
Temporal.ZonedDateTime.from("2024-03-31T23:00:00+01:00[Europe/London]"),
47+
),
48+
).toEqual(
49+
Temporal.ZonedDateTime.from("2024-03-31T00:00:00+00:00[Europe/London]"),
50+
);
51+
expect(
52+
startOfDay(
53+
Temporal.ZonedDateTime.from("2024-03-31T12:00:00+03:00[Asia/Beirut]"),
54+
),
55+
).toEqual(
56+
Temporal.ZonedDateTime.from("2024-03-31T01:00:00+03:00[Asia/Beirut]"),
57+
);
58+
expect(
59+
startOfDay(
60+
Temporal.ZonedDateTime.from("1919-03-31T12:00:00-04:00[America/Toronto]"),
61+
),
62+
).toEqual(
63+
Temporal.ZonedDateTime.from("1919-03-31T00:30:00-04:00[America/Toronto]"),
64+
);
65+
});

src/datetime/startOfDay.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
1+
import { isZonedDateTime } from "../type-utils.js";
12
import type { Temporal } from "../types.js";
3+
import { startOfTimeForZonedDateTime } from "./_startOfTimeForZonedDateTime.js";
24

35
/**
46
* Returns the start of a day for the given datetime
57
* @param dt datetime object which includes date and time info
68
* @returns Temporal object which represents the start of a day
79
*/
8-
export function startOfDay<DateTime extends Temporal.PlainDateTime>(
9-
dt: DateTime,
10-
): DateTime {
11-
return dt.with({
10+
export function startOfDay<
11+
DateTime extends Temporal.PlainDateTime | Temporal.ZonedDateTime,
12+
>(dt: DateTime): DateTime {
13+
const withArg = {
1214
hour: 0,
1315
minute: 0,
1416
second: 0,
1517
millisecond: 0,
1618
microsecond: 0,
1719
nanosecond: 0,
18-
}) as DateTime;
20+
};
21+
if (!isZonedDateTime(dt)) {
22+
return dt.with(withArg) as DateTime;
23+
}
24+
25+
// TODO: Use built-in `startOfDay` method after polyfills follow up https://github.com/tc39/proposal-temporal/pull/2918
26+
return startOfTimeForZonedDateTime(dt, withArg) as DateTime;
1927
}

src/datetime/startOfHour.test.ts

+74
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,77 @@ test("PlainTime", () => {
1414
Temporal.PlainTime.from("12:00:00"),
1515
);
1616
});
17+
18+
test("ZonedDateTime without offset transition", () => {
19+
expect(
20+
startOfHour(
21+
Temporal.ZonedDateTime.from(
22+
"2024-01-01T01:23:45.678901234+09:00[Asia/Tokyo]",
23+
),
24+
),
25+
).toEqual(
26+
Temporal.ZonedDateTime.from("2024-01-01T01:00:00+09:00[Asia/Tokyo]"),
27+
);
28+
});
29+
30+
test("ZonedDateTime and backward transition", () => {
31+
expect(
32+
startOfHour(
33+
Temporal.ZonedDateTime.from(
34+
"2023-04-02T01:45:00+10:30[Australia/Lord_Howe]",
35+
),
36+
),
37+
).toEqual(
38+
Temporal.ZonedDateTime.from(
39+
"2023-04-02T01:00:00+11:00[Australia/Lord_Howe]",
40+
),
41+
);
42+
expect(
43+
startOfHour(
44+
Temporal.ZonedDateTime.from(
45+
"1945-09-11T23:30:00+07:30[Asia/Kuala_Lumpur]",
46+
),
47+
),
48+
).toEqual(
49+
Temporal.ZonedDateTime.from("1945-09-11T23:00:00+09:00[Asia/Kuala_Lumpur]"),
50+
);
51+
expect(
52+
startOfHour(
53+
Temporal.ZonedDateTime.from(
54+
"2010-11-07T00:30:00-03:30[America/St_Johns]",
55+
),
56+
),
57+
).toEqual(
58+
Temporal.ZonedDateTime.from("2010-11-07T00:00:00-02:30[America/St_Johns]"),
59+
);
60+
expect(
61+
startOfHour(
62+
Temporal.ZonedDateTime.from(
63+
"1944-09-10T02:30:00-04:00[America/Barbados]",
64+
),
65+
),
66+
).toEqual(
67+
Temporal.ZonedDateTime.from("1944-09-10T02:00:00-03:30[America/Barbados]"),
68+
);
69+
});
70+
71+
test("ZonedDateTime and forward transition", () => {
72+
expect(
73+
startOfHour(
74+
Temporal.ZonedDateTime.from(
75+
"1984-10-01T00:45:00-03:00[America/Paramaribo]",
76+
),
77+
),
78+
).toEqual(
79+
Temporal.ZonedDateTime.from(
80+
"1984-10-01T00:30:00-03:00[America/Paramaribo]",
81+
),
82+
);
83+
expect(
84+
startOfHour(
85+
Temporal.ZonedDateTime.from("1919-03-31T00:45:00-04:00[America/Toronto]"),
86+
),
87+
).toEqual(
88+
Temporal.ZonedDateTime.from("1919-03-31T00:30:00-04:00[America/Toronto]"),
89+
);
90+
});

src/datetime/startOfHour.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1+
import { isZonedDateTime } from "../type-utils.js";
12
import type { Temporal } from "../types.js";
3+
import { startOfTimeForZonedDateTime } from "./_startOfTimeForZonedDateTime.js";
24

35
/**
46
* Returns the start of a hour for the given datetime
57
* @param dt datetime object which includes time info
68
* @returns Temporal object which represents the start of a hour
79
*/
810
export function startOfHour<
9-
DateTime extends Temporal.PlainTime | Temporal.PlainDateTime,
11+
DateTime extends
12+
| Temporal.PlainTime
13+
| Temporal.PlainDateTime
14+
| Temporal.ZonedDateTime,
1015
>(dt: DateTime): DateTime {
11-
return dt.with({
16+
const withArg = {
1217
minute: 0,
1318
second: 0,
1419
millisecond: 0,
1520
microsecond: 0,
1621
nanosecond: 0,
17-
}) as DateTime;
22+
};
23+
if (!isZonedDateTime(dt)) {
24+
return dt.with(withArg) as DateTime;
25+
}
26+
27+
return startOfTimeForZonedDateTime(dt, withArg) as DateTime;
1828
}

src/datetime/startOfMinute.test.ts

+20
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,23 @@ test("PlainTime", () => {
1414
Temporal.PlainTime.from("12:34:00"),
1515
);
1616
});
17+
18+
test("ZonedDateTime and forward transition", () => {
19+
expect(
20+
startOfMinute(
21+
Temporal.ZonedDateTime.from("1972-01-07T00:44:59+00:00[Africa/Monrovia]"),
22+
),
23+
).toEqual(
24+
Temporal.ZonedDateTime.from("1972-01-07T00:44:30+00:00[Africa/Monrovia]"),
25+
);
26+
});
27+
28+
test("ZonedDateTime and backward transition", () => {
29+
expect(
30+
startOfMinute(
31+
Temporal.ZonedDateTime.from("1952-10-15T23:59:50-11:20[Pacific/Niue]"),
32+
),
33+
).toEqual(
34+
Temporal.ZonedDateTime.from("1952-10-15T23:59:00-11:19:40[Pacific/Niue]"),
35+
);
36+
});

src/datetime/startOfMinute.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1+
import { isZonedDateTime } from "../type-utils.js";
12
import type { Temporal } from "../types.js";
3+
import { startOfTimeForZonedDateTime } from "./_startOfTimeForZonedDateTime.js";
24

35
/**
46
* Returns the start of a minute for the given datetime
57
* @param dt datetime object which includes time info
68
* @returns Temporal object which represents the start of a minute
79
*/
810
export function startOfMinute<
9-
DateTime extends Temporal.PlainTime | Temporal.PlainDateTime,
11+
DateTime extends
12+
| Temporal.PlainTime
13+
| Temporal.PlainDateTime
14+
| Temporal.ZonedDateTime,
1015
>(dt: DateTime): DateTime {
11-
return dt.with({
16+
const withArg = {
1217
second: 0,
1318
millisecond: 0,
1419
microsecond: 0,
1520
nanosecond: 0,
16-
}) as DateTime;
21+
};
22+
if (!isZonedDateTime(dt)) {
23+
return dt.with(withArg) as DateTime;
24+
}
25+
return startOfTimeForZonedDateTime(dt, withArg) as DateTime;
1726
}

src/datetime/startOfSecond.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,15 @@ test("PlainTime", () => {
1414
Temporal.PlainTime.from("12:34:56"),
1515
);
1616
});
17+
18+
test("ZonedDateTime", () => {
19+
expect(
20+
startOfSecond(
21+
Temporal.ZonedDateTime.from(
22+
"2024-01-01T12:34:56.789123456+09:00[Asia/Tokyo]",
23+
),
24+
),
25+
).toEqual(
26+
Temporal.ZonedDateTime.from("2024-01-01T12:34:56+09:00[Asia/Tokyo]"),
27+
);
28+
});

src/datetime/startOfSecond.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import type { Temporal } from "../types.js";
66
* @returns Temporal object which represents the start of a second
77
*/
88
export function startOfSecond<
9-
DateTime extends Temporal.PlainTime | Temporal.PlainDateTime,
9+
DateTime extends
10+
| Temporal.PlainTime
11+
| Temporal.PlainDateTime
12+
| Temporal.ZonedDateTime,
1013
>(dt: DateTime): DateTime {
14+
// assumption: no sub-second offset transition in timezone database
1115
return dt.with({ millisecond: 0, microsecond: 0, nanosecond: 0 }) as DateTime;
1216
}

0 commit comments

Comments
 (0)