Skip to content

Commit ee788f4

Browse files
authored
feat(mssql-driver): Support use named timezones (cube-js#10582)
Add CUBEJS_DB_MSSQL_USE_NAMED_TIMEZONES env variable to enable server-side DST-aware timezone conversion via AT TIME ZONE in MSSQL. Previously, convertTz() computed a fixed UTC offset at query build time using moment().tz().format('Z'), which didn't account for DST transitions within the queried data range. When enabled, IANA timezone names (e.g. 'America/New_York') are automatically mapped to Windows timezone names (e.g. 'Eastern Standard Time') required by MSSQL's AT TIME ZONE clause. Defaults to false for backward compatibility..
1 parent 1768c32 commit ee788f4

6 files changed

Lines changed: 516 additions & 31 deletions

File tree

packages/cubejs-backend-shared/src/env.ts

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,33 +1055,29 @@ const variables: Record<string, (...args: any) => any> = {
10551055
* @see https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html#function_convert-tz
10561056
* @see https://dev.mysql.com/doc/refman/8.4/en/time-zone-support.html
10571057
*/
1058-
mysqlUseNamedTimezones: ({ dataSource }: { dataSource: string }) => {
1059-
const val = process.env[
1060-
keyByDataSource(
1061-
'CUBEJS_DB_MYSQL_USE_NAMED_TIMEZONES',
1062-
dataSource,
1063-
)
1064-
];
1058+
mysqlUseNamedTimezones: ({ dataSource }: { dataSource: string }) => (
1059+
get(keyByDataSource('CUBEJS_DB_MYSQL_USE_NAMED_TIMEZONES', dataSource))
1060+
// It's true in schema-compiler integration tests
1061+
.default('false')
1062+
.asBool()
1063+
),
10651064

1066-
if (val) {
1067-
if (val.toLocaleLowerCase() === 'true') {
1068-
return true;
1069-
} else if (val.toLowerCase() === 'false') {
1070-
return false;
1071-
} else {
1072-
throw new TypeError(
1073-
`The ${
1074-
keyByDataSource(
1075-
'CUBEJS_DB_MYSQL_USE_NAMED_TIMEZONES',
1076-
dataSource,
1077-
)
1078-
} must be either 'true' or 'false'.`
1079-
);
1080-
}
1081-
} else {
1082-
return false;
1083-
}
1084-
},
1065+
/** ****************************************************************
1066+
* MSSQL Driver *
1067+
***************************************************************** */
1068+
1069+
/**
1070+
* Use named timezones for date/time conversions via AT TIME ZONE.
1071+
* Defaults to FALSE, meaning that numeric offsets for timezone will be used.
1072+
*
1073+
* @see https://learn.microsoft.com/en-us/sql/t-sql/queries/at-time-zone-transact-sql
1074+
*/
1075+
mssqlUseNamedTimezones: ({ dataSource }: { dataSource: string }) => (
1076+
get(keyByDataSource('CUBEJS_DB_MSSQL_USE_NAMED_TIMEZONES', dataSource))
1077+
// It's true in schema-compiler integration tests
1078+
.default('false')
1079+
.asBool()
1080+
),
10851081

10861082
/** ****************************************************************
10871083
* Databricks Driver *

packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import R from 'ramda';
22
import moment from 'moment-timezone';
33

4-
import { QueryAlias, parseSqlInterval } from '@cubejs-backend/shared';
4+
import { getEnv, QueryAlias, parseSqlInterval } from '@cubejs-backend/shared';
55
import { BaseQuery } from './BaseQuery';
66
import { BaseFilter } from './BaseFilter';
77
import { BaseSegment } from './BaseSegment';
88
import { ParamAllocator } from './ParamAllocator';
9+
import { resolveWindowsTimezone } from './windows-iana';
910

1011
const abbrs = {
1112
EST: 'Eastern Standard Time',
@@ -73,6 +74,14 @@ class MssqlSegment extends BaseSegment {
7374
}
7475

7576
export class MssqlQuery extends BaseQuery {
77+
private readonly useNamedTimezones: boolean;
78+
79+
public constructor(compilers: any, options: any) {
80+
super(compilers, options);
81+
82+
this.useNamedTimezones = getEnv('mssqlUseNamedTimezones', { dataSource: this.dataSource });
83+
}
84+
7685
public newFilter(filter) {
7786
return new MssqlFilter(this, filter);
7887
}
@@ -90,6 +99,11 @@ export class MssqlQuery extends BaseQuery {
9099
}
91100

92101
public convertTz(field) {
102+
if (this.useNamedTimezones) {
103+
const windowsTz = resolveWindowsTimezone(this.timezone);
104+
return `CAST(${field} AT TIME ZONE 'UTC' AT TIME ZONE '${windowsTz}' AS DATETIME2)`;
105+
}
106+
93107
const offset = moment().tz(this.timezone).format('Z');
94108

95109
// 1. Treating the field as UTC (add '+00:00' offset)

packages/cubejs-schema-compiler/src/adapter/MysqlQuery.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export class MysqlQuery extends BaseQuery {
4545
if (this.useNamedTimezones) {
4646
return `CONVERT_TZ(${field}, @@session.time_zone, '${this.timezone}')`;
4747
}
48+
4849
return `CONVERT_TZ(${field}, @@session.time_zone, '${moment().tz(this.timezone).format('Z')}')`;
4950
}
5051

0 commit comments

Comments
 (0)