diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs index fe278a0e1edc0..a3367dd96ce22 100644 --- a/datafusion/sql/src/unparser/dialect.rs +++ b/datafusion/sql/src/unparser/dialect.rs @@ -635,6 +635,18 @@ impl Dialect for BigQueryDialect { fn unnest_as_table_factor(&self) -> bool { true } + + fn timestamp_with_tz_to_string(&self, dt: DateTime, unit: TimeUnit) -> String { + // https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/data-types#timestamp_type + let format = match unit { + TimeUnit::Second => "%Y-%m-%d %H:%M:%S%:z", + TimeUnit::Millisecond => "%Y-%m-%d %H:%M:%S%.3f%:z", + TimeUnit::Microsecond => "%Y-%m-%d %H:%M:%S%.6f%:z", + TimeUnit::Nanosecond => "%Y-%m-%d %H:%M:%S%.9f%:z", + }; + + dt.format(format).to_string() + } } impl BigQueryDialect { diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index 54c8eeb1252d9..d34d0d980ebee 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -1860,8 +1860,9 @@ mod tests { use sqlparser::ast::ExactNumberInfo; use crate::unparser::dialect::{ - CharacterLengthStyle, CustomDialect, CustomDialectBuilder, DateFieldExtractStyle, - DefaultDialect, Dialect, DuckDBDialect, PostgreSqlDialect, ScalarFnToSqlHandler, + BigQueryDialect, CharacterLengthStyle, CustomDialect, CustomDialectBuilder, + DateFieldExtractStyle, DefaultDialect, Dialect, DuckDBDialect, PostgreSqlDialect, + ScalarFnToSqlHandler, }; use super::*; @@ -3353,6 +3354,7 @@ mod tests { Arc::new(CustomDialectBuilder::new().build()); let duckdb_dialect: Arc = Arc::new(DuckDBDialect::new()); + let bigquery_dialect: Arc = Arc::new(BigQueryDialect::new()); for (dialect, scalar, expected) in [ ( @@ -3413,6 +3415,36 @@ mod tests { ), "CAST('2025-09-15 11:00:00.123456789+00:00' AS TIMESTAMP)", ), + // BigQuery: should be no space between timestamp and timezone + ( + Arc::clone(&bigquery_dialect), + ScalarValue::TimestampSecond(Some(1757934000), Some("+00:00".into())), + "CAST('2025-09-15 11:00:00+00:00' AS TIMESTAMP)", + ), + ( + Arc::clone(&bigquery_dialect), + ScalarValue::TimestampMillisecond( + Some(1757934000123), + Some("+01:00".into()), + ), + "CAST('2025-09-15 12:00:00.123+01:00' AS TIMESTAMP)", + ), + ( + Arc::clone(&bigquery_dialect), + ScalarValue::TimestampMicrosecond( + Some(1757934000123456), + Some("-01:00".into()), + ), + "CAST('2025-09-15 10:00:00.123456-01:00' AS TIMESTAMP)", + ), + ( + Arc::clone(&bigquery_dialect), + ScalarValue::TimestampNanosecond( + Some(1757934000123456789), + Some("+00:00".into()), + ), + "CAST('2025-09-15 11:00:00.123456789+00:00' AS TIMESTAMP)", + ), ] { let unparser = Unparser::new(dialect.as_ref());