Skip to content

Commit e707638

Browse files
committed
fix(mssql-driver): Respect timezone for origin in custom granularities
1 parent 2d1b850 commit e707638

File tree

3 files changed

+19
-13
lines changed

3 files changed

+19
-13
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,17 @@ export class ClickHouseQuery extends BaseQuery {
6969
public dateBin(interval: string, source: string, origin: string): string {
7070
// Pass timezone to dateTimeCast to ensure origin is in the same timezone as a source, because ClickHouse aligns
7171
// both timestamps internally to the same timezone before computing the difference, causing an unintended offset.
72-
const alignedOrigin = this.dateTimeCast(`'${origin}'`, this.timezone);
72+
const originAligned = this.dateTimeCast(`'${origin}'`, this.timezone);
7373
const intervalFormatted = this.formatInterval(interval);
7474
const timeUnit = this.diffTimeUnitForInterval(interval);
7575
const beginOfTime = 'fromUnixTimestamp(0)';
7676

7777
return `date_add(${timeUnit},
7878
FLOOR(
79-
date_diff(${timeUnit}, ${alignedOrigin}, ${source}) /
79+
date_diff(${timeUnit}, ${originAligned}, ${source}) /
8080
date_diff(${timeUnit}, ${beginOfTime}, ${beginOfTime} + ${intervalFormatted})
8181
) * date_diff(${timeUnit}, ${beginOfTime}, ${beginOfTime} + ${intervalFormatted}),
82-
${alignedOrigin}
82+
${originAligned}
8383
)`;
8484
}
8585

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,12 @@ export class MssqlQuery extends BaseQuery {
9090
}
9191

9292
public convertTz(field) {
93-
return `TODATETIMEOFFSET(${field}, '${moment().tz(this.timezone).format('Z')}')`;
93+
const offset = moment().tz(this.timezone).format('Z');
94+
95+
// 1. Treating the field as UTC (add '+00:00' offset)
96+
// 2. Switch to target timezone offset
97+
// 3. Cast to DATETIME2 to get naive timestamp in target timezone
98+
return `CAST(SWITCHOFFSET(TODATETIMEOFFSET(${field}, '+00:00'), '${offset}') AS DATETIME2)`;
9499
}
95100

96101
public timeStampCast(value: string) {
@@ -106,21 +111,24 @@ export class MssqlQuery extends BaseQuery {
106111
}
107112

108113
/**
109-
* Returns sql for source expression floored to timestamps aligned with
110-
* intervals relative to origin timestamp point.
111-
* The formula operates with seconds diffs so it won't produce human-expected dates aligned with offset date parts.
114+
* Returns SQL for source expression floored to timestamps aligned with
115+
* intervals relative to the origin timestamp point.
116+
* The formula operates with seconds diffs, so it won't produce human-expected dates aligned with offset date parts.
112117
*/
113118
public dateBin(interval: string, source: string, origin: string): string {
119+
// Both source and origin are now DATETIME2 in the query timezone (naive timestamps),
120+
// ensuring their in the same timezone context for correct date bin calculation.
121+
const originAligned = this.dateTimeCast(`'${origin}'`);
114122
const beginOfTime = this.dateTimeCast('DATEFROMPARTS(1970, 1, 1)');
115123
const timeUnit = this.diffTimeUnitForInterval(interval);
116124

117125
// Need to explicitly cast one argument of floor to float to trigger correct sign logic
118126
return `DATEADD(${timeUnit},
119127
FLOOR(
120-
CAST(DATEDIFF(${timeUnit}, ${this.dateTimeCast(`'${origin}'`)}, ${source}) AS FLOAT) /
128+
CAST(DATEDIFF(${timeUnit}, ${originAligned}, ${source}) AS FLOAT) /
121129
DATEDIFF(${timeUnit}, ${beginOfTime}, ${this.addInterval(beginOfTime, interval)})
122130
) * DATEDIFF(${timeUnit}, ${beginOfTime}, ${this.addInterval(beginOfTime, interval)}),
123-
${this.dateTimeCast(`'${origin}'`)}
131+
${originAligned}
124132
)`;
125133
}
126134

packages/cubejs-schema-compiler/test/integration/mssql/custom-granularities.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,7 @@ describe('Custom Granularities', () => {
158158
{ joinGraph, cubeEvaluator, compiler }
159159
));
160160

161-
/// TODO: fix date bin calculation... for some reason it goes from 2023-12-31T23:00:00.000Z
162-
xit('works with five_minutes_from_utc_origin custom granularity in Europe/Paris timezone', async () => dbRunner.runQueryTest(
161+
it('works with five_minutes_from_utc_origin custom granularity in Europe/Paris timezone', async () => dbRunner.runQueryTest(
163162
{
164163
measures: ['orders.count'],
165164
timeDimensions: [{
@@ -188,8 +187,7 @@ describe('Custom Granularities', () => {
188187
{ joinGraph, cubeEvaluator, compiler }
189188
));
190189

191-
/// TODO: fix date bin calculation... for some reason it goes from 2023-12-31T23:00:00.000Z
192-
xit('works with five_minutes_from_local_origin custom granularity in Europe/Paris timezone', async () => dbRunner.runQueryTest(
190+
it('works with five_minutes_from_local_origin custom granularity in Europe/Paris timezone', async () => dbRunner.runQueryTest(
193191
{
194192
measures: ['orders.count'],
195193
timeDimensions: [{

0 commit comments

Comments
 (0)