





















@@ -3,12 +3,245 @@ import { describe, expect, it } from "vitest";
33import { parseAbsoluteTimeMs } from "./parse.js";
4455describe("parseAbsoluteTimeMs", () => {
6-it("parses positive epoch milliseconds", () => {
7-expect(parseAbsoluteTimeMs("1700000000000")).toBe(1_700_000_000_000);
6+describe("epoch milliseconds", () => {
7+it("parses positive epoch milliseconds", () => {
8+expect(parseAbsoluteTimeMs("1700000000000")).toBe(1_700_000_000_000);
9+});
10+11+it("rejects digit-only timestamps outside the Date range", () => {
12+expect(parseAbsoluteTimeMs(String(Number.MAX_SAFE_INTEGER))).toBeNull();
13+});
14+15+it("rejects negative epoch milliseconds", () => {
16+// Negative numbers don't match /^\d+$/ pattern, so they're parsed as dates
17+// "-1000" is interpreted as a date string by Date.parse()
18+// This tests that very old timestamps outside valid range are rejected
19+expect(parseAbsoluteTimeMs("-8640000000000001")).toBeNull();
20+});
21+22+it("rejects non-numeric strings that look like numbers", () => {
23+expect(parseAbsoluteTimeMs("123abc")).toBeNull();
24+});
825});
92610-it("rejects digit-only timestamps outside the Date range", () => {
11-expect(parseAbsoluteTimeMs(String(Number.MAX_SAFE_INTEGER))).toBeNull();
27+describe("ISO 8601 date only", () => {
28+it("parses date only as midnight UTC", () => {
29+expect(parseAbsoluteTimeMs("2024-01-15")).toBe(Date.parse("2024-01-15T00:00:00Z"));
30+});
31+32+it("parses date with implicit Z suffix", () => {
33+expect(parseAbsoluteTimeMs("2024-06-01")).toBe(Date.parse("2024-06-01T00:00:00Z"));
34+});
35+});
36+37+describe("ISO 8601 datetime without timezone", () => {
38+it("parses datetime without timezone as UTC", () => {
39+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00")).toBe(Date.parse("2024-01-15T10:30:00Z"));
40+});
41+42+it("parses datetime with seconds as UTC", () => {
43+expect(parseAbsoluteTimeMs("2024-03-20T15:45:30")).toBe(Date.parse("2024-03-20T15:45:30Z"));
44+});
45+});
46+47+describe("ISO 8601 with Z (UTC) timezone", () => {
48+it("parses datetime with Z suffix", () => {
49+const expected = Date.parse("2024-01-15T10:30:00Z");
50+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00Z")).toBe(expected);
51+});
52+53+it("parses datetime with lowercase z suffix", () => {
54+const expected = Date.parse("2024-01-15T10:30:00Z");
55+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00z")).toBe(expected);
56+});
57+58+it("parses datetime with milliseconds and Z", () => {
59+const expected = Date.parse("2024-01-15T10:30:45.123Z");
60+expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123Z")).toBe(expected);
61+});
62+63+it("parses datetime with microseconds and Z", () => {
64+// JavaScript Date has millisecond precision, but should parse without error
65+const expected = Date.parse("2024-01-15T10:30:45.123456Z");
66+expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123456Z")).toBe(expected);
67+});
68+69+it("parses datetime with nanoseconds and Z (truncates to ms)", () => {
70+const expected = Date.parse("2024-01-15T10:30:45.123456789Z");
71+expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123456789Z")).toBe(expected);
72+});
73+});
74+75+describe("ISO 8601 with timezone offset (colon format)", () => {
76+it("parses datetime with positive offset +HH:MM", () => {
77+// UTC+8 (Beijing/Singapore)
78+const expected = Date.parse("2024-01-15T10:30:00+08:00");
79+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+08:00")).toBe(expected);
80+});
81+82+it("parses datetime with negative offset -HH:MM", () => {
83+// UTC-5 (EST)
84+const expected = Date.parse("2024-01-15T10:30:00-05:00");
85+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00-05:00")).toBe(expected);
86+});
87+88+it("parses datetime with zero offset +00:00", () => {
89+const expected = Date.parse("2024-01-15T10:30:00+00:00");
90+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+00:00")).toBe(expected);
91+});
92+93+it("parses datetime with maximum positive offset +14:00", () => {
94+const expected = Date.parse("2024-01-15T10:30:00+14:00");
95+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+14:00")).toBe(expected);
96+});
97+98+it("parses datetime with maximum negative offset -12:00", () => {
99+const expected = Date.parse("2024-01-15T10:30:00-12:00");
100+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00-12:00")).toBe(expected);
101+});
102+103+it("parses datetime with half-hour offset +05:30", () => {
104+// India Standard Time
105+const expected = Date.parse("2024-01-15T10:30:00+05:30");
106+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+05:30")).toBe(expected);
107+});
108+109+it("parses datetime with 45-minute offset +12:45", () => {
110+// New Zealand Chatham Islands
111+const expected = Date.parse("2024-01-15T10:30:00+12:45");
112+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+12:45")).toBe(expected);
113+});
114+});
115+116+describe("ISO 8601 with timezone offset (no colon format)", () => {
117+it("parses datetime with positive offset +HHMM", () => {
118+const expected = Date.parse("2024-01-15T10:30:00+0800");
119+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+0800")).toBe(expected);
120+});
121+122+it("parses datetime with negative offset -HHMM", () => {
123+const expected = Date.parse("2024-01-15T10:30:00-0500");
124+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00-0500")).toBe(expected);
125+});
126+127+it("parses datetime with zero offset +0000", () => {
128+const expected = Date.parse("2024-01-15T10:30:00+0000");
129+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+0000")).toBe(expected);
130+});
131+});
132+133+describe("ISO 8601 with milliseconds and timezone", () => {
134+it("parses datetime with milliseconds and positive offset", () => {
135+const expected = Date.parse("2024-01-15T10:30:45.500+08:00");
136+expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.500+08:00")).toBe(expected);
137+});
138+139+it("parses datetime with milliseconds and negative offset", () => {
140+const expected = Date.parse("2024-01-15T10:30:45.999-05:00");
141+expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.999-05:00")).toBe(expected);
142+});
143+144+it("parses datetime with microseconds and timezone", () => {
145+const expected = Date.parse("2024-01-15T10:30:45.123456+08:00");
146+expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123456+08:00")).toBe(expected);
147+});
148+});
149+150+describe("whitespace handling", () => {
151+it("trims leading and trailing whitespace", () => {
152+expect(parseAbsoluteTimeMs(" 1700000000000 ")).toBe(1_700_000_000_000);
153+expect(parseAbsoluteTimeMs(" 2024-01-15T10:30:00Z ")).toBe(
154+Date.parse("2024-01-15T10:30:00Z"),
155+);
156+});
157+158+it("rejects strings with only whitespace", () => {
159+expect(parseAbsoluteTimeMs("")).toBeNull();
160+expect(parseAbsoluteTimeMs(" ")).toBeNull();
161+});
162+});
163+164+describe("invalid formats", () => {
165+it("rejects invalid date strings", () => {
166+expect(parseAbsoluteTimeMs("not-a-date")).toBeNull();
167+expect(parseAbsoluteTimeMs("2024-13-40")).toBeNull();
168+expect(parseAbsoluteTimeMs("invalid")).toBeNull();
169+});
170+171+it("rejects truly malformed date strings", () => {
172+// JavaScript Date.parse is very lenient, so we test only truly invalid formats
173+expect(parseAbsoluteTimeMs("24-01-15")).toBeNull(); // Two-digit year too ambiguous
174+expect(parseAbsoluteTimeMs("not-a-date")).toBeNull();
175+expect(parseAbsoluteTimeMs("")).toBeNull();
176+});
177+178+it("rejects non-padded ISO-like date formats", () => {
179+expect(parseAbsoluteTimeMs("2024-1-15")).toBeNull();
180+});
181+182+it("rejects incomplete datetime strings", () => {
183+expect(parseAbsoluteTimeMs("2024-01-15T")).toBeNull();
184+expect(parseAbsoluteTimeMs("2024-01-15T10")).toBeNull();
185+});
186+187+it("rejects invalid timezone formats", () => {
188+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+8:00")).toBeNull();
189+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+080")).toBeNull();
190+expect(parseAbsoluteTimeMs("2024-01-15T10:30:00GMT")).toBeNull();
191+});
192+});
193+194+describe("edge cases", () => {
195+it("handles leap year dates", () => {
196+expect(parseAbsoluteTimeMs("2024-02-29T00:00:00Z")).toBe(Date.parse("2024-02-29T00:00:00Z"));
197+});
198+199+it("handles year boundary dates", () => {
200+expect(parseAbsoluteTimeMs("2023-12-31T23:59:59Z")).toBe(Date.parse("2023-12-31T23:59:59Z"));
201+expect(parseAbsoluteTimeMs("2024-01-01T00:00:00Z")).toBe(Date.parse("2024-01-01T00:00:00Z"));
202+});
203+204+it("handles edge of valid timestamp range", () => {
205+// JavaScript Date range is approximately -100,000,000 to +100,000,000 days
206+// Test timestamps well within the valid range that are realistic for cron usage
207+const year2000 = new Date("2000-01-01T00:00:00Z").getTime();
208+expect(parseAbsoluteTimeMs(year2000.toString())).toBe(year2000);
209+210+const year2050 = new Date("2050-01-01T00:00:00Z").getTime();
211+expect(parseAbsoluteTimeMs(year2050.toString())).toBe(year2050);
212+});
213+214+it("handles maximum valid timestamp", () => {
215+// JavaScript Date range ends at +100,000,000 days
216+const maxValid = new Date(8640000000000000).getTime();
217+expect(parseAbsoluteTimeMs(maxValid.toString())).toBe(maxValid);
218+});
219+});
220+221+describe("real-world cron examples", () => {
222+it("parses common cron scheduling timestamps", () => {
223+// Daily at midnight UTC
224+expect(parseAbsoluteTimeMs("2024-01-01T00:00:00Z")).toBe(Date.parse("2024-01-01T00:00:00Z"));
225+226+// Hourly at the top of the hour
227+expect(parseAbsoluteTimeMs("2024-06-15T12:00:00+00:00")).toBe(
228+Date.parse("2024-06-15T12:00:00+00:00"),
229+);
230+231+// Specific business hours in different timezones
232+expect(parseAbsoluteTimeMs("2024-03-01T09:00:00+08:00")).toBe(
233+Date.parse("2024-03-01T09:00:00+08:00"),
234+);
235+});
236+237+it("parses timestamps with sub-second precision for high-frequency jobs", () => {
238+expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.001Z")).toBe(
239+Date.parse("2024-01-15T10:30:45.001Z"),
240+);
241+expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.999Z")).toBe(
242+Date.parse("2024-01-15T10:30:45.999Z"),
243+);
244+});
12245});
1324614247it("parses ISO timestamps with UTC defaults and explicit offsets", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。