Skip to content

Commit ed1e093

Browse files
JeseeqQtax
andauthored
UTC date hydration serialization (#41)
Co-authored-by: Andreas Zetterlund <[email protected]>
1 parent d9716c7 commit ed1e093

File tree

5 files changed

+268
-27
lines changed

5 files changed

+268
-27
lines changed

src/common/util.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ export const isDataQuery = (query: string): boolean => {
2525

2626
export const withNullableTypes = (types: string[]) => {
2727
return types.reduce((acc: string[], type) => {
28-
acc.push(type);
29-
acc.push(`nullable(${type})`);
30-
return acc;
28+
return [
29+
...acc,
30+
type,
31+
`nullable(${type})`,
32+
`${type} not null`,
33+
`${type} null`
34+
];
3135
}, []);
3236
};
3337

@@ -65,3 +69,20 @@ export const generateUserAgent = (
6569
: "";
6670
return clientString + systemInfoString() + driverString;
6771
};
72+
73+
export const zeroPad = (
74+
param: number | string,
75+
length: number,
76+
direction = "left"
77+
) => {
78+
let paded = param.toString();
79+
while (paded.length < length) {
80+
if (direction === "left") {
81+
paded = "0" + paded;
82+
} else {
83+
paded = paded + "0";
84+
}
85+
}
86+
87+
return paded;
88+
};

src/formatter/index.ts

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import BigNumber from "bignumber.js";
2-
import { checkArgumentValid } from "../common/util";
2+
import { checkArgumentValid, zeroPad } from "../common/util";
33
import { INVALID_PARAMETERS } from "../common/errors";
44

55
const CHARS_GLOBAL_REGEXP = /[\0\b\t\n\r\x1a"'\\]/g; // eslint-disable-line no-control-regex
@@ -16,19 +16,6 @@ const CHARS_ESCAPE_MAP: Record<string, string> = {
1616
"\\": "\\\\"
1717
};
1818

19-
const zeroPad = (param: number, length: number, direction = "left") => {
20-
let paded = param.toString();
21-
while (paded.length < length) {
22-
if (direction === "left") {
23-
paded = "0" + paded;
24-
} else {
25-
paded = paded + "0";
26-
}
27-
}
28-
29-
return paded;
30-
};
31-
3219
export class Tuple {
3320
value: unknown[];
3421

@@ -42,9 +29,9 @@ export class PGDate extends Date {}
4229
export class TimestampTZ extends Date {
4330
timeZone: string;
4431

45-
constructor(value: number | string, { timeZone }: { timeZone: string }) {
32+
constructor(value: number | string, { timeZone }: { timeZone?: string }) {
4633
super(value);
47-
this.timeZone = timeZone;
34+
this.timeZone = timeZone || "UTC";
4835
}
4936
}
5037

@@ -178,13 +165,13 @@ export class QueryFormatter {
178165
return "NULL";
179166
}
180167

181-
const year = dt.getFullYear();
182-
const month = dt.getMonth() + 1;
183-
const day = dt.getDate();
184-
const hour = dt.getHours();
185-
const minute = dt.getMinutes();
186-
const second = dt.getSeconds();
187-
const millisecond = dt.getMilliseconds();
168+
const year = dt.getUTCFullYear();
169+
const month = dt.getUTCMonth() + 1;
170+
const day = dt.getUTCDate();
171+
const hour = dt.getUTCHours();
172+
const minute = dt.getUTCMinutes();
173+
const second = dt.getUTCSeconds();
174+
const millisecond = dt.getUTCMilliseconds();
188175

189176
// YYYY-MM-DD HH:mm:ss.mmm
190177
const yearMonthDay =

src/statement/hydrateDate.test.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { hydrateDate } from "./hydrateDate";
2+
3+
describe("hydrate Date", () => {
4+
it("hydrate date type", () => {
5+
const dates = ["2006-01-02", "2006-1-02", "2006-01-2", "2006-1-2"];
6+
for (const input of dates) {
7+
const date = hydrateDate(input);
8+
9+
const year = date?.getUTCFullYear();
10+
const month = date?.getUTCMonth();
11+
const day = date?.getUTCDate();
12+
13+
expect(year).toEqual(2006);
14+
expect(month).toEqual(0);
15+
expect(day).toEqual(2);
16+
}
17+
});
18+
it("hydrate pg_date type", () => {
19+
const dates = ["2023-2-13", "2023-02-13"];
20+
for (const input of dates) {
21+
const date = hydrateDate(input);
22+
23+
const year = date?.getUTCFullYear();
24+
const month = date?.getUTCMonth();
25+
const day = date?.getUTCDate();
26+
27+
expect(year).toEqual(2023);
28+
expect(month).toEqual(1);
29+
expect(day).toEqual(13);
30+
}
31+
});
32+
it("hydrate timestamp type", () => {
33+
const input = "2033-02-13 12:00:10";
34+
const date = hydrateDate(input);
35+
36+
const year = date?.getUTCFullYear();
37+
const month = date?.getUTCMonth();
38+
const day = date?.getUTCDate();
39+
const hour = date?.getUTCHours();
40+
const minute = date?.getUTCMinutes();
41+
const second = date?.getUTCSeconds();
42+
43+
expect(year).toEqual(2033);
44+
expect(month).toEqual(1);
45+
expect(day).toEqual(13);
46+
expect(hour).toEqual(12);
47+
expect(minute).toEqual(0);
48+
expect(second).toEqual(10);
49+
});
50+
it("hydrate timestamptz type", () => {
51+
const input = "2033-02-13 12:00:10+01:00";
52+
const date = hydrateDate(input);
53+
54+
const year = date?.getUTCFullYear();
55+
const month = date?.getUTCMonth();
56+
const day = date?.getUTCDate();
57+
const hour = date?.getUTCHours();
58+
const minute = date?.getUTCMinutes();
59+
const second = date?.getUTCSeconds();
60+
61+
expect(year).toEqual(2033);
62+
expect(month).toEqual(1);
63+
expect(day).toEqual(13);
64+
expect(hour).toEqual(11);
65+
expect(minute).toEqual(0);
66+
expect(second).toEqual(10);
67+
});
68+
it("hydrate timestamptz type 2", () => {
69+
const input = "2033-02-13 12:00:10-01:00";
70+
const date = hydrateDate(input);
71+
72+
const year = date?.getUTCFullYear();
73+
const month = date?.getUTCMonth();
74+
const day = date?.getUTCDate();
75+
const hour = date?.getUTCHours();
76+
const minute = date?.getUTCMinutes();
77+
const second = date?.getUTCSeconds();
78+
79+
expect(year).toEqual(2033);
80+
expect(month).toEqual(1);
81+
expect(day).toEqual(13);
82+
expect(hour).toEqual(13);
83+
expect(minute).toEqual(0);
84+
expect(second).toEqual(10);
85+
});
86+
it("hydrate timestamptz type 3", () => {
87+
const input = "2033-02-13 12:00:10-02:30";
88+
const date = hydrateDate(input);
89+
90+
const year = date?.getUTCFullYear();
91+
const month = date?.getUTCMonth();
92+
const day = date?.getUTCDate();
93+
const hour = date?.getUTCHours();
94+
const minute = date?.getUTCMinutes();
95+
const second = date?.getUTCSeconds();
96+
97+
expect(year).toEqual(2033);
98+
expect(month).toEqual(1);
99+
expect(day).toEqual(13);
100+
expect(hour).toEqual(14);
101+
expect(minute).toEqual(30);
102+
expect(second).toEqual(10);
103+
});
104+
it("hydrate timestamptz type 4", () => {
105+
const input = "2033-02-13T12:00:10-02:30";
106+
const date = hydrateDate(input);
107+
108+
const year = date?.getUTCFullYear();
109+
const month = date?.getUTCMonth();
110+
const day = date?.getUTCDate();
111+
const hour = date?.getUTCHours();
112+
const minute = date?.getUTCMinutes();
113+
const second = date?.getUTCSeconds();
114+
115+
expect(year).toEqual(2033);
116+
expect(month).toEqual(1);
117+
expect(day).toEqual(13);
118+
expect(hour).toEqual(14);
119+
expect(minute).toEqual(30);
120+
expect(second).toEqual(10);
121+
});
122+
it("hydrate timestamptz padded time", () => {
123+
const input = "2033-02-13 2:15:10-1:00";
124+
const date = hydrateDate(input);
125+
126+
const year = date?.getUTCFullYear();
127+
const month = date?.getUTCMonth();
128+
const day = date?.getUTCDate();
129+
const hour = date?.getUTCHours();
130+
const minute = date?.getUTCMinutes();
131+
const second = date?.getUTCSeconds();
132+
133+
expect(year).toEqual(2033);
134+
expect(month).toEqual(1);
135+
expect(day).toEqual(13);
136+
expect(hour).toEqual(3);
137+
expect(minute).toEqual(15);
138+
expect(second).toEqual(10);
139+
});
140+
it("hydrate timestampntz type", () => {
141+
const input = "2033-02-13T12:00:10";
142+
const date = hydrateDate(input);
143+
144+
const year = date?.getUTCFullYear();
145+
const month = date?.getUTCMonth();
146+
const day = date?.getUTCDate();
147+
const hour = date?.getUTCHours();
148+
const minute = date?.getUTCMinutes();
149+
const second = date?.getUTCSeconds();
150+
151+
expect(year).toEqual(2033);
152+
expect(month).toEqual(1);
153+
expect(day).toEqual(13);
154+
expect(hour).toEqual(12);
155+
expect(minute).toEqual(0);
156+
expect(second).toEqual(10);
157+
});
158+
it("handle old dates", () => {
159+
const input = "0033-01-01 12:00:00";
160+
const date = hydrateDate(input);
161+
162+
const year = date?.getUTCFullYear();
163+
const month = date?.getUTCMonth();
164+
const day = date?.getUTCDate();
165+
const hour = date?.getUTCHours();
166+
const minute = date?.getUTCMinutes();
167+
const second = date?.getUTCSeconds();
168+
169+
expect(year).toEqual(33);
170+
expect(month).toEqual(0);
171+
expect(day).toEqual(1);
172+
expect(hour).toEqual(12);
173+
expect(minute).toEqual(0);
174+
expect(second).toEqual(0);
175+
});
176+
});

src/statement/hydrateDate.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { zeroPad } from "../common/util";
2+
3+
// will match such formats
4+
// 2006-01-02
5+
6+
// 9999-12-31 23:59:59.999999
7+
8+
// 9999-12-31 23:59:59
9+
10+
// 2006-1-2 1:24:5.000+00
11+
// 2006-1-2 1:24:5.000-07
12+
// 2006-1-2 15:04:05.000-07
13+
// 2006-01-02 15:04:05.000000-07
14+
// 2006-01-02 15:04:05.000000-07:00
15+
// 2006-01-02 15:04:05.000000-07:00:00
16+
// 2006-01-02 15:04:05-07
17+
// 2006-01-02 15:04:05-07:00
18+
// 2006-01-02 15:04:05-07:00:00
19+
20+
// 2105-12-31 23:59:59
21+
22+
// 2033-02-13 2:15:10+7:00
23+
24+
const DATE_FORMAT =
25+
/^(\d{4})-(\d{1,2})-(\d{1,2})(?:(?:[ \t]+|T)(\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{1,6}))?)?(?:([+-])(\d{1,2})(?::?(\d{2})(?::?(\d{2}))?)?|Z)?)?$/;
26+
27+
export const hydrateDate = (value: string) => {
28+
const match = DATE_FORMAT.exec(value);
29+
30+
if (!match) {
31+
return null;
32+
}
33+
34+
// date
35+
const year = zeroPad(match[1], 4);
36+
const month = zeroPad(match[2], 2);
37+
const day = zeroPad(match[3], 2);
38+
39+
// time
40+
const hour = zeroPad(match[4] || "00", 2);
41+
const minute = zeroPad(match[5] || "00", 2);
42+
const second = zeroPad(match[6] || "00", 2);
43+
const msec = zeroPad(match[7] || "000", 6);
44+
45+
// tz
46+
const sign = match[8] || "-";
47+
const tzHour = zeroPad(match[9] || "00", 2);
48+
const tzMin = zeroPad(match[10] || "00", 2);
49+
50+
// ignore tzsec
51+
const tzSec = zeroPad(match[11] || "00", 2);
52+
53+
const date = `${year}-${month}-${day}T${hour}:${minute}:${second}.${msec}${sign}${tzHour}:${tzMin}`;
54+
55+
return new Date(date);
56+
};

src/statement/hydrateResponse.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import BigNumber from "bignumber.js";
22
import { ExecuteQueryOptions, Row } from "../types";
33
import { Meta } from "../meta";
44
import { isDateType, isNumberType } from "./dataTypes";
5+
import { hydrateDate } from "./hydrateDate";
56

67
const getHydratedValue = (
78
value: unknown,
@@ -10,7 +11,7 @@ const getHydratedValue = (
1011
) => {
1112
const { type } = meta;
1213
if (isDateType(type)) {
13-
return value ? new Date(value as string) : value;
14+
return value ? hydrateDate(value as string) : value;
1415
}
1516
if (isNumberType(type)) {
1617
if (

0 commit comments

Comments
 (0)