Skip to content

Commit 907f22a

Browse files
committed
Better handling of timezones in dates, with tests
1 parent 80455bc commit 907f22a

File tree

3 files changed

+65
-10
lines changed

3 files changed

+65
-10
lines changed

internal-packages/tsql/src/query/escape.test.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,20 @@ describe("SQLValueEscaper", () => {
120120
).toBe("[[1, 2], [3, 4]]");
121121
});
122122

123-
it("should escape dates with toDateTime64", () => {
123+
it("should escape dates with toDateTime64 and default UTC timezone", () => {
124124
const date = new Date("2024-01-15T10:30:00.500Z");
125125
const result = escaper.visit(date);
126-
expect(result).toContain("toDateTime64");
127-
expect(result).toContain("2024-01-15");
126+
expect(result).toBe("toDateTime64('2024-01-15 10:30:00.500000', 6, 'UTC')");
127+
});
128+
129+
it("should escape dates with custom timezone", () => {
130+
const escaperWithTz = new SQLValueEscaper({
131+
dialect: "clickhouse",
132+
timezone: "America/New_York",
133+
});
134+
const date = new Date("2024-01-15T10:30:00.500Z");
135+
const result = escaperWithTz.visit(date);
136+
expect(result).toBe("toDateTime64('2024-01-15 10:30:00.500000', 6, 'America/New_York')");
128137
});
129138
});
130139

@@ -136,11 +145,20 @@ describe("SQLValueEscaper", () => {
136145
expect(escaper.visit(false)).toBe("false");
137146
});
138147

139-
it("should escape dates with toDateTime", () => {
148+
it("should escape dates with toDateTime and default UTC timezone", () => {
140149
const date = new Date("2024-01-15T10:30:00.500Z");
141150
const result = escaper.visit(date);
142-
expect(result).toContain("toDateTime");
143-
expect(result).toContain("2024-01-15");
151+
expect(result).toBe("toDateTime('2024-01-15 10:30:00.500000', 'UTC')");
152+
});
153+
154+
it("should escape dates with custom timezone", () => {
155+
const escaperWithTz = new SQLValueEscaper({
156+
dialect: "tsql",
157+
timezone: "Europe/London",
158+
});
159+
const date = new Date("2024-01-15T10:30:00.500Z");
160+
const result = escaperWithTz.visit(date);
161+
expect(result).toBe("toDateTime('2024-01-15 10:30:00.500000', 'Europe/London')");
144162
});
145163
});
146164
});

internal-packages/tsql/src/query/escape.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,11 @@ export class SQLValueEscaper {
200200
const datetimeString = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}000`;
201201

202202
if (this.dialect === "tsql") {
203-
return `toDateTime(${this.visitString(datetimeString)})`;
203+
return `toDateTime(${this.visitString(datetimeString)}, ${this.visitString(this.timezone)})`;
204204
}
205-
return `toDateTime64(${this.visitString(datetimeString)}, 6, ${this.visitString(this.timezone)})`;
205+
return `toDateTime64(${this.visitString(datetimeString)}, 6, ${this.visitString(
206+
this.timezone
207+
)})`;
206208
}
207209

208210
private visitArray(value: EscapableValue[]): string {
@@ -261,4 +263,3 @@ export function getClickHouseType(value: unknown): string {
261263
// Default to String for unknown types
262264
return "String";
263265
}
264-

internal-packages/tsql/src/query/printer.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,12 +867,48 @@ describe("ClickHousePrinter", () => {
867867
});
868868

869869
describe("Functions", () => {
870-
it("should print toDateTime", () => {
870+
it("should print toDateTime with column argument", () => {
871871
const { sql } = printQuery("SELECT toDateTime(created_at) FROM task_runs");
872872

873873
expect(sql).toContain("toDateTime(created_at)");
874874
});
875875

876+
it("should print toDateTime with string and timezone arguments", () => {
877+
const { sql, params } = printQuery(
878+
"SELECT toDateTime('2024-01-15 10:30:00', 'UTC') FROM task_runs"
879+
);
880+
881+
// String values are parameterized for security
882+
expect(sql).toContain("toDateTime(");
883+
expect(sql).toMatch(/toDateTime\(\{tsql_val_\d+: String\}, \{tsql_val_\d+: String\}\)/);
884+
expect(Object.values(params)).toContain("2024-01-15 10:30:00");
885+
expect(Object.values(params)).toContain("UTC");
886+
});
887+
888+
it("should print toDateTime with timezone containing special characters", () => {
889+
const { sql, params } = printQuery(
890+
"SELECT toDateTime('2024-01-15 10:30:00', 'America/New_York') FROM task_runs"
891+
);
892+
893+
// String values are parameterized for security
894+
expect(sql).toContain("toDateTime(");
895+
expect(sql).toMatch(/toDateTime\(\{tsql_val_\d+: String\}, \{tsql_val_\d+: String\}\)/);
896+
expect(Object.values(params)).toContain("2024-01-15 10:30:00");
897+
expect(Object.values(params)).toContain("America/New_York");
898+
});
899+
900+
it("should print toDateTime64 with precision and timezone", () => {
901+
const { sql, params } = printQuery(
902+
"SELECT toDateTime64('2024-01-15 10:30:00.500000', 6, 'Europe/London') FROM task_runs"
903+
);
904+
905+
// String values are parameterized, but numeric precision is inline
906+
expect(sql).toContain("toDateTime64(");
907+
expect(sql).toMatch(/toDateTime64\(\{tsql_val_\d+: String\}, 6, \{tsql_val_\d+: String\}\)/);
908+
expect(Object.values(params)).toContain("2024-01-15 10:30:00.500000");
909+
expect(Object.values(params)).toContain("Europe/London");
910+
});
911+
876912
it("should print now()", () => {
877913
const { sql } = printQuery("SELECT * FROM task_runs WHERE created_at > now()");
878914

0 commit comments

Comments
 (0)