Skip to content

Commit f1bf6fa

Browse files
feat(smithy-client): support strict timestamp parsing (#2737)
From context, we know what the format of a timestamp should be, and we can restrict the input to match that format rather than relying on the unspecified behavior of new Date(string).
1 parent 209784d commit f1bf6fa

File tree

2 files changed

+491
-7
lines changed

2 files changed

+491
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import { parseEpochTimestamp, parseRfc3339DateTime, parseRfc7231DateTime } from "./date-utils";
2+
3+
describe("parseRfc3339DateTime", () => {
4+
it.each([null, undefined])("returns undefined for %s", (value) => {
5+
expect(parseRfc3339DateTime(value)).toBeUndefined();
6+
});
7+
8+
describe("parses properly formatted dates", () => {
9+
it("with fractional seconds", () => {
10+
expect(parseRfc3339DateTime("1985-04-12T23:20:50.52Z")).toEqual(new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 520)));
11+
});
12+
it("without fractional seconds", () => {
13+
expect(parseRfc3339DateTime("1985-04-12T23:20:50Z")).toEqual(new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 0)));
14+
});
15+
it("with leap seconds", () => {
16+
expect(parseRfc3339DateTime("1990-12-31T15:59:60Z")).toEqual(new Date(Date.UTC(1990, 11, 31, 15, 59, 60, 0)));
17+
});
18+
it("with leap days", () => {
19+
expect(parseRfc3339DateTime("2004-02-29T15:59:59Z")).toEqual(new Date(Date.UTC(2004, 1, 29, 15, 59, 59, 0)));
20+
});
21+
it("with leading zeroes", () => {
22+
expect(parseRfc3339DateTime("0004-02-09T05:09:09.09Z")).toEqual(new Date(Date.UTC(4, 1, 9, 5, 9, 9, 90)));
23+
expect(parseRfc3339DateTime("0004-02-09T00:00:00.00Z")).toEqual(new Date(Date.UTC(4, 1, 9, 0, 0, 0, 0)));
24+
});
25+
});
26+
27+
it.each([
28+
"85-04-12T23:20:50.52Z",
29+
"985-04-12T23:20:50.52Z",
30+
"1985-13-12T23:20:50.52Z",
31+
"1985-00-12T23:20:50.52Z",
32+
"1985-4-12T23:20:50.52Z",
33+
"1985-04-32T23:20:50.52Z",
34+
"1985-04-00T23:20:50.52Z",
35+
"1985-04-05T24:20:50.52Z",
36+
"1985-04-05T23:61:50.52Z",
37+
"1985-04-05T23:20:61.52Z",
38+
"1985-04-31T23:20:50.52Z",
39+
"2005-02-29T15:59:59Z",
40+
"Mon, 31 Dec 1990 15:59:60 GMT",
41+
"Monday, 31-Dec-90 15:59:60 GMT",
42+
"Mon Dec 31 15:59:60 1990",
43+
"1985-04-12T23:20:50.52Z1985-04-12T23:20:50.52Z",
44+
"1985-04-12T23:20:50.52ZA",
45+
"A1985-04-12T23:20:50.52Z",
46+
])("rejects %s", (value) => {
47+
expect(() => parseRfc3339DateTime(value)).toThrowError();
48+
});
49+
});
50+
51+
describe("parseRfc7231DateTime", () => {
52+
it.each([null, undefined])("returns undefined for %s", (value) => {
53+
expect(parseRfc7231DateTime(value)).toBeUndefined();
54+
});
55+
56+
describe("parses properly formatted dates", () => {
57+
describe("with fractional seconds", () => {
58+
it.each([
59+
["imf-fixdate", "Sun, 06 Nov 1994 08:49:37.52 GMT"],
60+
["rfc-850", "Sunday, 06-Nov-94 08:49:37.52 GMT"],
61+
["asctime", "Sun Nov 6 08:49:37.52 1994"],
62+
])("in format %s", (_, value) => {
63+
expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1994, 10, 6, 8, 49, 37, 520)));
64+
});
65+
});
66+
describe("without fractional seconds", () => {
67+
it.each([
68+
["imf-fixdate", "Sun, 06 Nov 1994 08:49:37 GMT"],
69+
["rfc-850", "Sunday, 06-Nov-94 08:49:37 GMT"],
70+
["asctime", "Sun Nov 6 08:49:37 1994"],
71+
])("in format %s", (_, value) => {
72+
expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1994, 10, 6, 8, 49, 37, 0)));
73+
});
74+
});
75+
describe("with leap seconds", () => {
76+
it.each([
77+
["imf-fixdate", "Mon, 31 Dec 1990 15:59:60 GMT"],
78+
["rfc-850", "Monday, 31-Dec-90 15:59:60 GMT"],
79+
["asctime", "Mon Dec 31 15:59:60 1990"],
80+
])("in format %s", (_, value) => {
81+
expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1990, 11, 31, 15, 59, 60, 0)));
82+
});
83+
});
84+
describe("with leap days", () => {
85+
it.each([
86+
["imf-fixdate", "Sun, 29 Feb 2004 15:59:59 GMT"],
87+
["rfc-850", "Sunday, 29-Feb-04 15:59:59 GMT"],
88+
["asctime", "Sun Feb 29 15:59:59 2004"],
89+
])("in format %s", (_, value) => {
90+
expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(2004, 1, 29, 15, 59, 59, 0)));
91+
});
92+
});
93+
describe("with leading zeroes", () => {
94+
it.each([
95+
["imf-fixdate", "Sun, 06 Nov 0004 08:09:07.02 GMT", 4],
96+
["rfc-850", "Sunday, 06-Nov-04 08:09:07.02 GMT", 2004],
97+
["asctime", "Sun Nov 6 08:09:07.02 0004", 4],
98+
])("in format %s", (_, value, year) => {
99+
expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(year, 10, 6, 8, 9, 7, 20)));
100+
});
101+
});
102+
describe("with all-zero components", () => {
103+
it.each([
104+
["imf-fixdate", "Sun, 06 Nov 0004 00:00:00.00 GMT", 4],
105+
["rfc-850", "Sunday, 06-Nov-04 00:00:00.00 GMT", 2004],
106+
["asctime", "Sun Nov 6 00:00:00.00 0004", 4],
107+
])("in format %s", (_, value, year) => {
108+
expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(year, 10, 6, 0, 0, 0, 0)));
109+
});
110+
});
111+
});
112+
113+
describe("when parsing rfc-850 dates", () => {
114+
it("properly adjusts 2-digit years", () => {
115+
// These tests will fail in a couple of decades. Good luck future developers.
116+
expect(parseRfc7231DateTime("Friday, 31-Dec-99 12:34:56.789 GMT")).toEqual(
117+
new Date(Date.UTC(1999, 11, 31, 12, 34, 56, 789))
118+
);
119+
expect(parseRfc7231DateTime("Thursday, 31-Dec-65 12:34:56.789 GMT")).toEqual(
120+
new Date(Date.UTC(2065, 11, 31, 12, 34, 56, 789))
121+
);
122+
});
123+
});
124+
125+
it.each([
126+
"1985-04-12T23:20:50.52Z",
127+
"1985-04-12T23:20:50Z",
128+
129+
"Sun, 06 Nov 0004 08:09:07.02 GMTSun, 06 Nov 0004 08:09:07.02 GMT",
130+
"Sun, 06 Nov 0004 08:09:07.02 GMTA",
131+
"ASun, 06 Nov 0004 08:09:07.02 GMT",
132+
"Sun, 06 Nov 94 08:49:37 GMT",
133+
"Sun, 06 Dov 1994 08:49:37 GMT",
134+
"Mun, 06 Nov 1994 08:49:37 GMT",
135+
"Sunday, 06 Nov 1994 08:49:37 GMT",
136+
"Sun, 06 November 1994 08:49:37 GMT",
137+
"Sun, 06 Nov 1994 24:49:37 GMT",
138+
"Sun, 06 Nov 1994 08:69:37 GMT",
139+
"Sun, 06 Nov 1994 08:49:67 GMT",
140+
"Sun, 06-11-1994 08:49:37 GMT",
141+
"Sun, 06 11 1994 08:49:37 GMT",
142+
"Sun, 31 Nov 1994 08:49:37 GMT",
143+
"Sun, 29 Feb 2005 15:59:59 GMT",
144+
145+
"Sunday, 06-Nov-04 08:09:07.02 GMTSunday, 06-Nov-04 08:09:07.02 GMT",
146+
"ASunday, 06-Nov-04 08:09:07.02 GMT",
147+
"Sunday, 06-Nov-04 08:09:07.02 GMTA",
148+
"Sunday, 06-Nov-1994 08:49:37 GMT",
149+
"Sunday, 06-Dov-94 08:49:37 GMT",
150+
"Sundae, 06-Nov-94 08:49:37 GMT",
151+
"Sun, 06-Nov-94 08:49:37 GMT",
152+
"Sunday, 06-November-94 08:49:37 GMT",
153+
"Sunday, 06-Nov-94 24:49:37 GMT",
154+
"Sunday, 06-Nov-94 08:69:37 GMT",
155+
"Sunday, 06-Nov-94 08:49:67 GMT",
156+
"Sunday, 06 11 94 08:49:37 GMT",
157+
"Sunday, 06-11-1994 08:49:37 GMT",
158+
"Sunday, 31-Nov-94 08:49:37 GMT",
159+
"Sunday, 29-Feb-05 15:59:59 GMT",
160+
161+
"Sun Nov 6 08:09:07.02 0004Sun Nov 6 08:09:07.02 0004",
162+
"ASun Nov 6 08:09:07.02 0004",
163+
"Sun Nov 6 08:09:07.02 0004A",
164+
"Sun Nov 6 08:49:37 94",
165+
"Sun Dov 6 08:49:37 1994",
166+
"Mun Nov 6 08:49:37 1994",
167+
"Sunday Nov 6 08:49:37 1994",
168+
"Sun November 6 08:49:37 1994",
169+
"Sun Nov 6 24:49:37 1994",
170+
"Sun Nov 6 08:69:37 1994",
171+
"Sun Nov 6 08:49:67 1994",
172+
"Sun 06-11 08:49:37 1994",
173+
"Sun 06 11 08:49:37 1994",
174+
"Sun 11 6 08:49:37 1994",
175+
"Sun Nov 31 08:49:37 1994",
176+
"Sun Feb 29 15:59:59 2005",
177+
"Sun Nov 6 08:49:37 1994",
178+
])("rejects %s", (value) => {
179+
expect(() => parseRfc7231DateTime(value)).toThrowError();
180+
});
181+
});
182+
183+
describe("parseEpochTimestamp", () => {
184+
it.each([null, undefined])("returns undefined for %s", (value) => {
185+
expect(parseEpochTimestamp(value)).toBeUndefined();
186+
});
187+
188+
describe("parses properly formatted dates", () => {
189+
describe("with fractional seconds", () => {
190+
it.each(["482196050.52", 482196050.52])("parses %s", (value) => {
191+
expect(parseEpochTimestamp(value)).toEqual(new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 520)));
192+
});
193+
});
194+
describe("without fractional seconds", () => {
195+
it.each(["482196050", 482196050, 482196050.0])("parses %s", (value) => {
196+
expect(parseEpochTimestamp(value)).toEqual(new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 0)));
197+
});
198+
});
199+
});
200+
it.each([
201+
"1985-04-12T23:20:50.52Z",
202+
"1985-04-12T23:20:50Z",
203+
"Mon, 31 Dec 1990 15:59:60 GMT",
204+
"Monday, 31-Dec-90 15:59:60 GMT",
205+
"Mon Dec 31 15:59:60 1990",
206+
"NaN",
207+
NaN,
208+
"Infinity",
209+
Infinity,
210+
"0x42",
211+
])("rejects %s", (value) => {
212+
expect(() => parseEpochTimestamp(value)).toThrowError();
213+
});
214+
});

0 commit comments

Comments
 (0)