@@ -56,16 +56,43 @@ const evaluatedPatchMeasureExpression = parsedPatchMeasureExpression.keys({
5656} ) ;
5757
5858const id = Joi . string ( ) . regex ( / ^ [ a - z A - Z 0 - 9 _ ] + \. [ a - z A - Z 0 - 9 _ ] + $ / ) ;
59- const timezoneSchema = Joi . string ( ) . custom ( ( value , helpers ) => {
59+ const canonicalTimezone = ( value ) => {
6060 const zone = moment . tz . zone ( value ) ;
61- if ( ! zone ) {
62- return helpers . error ( 'any.invalid' ) ;
61+ if ( zone ) {
62+ // Normalize to the canonical IANA name.
63+ return zone . name ;
64+ }
65+
66+ return null ;
67+ } ;
68+
69+ const timezoneSchema = Joi . string ( ) . custom ( ( value , helpers ) => {
70+ const name = canonicalTimezone ( value ) ;
71+ if ( ! name ) {
72+ return helpers . message ( `{{#label}} must be a valid IANA time zone, got "${ value } "` ) ;
6373 }
6474
65- // Normalize to the canonical IANA name (case-insensitively) .
66- return zone . name ;
75+ // Normalize to the canonical IANA name.
76+ return name ;
6777} , 'timezone' ) ;
6878
79+ /**
80+ * @param {string|undefined } value
81+ * @returns {string|undefined }
82+ */
83+ export const normalizeTimezone = ( value ) => {
84+ if ( ! value ) {
85+ return value ;
86+ }
87+
88+ const name = canonicalTimezone ( value ) ;
89+ if ( ! name ) {
90+ throw new UserError ( `timezone must be a valid IANA time zone, got "${ value } "` ) ;
91+ }
92+
93+ return name ;
94+ } ;
95+
6996// It might be member name, td+granularity or member expression
7097const idOrMemberExpressionName = Joi . string ( ) . regex ( / ^ [ a - z A - Z 0 - 9 _ ] + \. [ a - z A - Z 0 - 9 _ ] + $ | ^ [ a - z A - Z 0 - 9 _ ] + $ | ^ [ a - z A - Z 0 - 9 _ ] + \. [ a - z A - Z 0 - 9 _ ] + \. [ a - z A - Z 0 - 9 _ ] + $ / ) ;
7198const dimensionWithTime = Joi . string ( ) . regex ( / ^ [ a - z A - Z 0 - 9 _ ] + \. [ a - z A - Z 0 - 9 _ ] + ( \. [ a - z A - Z 0 - 9 _ ] + ) ? $ / ) ;
@@ -426,7 +453,7 @@ function normalizeQueryCacheMode(query, cacheMode) {
426453const normalizeQuery = ( query , persistent , cacheMode ) => {
427454 query = normalizeQueryCacheMode ( query , cacheMode ) ;
428455 query . timezone = query . timezone || getEnv ( 'defaultTimezone' ) ;
429- const { error } = querySchema . validate ( query ) ;
456+ const { error, value } = querySchema . validate ( query ) ;
430457 if ( error ) {
431458 throw new UserError ( `Invalid query format: ${ error . message || error . toString ( ) } ` ) ;
432459 }
@@ -444,10 +471,9 @@ const normalizeQuery = (query, persistent, cacheMode) => {
444471 dimension : d . split ( '.' ) . slice ( 0 , 2 ) . join ( '.' ) ,
445472 granularity : d . split ( '.' ) [ 2 ]
446473 } ) ) ;
447- // query.timezone is already validated as a known zone above; normalize it to the
448- // canonical IANA name (moment matches zones case-insensitively).
449- const rawTimezone = query . timezone || 'UTC' ;
450- const timezone = moment . tz . zone ( rawTimezone ) ?. name || rawTimezone ;
474+ // Use the timezone normalized by the schema (canonical IANA name); the raw request
475+ // may carry a different casing.
476+ const timezone = value . timezone || 'UTC' ;
451477
452478 const def = getEnv ( 'dbQueryDefaultLimit' ) <= getEnv ( 'dbQueryLimit' )
453479 ? getEnv ( 'dbQueryDefaultLimit' )
@@ -522,14 +548,15 @@ const queryPreAggregationsSchema = Joi.object().keys({
522548} ) ;
523549
524550const normalizeQueryPreAggregations = ( query , defaultValues ) => {
525- const { error } = queryPreAggregationsSchema . validate ( query ) ;
551+ const { error, value } = queryPreAggregationsSchema . validate ( query ) ;
526552 if ( error ) {
527553 throw new UserError ( `Invalid query format: ${ error . message || error . toString ( ) } ` ) ;
528554 }
529555
556+ // Use timezones normalized by the schema (canonical IANA names).
530557 return {
531558 metadata : query . metadata ,
532- timezones : query . timezones || ( query . timezone && [ query . timezone ] ) || defaultValues ?. timezones || [ 'UTC' ] ,
559+ timezones : value . timezones || ( value . timezone && [ value . timezone ] ) || defaultValues ?. timezones || [ 'UTC' ] ,
533560 preAggregations : query . preAggregations ,
534561 expand : query . expand
535562 } ;
@@ -549,12 +576,13 @@ const queryPreAggregationPreviewSchema = Joi.object().keys({
549576} ) ;
550577
551578const normalizeQueryPreAggregationPreview = ( query ) => {
552- const { error } = queryPreAggregationPreviewSchema . validate ( query ) ;
579+ const { error, value } = queryPreAggregationPreviewSchema . validate ( query ) ;
553580 if ( error ) {
554581 throw new UserError ( `Invalid query format: ${ error . message || error . toString ( ) } ` ) ;
555582 }
556583
557- return query ;
584+ // Use the timezone normalized by the schema (canonical IANA name).
585+ return { ...query , timezone : value . timezone } ;
558586} ;
559587
560588const queryCancelPreAggregationPreviewSchema = Joi . object ( ) . keys ( {
0 commit comments