diff --git a/Cargo.lock b/Cargo.lock index 89e77773b2e3..802d09da3393 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1533,6 +1533,7 @@ dependencies = [ "common-recordbatch", "common-telemetry", "common-test-util", + "common-time", "common-version", "config", "datanode", @@ -1984,6 +1985,7 @@ dependencies = [ "chrono-tz 0.8.4", "common-error", "common-macro", + "once_cell", "rand", "serde", "serde_json", diff --git a/config/frontend.example.toml b/config/frontend.example.toml index 37a31ba8799d..83da275afab1 100644 --- a/config/frontend.example.toml +++ b/config/frontend.example.toml @@ -1,5 +1,7 @@ # Node running mode, see `standalone.example.toml`. mode = "distributed" +# The default time zone of the server +# default_time_zone = "UTC" [heartbeat] # Interval for sending heartbeat task to the Metasrv, 5 seconds by default. diff --git a/config/standalone.example.toml b/config/standalone.example.toml index c8930a9646ca..f153916495d9 100644 --- a/config/standalone.example.toml +++ b/config/standalone.example.toml @@ -2,6 +2,8 @@ mode = "standalone" # Whether to enable greptimedb telemetry, true by default. enable_telemetry = true +# The default time zone of the server +# default_time_zone = "UTC" # HTTP server options. [http] diff --git a/src/cmd/Cargo.toml b/src/cmd/Cargo.toml index c28b2982877b..699b699a86c9 100644 --- a/src/cmd/Cargo.toml +++ b/src/cmd/Cargo.toml @@ -32,6 +32,7 @@ common-recordbatch.workspace = true common-telemetry = { workspace = true, features = [ "deadlock_detection", ] } +common-time.workspace = true config = "0.13" datanode.workspace = true datatypes.workspace = true diff --git a/src/cmd/src/error.rs b/src/cmd/src/error.rs index d90afaef2442..2dd46ac48f9c 100644 --- a/src/cmd/src/error.rs +++ b/src/cmd/src/error.rs @@ -43,6 +43,12 @@ pub enum Error { source: common_meta::error::Error, }, + #[snafu(display("Failed to init default time zone"))] + InitTimeZone { + location: Location, + source: common_time::error::Error, + }, + #[snafu(display("Failed to start procedure manager"))] StartProcedureManager { location: Location, @@ -268,6 +274,7 @@ impl ErrorExt for Error { | Error::LoadLayeredConfig { .. } | Error::IllegalConfig { .. } | Error::InvalidReplCommand { .. } + | Error::InitTimeZone { .. } | Error::ConnectEtcd { .. } | Error::NotDataFromOutput { .. } | Error::CreateDir { .. } diff --git a/src/cmd/src/frontend.rs b/src/cmd/src/frontend.rs index b1d12e8844bc..4e9a2c3ba6e7 100644 --- a/src/cmd/src/frontend.rs +++ b/src/cmd/src/frontend.rs @@ -22,6 +22,7 @@ use client::client_manager::DatanodeClients; use common_meta::heartbeat::handler::parse_mailbox_message::ParseMailboxMessageHandler; use common_meta::heartbeat::handler::HandlerGroupExecutor; use common_telemetry::logging; +use common_time::timezone::set_default_time_zone; use frontend::frontend::FrontendOptions; use frontend::heartbeat::handler::invalidate_table_cache::InvalidateTableCacheHandler; use frontend::heartbeat::HeartbeatTask; @@ -32,7 +33,7 @@ use servers::tls::{TlsMode, TlsOption}; use servers::Mode; use snafu::{OptionExt, ResultExt}; -use crate::error::{self, MissingConfigSnafu, Result, StartFrontendSnafu}; +use crate::error::{self, InitTimeZoneSnafu, MissingConfigSnafu, Result, StartFrontendSnafu}; use crate::options::{CliOptions, Options}; use crate::App; @@ -217,6 +218,9 @@ impl StartCommand { logging::info!("Frontend start command: {:#?}", self); logging::info!("Frontend options: {:#?}", opts); + set_default_time_zone(opts.default_time_zone.as_deref().unwrap_or("")) + .context(InitTimeZoneSnafu)?; + let meta_client_options = opts.meta_client.as_ref().context(MissingConfigSnafu { msg: "'meta_client'", })?; diff --git a/src/cmd/src/standalone.rs b/src/cmd/src/standalone.rs index b283dc59fb2e..1d07fca16846 100644 --- a/src/cmd/src/standalone.rs +++ b/src/cmd/src/standalone.rs @@ -31,6 +31,7 @@ use common_meta::wal::{WalOptionsAllocator, WalOptionsAllocatorRef}; use common_procedure::ProcedureManagerRef; use common_telemetry::info; use common_telemetry::logging::LoggingOptions; +use common_time::timezone::set_default_time_zone; use datanode::config::{DatanodeOptions, ProcedureConfig, RegionEngineConfig, StorageConfig}; use datanode::datanode::{Datanode, DatanodeBuilder}; use file_engine::config::EngineConfig as FileEngineConfig; @@ -50,8 +51,8 @@ use servers::Mode; use snafu::ResultExt; use crate::error::{ - CreateDirSnafu, IllegalConfigSnafu, InitDdlManagerSnafu, InitMetadataSnafu, Result, - ShutdownDatanodeSnafu, ShutdownFrontendSnafu, StartDatanodeSnafu, StartFrontendSnafu, + CreateDirSnafu, IllegalConfigSnafu, InitDdlManagerSnafu, InitMetadataSnafu, InitTimeZoneSnafu, + Result, ShutdownDatanodeSnafu, ShutdownFrontendSnafu, StartDatanodeSnafu, StartFrontendSnafu, StartProcedureManagerSnafu, StartWalOptionsAllocatorSnafu, StopProcedureManagerSnafu, }; use crate::options::{CliOptions, MixOptions, Options}; @@ -97,6 +98,7 @@ impl SubCommand { pub struct StandaloneOptions { pub mode: Mode, pub enable_telemetry: bool, + pub default_time_zone: Option, pub http: HttpOptions, pub grpc: GrpcOptions, pub mysql: MysqlOptions, @@ -120,6 +122,7 @@ impl Default for StandaloneOptions { Self { mode: Mode::Standalone, enable_telemetry: true, + default_time_zone: None, http: HttpOptions::default(), grpc: GrpcOptions::default(), mysql: MysqlOptions::default(), @@ -146,6 +149,7 @@ impl StandaloneOptions { fn frontend_options(self) -> FrontendOptions { FrontendOptions { mode: self.mode, + default_time_zone: self.default_time_zone, http: self.http, grpc: self.grpc, mysql: self.mysql, @@ -366,6 +370,9 @@ impl StartCommand { info!("Building standalone instance with {opts:#?}"); + set_default_time_zone(opts.frontend.default_time_zone.as_deref().unwrap_or("")) + .context(InitTimeZoneSnafu)?; + // Ensure the data_home directory exists. fs::create_dir_all(path::Path::new(&opts.data_home)).context(CreateDirSnafu { dir: &opts.data_home, diff --git a/src/common/time/Cargo.toml b/src/common/time/Cargo.toml index 5bdba94a7e9b..04976ebadd45 100644 --- a/src/common/time/Cargo.toml +++ b/src/common/time/Cargo.toml @@ -10,6 +10,7 @@ chrono-tz = "0.8" chrono.workspace = true common-error.workspace = true common-macro.workspace = true +once_cell.workspace = true serde = { version = "1.0", features = ["derive"] } serde_json.workspace = true snafu.workspace = true diff --git a/src/common/time/src/time.rs b/src/common/time/src/time.rs index 8deb03a73d3f..e4a669003794 100644 --- a/src/common/time/src/time.rs +++ b/src/common/time/src/time.rs @@ -19,8 +19,7 @@ use chrono::{NaiveDateTime, NaiveTime, TimeZone as ChronoTimeZone, Utc}; use serde::{Deserialize, Serialize}; use crate::timestamp::TimeUnit; -use crate::timezone::TimeZone; -use crate::util::format_utc_datetime; +use crate::timezone::{get_time_zone, TimeZone}; /// Time value, represents the elapsed time since midnight in the unit of `TimeUnit`. #[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)] @@ -109,13 +108,13 @@ impl Time { self.as_formatted_string("%H:%M:%S%.f%z", None) } - /// Format Time for local timezone. + /// Format Time for server timezone. pub fn to_local_string(&self) -> String { self.as_formatted_string("%H:%M:%S%.f", None) } /// Format Time for given timezone. - /// When timezone is None, using local time by default. + /// When timezone is None, using server time zone by default. pub fn to_timezone_aware_string(&self, tz: Option) -> String { self.as_formatted_string("%H:%M:%S%.f", tz) } @@ -124,15 +123,13 @@ impl Time { if let Some(time) = self.to_chrono_time() { let date = Utc::now().date_naive(); let datetime = NaiveDateTime::new(date, time); - - match timezone { - Some(TimeZone::Offset(offset)) => { + match get_time_zone(timezone) { + TimeZone::Offset(offset) => { format!("{}", offset.from_utc_datetime(&datetime).format(pattern)) } - Some(TimeZone::Named(tz)) => { + TimeZone::Named(tz) => { format!("{}", tz.from_utc_datetime(&datetime).format(pattern)) } - None => format_utc_datetime(&datetime, pattern), } } else { format!("[Time{}: {}]", self.unit, self.value) @@ -223,6 +220,7 @@ mod tests { use serde_json::Value; use super::*; + use crate::timezone::set_default_time_zone; #[test] fn test_time() { @@ -312,33 +310,33 @@ mod tests { #[test] fn test_to_iso8601_string() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("+10:00").unwrap(); let time_millis = 1000001; let ts = Time::new_millisecond(time_millis); - assert_eq!("08:16:40.001+0800", ts.to_iso8601_string()); + assert_eq!("10:16:40.001+1000", ts.to_iso8601_string()); let time_millis = 1000; let ts = Time::new_millisecond(time_millis); - assert_eq!("08:00:01+0800", ts.to_iso8601_string()); + assert_eq!("10:00:01+1000", ts.to_iso8601_string()); let time_millis = 1; let ts = Time::new_millisecond(time_millis); - assert_eq!("08:00:00.001+0800", ts.to_iso8601_string()); + assert_eq!("10:00:00.001+1000", ts.to_iso8601_string()); let time_seconds = 9 * 3600; let ts = Time::new_second(time_seconds); - assert_eq!("17:00:00+0800", ts.to_iso8601_string()); + assert_eq!("19:00:00+1000", ts.to_iso8601_string()); let time_seconds = 23 * 3600; let ts = Time::new_second(time_seconds); - assert_eq!("07:00:00+0800", ts.to_iso8601_string()); + assert_eq!("09:00:00+1000", ts.to_iso8601_string()); } #[test] fn test_serialize_to_json_value() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("+10:00").unwrap(); assert_eq!( - "08:00:01+0800", + "10:00:01+1000", match serde_json::Value::from(Time::new(1, TimeUnit::Second)) { Value::String(s) => s, _ => unreachable!(), @@ -346,7 +344,7 @@ mod tests { ); assert_eq!( - "08:00:00.001+0800", + "10:00:00.001+1000", match serde_json::Value::from(Time::new(1, TimeUnit::Millisecond)) { Value::String(s) => s, _ => unreachable!(), @@ -354,7 +352,7 @@ mod tests { ); assert_eq!( - "08:00:00.000001+0800", + "10:00:00.000001+1000", match serde_json::Value::from(Time::new(1, TimeUnit::Microsecond)) { Value::String(s) => s, _ => unreachable!(), @@ -362,7 +360,7 @@ mod tests { ); assert_eq!( - "08:00:00.000000001+0800", + "10:00:00.000000001+1000", match serde_json::Value::from(Time::new(1, TimeUnit::Nanosecond)) { Value::String(s) => s, _ => unreachable!(), @@ -372,46 +370,47 @@ mod tests { #[test] fn test_to_timezone_aware_string() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("+10:00").unwrap(); assert_eq!( - "08:00:00.001", + "10:00:00.001", Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None) ); + std::env::set_var("TZ", "Asia/Shanghai"); assert_eq!( "08:00:00.001", Time::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("SYSTEM").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("SYSTEM").unwrap())) ); assert_eq!( "08:00:00.001", Time::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("+08:00").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("+08:00").unwrap())) ); assert_eq!( "07:00:00.001", Time::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("+07:00").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("+07:00").unwrap())) ); assert_eq!( "23:00:00.001", Time::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("-01:00").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("-01:00").unwrap())) ); assert_eq!( "08:00:00.001", Time::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("Asia/Shanghai").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("Asia/Shanghai").unwrap())) ); assert_eq!( "00:00:00.001", Time::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("UTC").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("UTC").unwrap())) ); assert_eq!( "03:00:00.001", Time::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("Europe/Moscow").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("Europe/Moscow").unwrap())) ); } } diff --git a/src/common/time/src/timestamp.rs b/src/common/time/src/timestamp.rs index 0e40082378ce..4c09b7231a16 100644 --- a/src/common/time/src/timestamp.rs +++ b/src/common/time/src/timestamp.rs @@ -27,8 +27,8 @@ use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt}; use crate::error::{ArithmeticOverflowSnafu, Error, ParseTimestampSnafu, TimestampOverflowSnafu}; -use crate::timezone::TimeZone; -use crate::util::{div_ceil, format_utc_datetime}; +use crate::timezone::{get_time_zone, TimeZone}; +use crate::util::div_ceil; use crate::{error, Interval}; /// Timestamp represents the value of units(seconds/milliseconds/microseconds/nanoseconds) elapsed @@ -293,26 +293,26 @@ impl Timestamp { self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f%z", None) } + /// Format timestamp use **system time zone**. pub fn to_local_string(&self) -> String { self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f", None) } /// Format timestamp for given timezone. - /// When timezone is None, using local time by default. + /// If `tz==None`, the server default time zone will used. pub fn to_timezone_aware_string(&self, tz: Option) -> String { self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f", tz) } fn as_formatted_string(self, pattern: &str, timezone: Option) -> String { if let Some(v) = self.to_chrono_datetime() { - match timezone { - Some(TimeZone::Offset(offset)) => { + match get_time_zone(timezone) { + TimeZone::Offset(offset) => { format!("{}", offset.from_utc_datetime(&v).format(pattern)) } - Some(TimeZone::Named(tz)) => { + TimeZone::Named(tz) => { format!("{}", tz.from_utc_datetime(&v).format(pattern)) } - None => format_utc_datetime(&v, pattern), } } else { format!("[Timestamp{}: {}]", self.unit, self.value) @@ -560,6 +560,7 @@ mod tests { use serde_json::Value; use super::*; + use crate::timezone::set_default_time_zone; #[test] pub fn test_time_unit() { @@ -789,7 +790,7 @@ mod tests { #[test] fn test_to_iso8601_string() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); let datetime_str = "2020-09-08 13:42:29.042+0000"; let ts = Timestamp::from_str(datetime_str).unwrap(); assert_eq!("2020-09-08 21:42:29.042+0800", ts.to_iso8601_string()); @@ -813,7 +814,7 @@ mod tests { #[test] fn test_serialize_to_json_value() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); assert_eq!( "1970-01-01 08:00:01+0800", match serde_json::Value::from(Timestamp::new(1, TimeUnit::Second)) { @@ -1074,7 +1075,7 @@ mod tests { #[test] fn test_to_local_string() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); assert_eq!( "1970-01-01 08:00:00.000000001", @@ -1107,51 +1108,52 @@ mod tests { #[test] fn test_to_timezone_aware_string() { + set_default_time_zone("Asia/Shanghai").unwrap(); std::env::set_var("TZ", "Asia/Shanghai"); - assert_eq!( "1970-01-01 08:00:00.001", - Timestamp::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None) + Timestamp::new(1, TimeUnit::Millisecond) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("SYSTEM").unwrap())) ); assert_eq!( "1970-01-01 08:00:00.001", Timestamp::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("SYSTEM").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("SYSTEM").unwrap())) ); assert_eq!( "1970-01-01 08:00:00.001", Timestamp::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("+08:00").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("+08:00").unwrap())) ); assert_eq!( "1970-01-01 07:00:00.001", Timestamp::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("+07:00").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("+07:00").unwrap())) ); assert_eq!( "1969-12-31 23:00:00.001", Timestamp::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("-01:00").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("-01:00").unwrap())) ); assert_eq!( "1970-01-01 08:00:00.001", Timestamp::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("Asia/Shanghai").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("Asia/Shanghai").unwrap())) ); assert_eq!( "1970-01-01 00:00:00.001", Timestamp::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("UTC").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("UTC").unwrap())) ); assert_eq!( "1970-01-01 01:00:00.001", Timestamp::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("Europe/Berlin").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("Europe/Berlin").unwrap())) ); assert_eq!( "1970-01-01 03:00:00.001", Timestamp::new(1, TimeUnit::Millisecond) - .to_timezone_aware_string(TimeZone::from_tz_string("Europe/Moscow").unwrap()) + .to_timezone_aware_string(Some(TimeZone::from_tz_string("Europe/Moscow").unwrap())) ); } diff --git a/src/common/time/src/timezone.rs b/src/common/time/src/timezone.rs index 4b1878c15f8c..c87a7e8f2a37 100644 --- a/src/common/time/src/timezone.rs +++ b/src/common/time/src/timezone.rs @@ -15,8 +15,9 @@ use std::fmt::Display; use std::str::FromStr; -use chrono::{FixedOffset, Local, Offset}; +use chrono::FixedOffset; use chrono_tz::Tz; +use once_cell::sync::OnceCell; use snafu::{OptionExt, ResultExt}; use crate::error::{ @@ -24,6 +25,30 @@ use crate::error::{ }; use crate::util::find_tz_from_env; +/// System time zone in `frontend`/`standalone`, +/// config by option `default_time_zone` in toml, +/// default value is `UTC` when `default_time_zone` is not set. +pub static DEFAULT_TIME_ZONE: OnceCell = OnceCell::new(); + +// Set the System time zone by `tz_str` +pub fn set_default_time_zone(tz_str: &str) -> Result<()> { + let tz = if tz_str.is_empty() { + TimeZone::Named(Tz::UTC) + } else { + TimeZone::from_tz_string(tz_str)? + }; + DEFAULT_TIME_ZONE.get_or_init(|| tz); + Ok(()) +} + +#[inline(always)] +/// If the `tz=Some(time_zone)`, return `time_zone` directly, +/// or return current system time zone. +pub fn get_time_zone(tz: Option) -> TimeZone { + tz.or(DEFAULT_TIME_ZONE.get().cloned()) + .unwrap_or(TimeZone::Named(Tz::UTC)) +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum TimeZone { Offset(FixedOffset), @@ -32,7 +57,7 @@ pub enum TimeZone { impl TimeZone { /// Compute timezone from given offset hours and minutes - /// Return `None` if given offset exceeds scope + /// Return `Err` if given offset exceeds scope pub fn hours_mins_opt(offset_hours: i32, offset_mins: u32) -> Result { let offset_secs = if offset_hours > 0 { offset_hours * 3600 + offset_mins as i32 * 60 @@ -57,10 +82,10 @@ impl TimeZone { /// - `SYSTEM` /// - Offset to UTC: `+08:00` , `-11:30` /// - Named zones: `Asia/Shanghai`, `Europe/Berlin` - pub fn from_tz_string(tz_string: &str) -> Result> { + pub fn from_tz_string(tz_string: &str) -> Result { // Use system timezone if tz_string.eq_ignore_ascii_case("SYSTEM") { - Ok(None) + Ok(TimeZone::Named(find_tz_from_env().unwrap_or(Tz::UTC))) } else if let Some((hrs, mins)) = tz_string.split_once(':') { let hrs = hrs .parse::() @@ -68,9 +93,9 @@ impl TimeZone { let mins = mins .parse::() .context(ParseOffsetStrSnafu { raw: tz_string })?; - Self::hours_mins_opt(hrs, mins).map(Some) + Self::hours_mins_opt(hrs, mins) } else if let Ok(tz) = Tz::from_str(tz_string) { - Ok(Some(Self::Named(tz))) + Ok(Self::Named(tz)) } else { ParseTimeZoneNameSnafu { raw: tz_string }.fail() } @@ -87,12 +112,9 @@ impl Display for TimeZone { } #[inline] +/// Return current system config time zone, default config is UTC pub fn system_time_zone_name() -> String { - if let Some(tz) = find_tz_from_env() { - Local::now().with_timezone(&tz).offset().fix().to_string() - } else { - Local::now().offset().to_string() - } + format!("{}", get_time_zone(None)) } #[cfg(test)] @@ -101,36 +123,35 @@ mod tests { #[test] fn test_from_tz_string() { - assert_eq!(None, TimeZone::from_tz_string("SYSTEM").unwrap()); + assert_eq!( + TimeZone::Named(Tz::UTC), + TimeZone::from_tz_string("SYSTEM").unwrap() + ); - let utc_plus_8 = Some(TimeZone::Offset(FixedOffset::east_opt(3600 * 8).unwrap())); + let utc_plus_8 = TimeZone::Offset(FixedOffset::east_opt(3600 * 8).unwrap()); assert_eq!(utc_plus_8, TimeZone::from_tz_string("+8:00").unwrap()); assert_eq!(utc_plus_8, TimeZone::from_tz_string("+08:00").unwrap()); assert_eq!(utc_plus_8, TimeZone::from_tz_string("08:00").unwrap()); - let utc_minus_8 = Some(TimeZone::Offset(FixedOffset::west_opt(3600 * 8).unwrap())); + let utc_minus_8 = TimeZone::Offset(FixedOffset::west_opt(3600 * 8).unwrap()); assert_eq!(utc_minus_8, TimeZone::from_tz_string("-08:00").unwrap()); assert_eq!(utc_minus_8, TimeZone::from_tz_string("-8:00").unwrap()); - let utc_minus_8_5 = Some(TimeZone::Offset( - FixedOffset::west_opt(3600 * 8 + 60 * 30).unwrap(), - )); + let utc_minus_8_5 = TimeZone::Offset(FixedOffset::west_opt(3600 * 8 + 60 * 30).unwrap()); assert_eq!(utc_minus_8_5, TimeZone::from_tz_string("-8:30").unwrap()); - let utc_plus_max = Some(TimeZone::Offset(FixedOffset::east_opt(3600 * 14).unwrap())); + let utc_plus_max = TimeZone::Offset(FixedOffset::east_opt(3600 * 14).unwrap()); assert_eq!(utc_plus_max, TimeZone::from_tz_string("14:00").unwrap()); - let utc_minus_max = Some(TimeZone::Offset( - FixedOffset::west_opt(3600 * 13 + 60 * 59).unwrap(), - )); + let utc_minus_max = TimeZone::Offset(FixedOffset::west_opt(3600 * 13 + 60 * 59).unwrap()); assert_eq!(utc_minus_max, TimeZone::from_tz_string("-13:59").unwrap()); assert_eq!( - Some(TimeZone::Named(Tz::Asia__Shanghai)), + TimeZone::Named(Tz::Asia__Shanghai), TimeZone::from_tz_string("Asia/Shanghai").unwrap() ); assert_eq!( - Some(TimeZone::Named(Tz::UTC)), + TimeZone::Named(Tz::UTC), TimeZone::from_tz_string("UTC").unwrap() ); @@ -147,15 +168,11 @@ mod tests { assert_eq!("UTC", TimeZone::Named(Tz::UTC).to_string()); assert_eq!( "+01:00", - TimeZone::from_tz_string("01:00") - .unwrap() - .unwrap() - .to_string() + TimeZone::from_tz_string("01:00").unwrap().to_string() ); assert_eq!( "Asia/Shanghai", TimeZone::from_tz_string("Asia/Shanghai") - .unwrap() .unwrap() .to_string() ); diff --git a/src/datatypes/src/time.rs b/src/datatypes/src/time.rs index 0612255817c8..8a0748a12a09 100644 --- a/src/datatypes/src/time.rs +++ b/src/datatypes/src/time.rs @@ -120,11 +120,13 @@ define_time_with_unit!(Nanosecond, i64); #[cfg(test)] mod tests { + use common_time::timezone::set_default_time_zone; + use super::*; #[test] fn test_to_serde_json_value() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); let time = TimeSecond::new(123); let val = serde_json::Value::from(time); match val { diff --git a/src/datatypes/src/timestamp.rs b/src/datatypes/src/timestamp.rs index fa07e043665f..eeaa318f93c5 100644 --- a/src/datatypes/src/timestamp.rs +++ b/src/datatypes/src/timestamp.rs @@ -122,11 +122,13 @@ define_timestamp_with_unit!(Nanosecond); #[cfg(test)] mod tests { + use common_time::timezone::set_default_time_zone; + use super::*; #[test] fn test_to_serde_json_value() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); let ts = TimestampSecond::new(123); let val = serde_json::Value::from(ts); match val { diff --git a/src/datatypes/src/types/cast.rs b/src/datatypes/src/types/cast.rs index 299d0d625066..cad541d3664e 100644 --- a/src/datatypes/src/types/cast.rs +++ b/src/datatypes/src/types/cast.rs @@ -176,6 +176,7 @@ mod tests { use common_base::bytes::StringBytes; use common_time::time::Time; + use common_time::timezone::set_default_time_zone; use common_time::{Date, DateTime, Timestamp}; use ordered_float::OrderedFloat; @@ -213,7 +214,7 @@ mod tests { #[test] fn test_cast_with_opt() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); // non-strict mode let cast_option = CastOption { strict: false }; let src_value = Value::Int8(-1); diff --git a/src/datatypes/src/types/date_type.rs b/src/datatypes/src/types/date_type.rs index 89a8889cf8f6..100fa8a48575 100644 --- a/src/datatypes/src/types/date_type.rs +++ b/src/datatypes/src/types/date_type.rs @@ -101,6 +101,7 @@ impl LogicalPrimitiveType for DateType { #[cfg(test)] mod tests { use common_base::bytes::StringBytes; + use common_time::timezone::set_default_time_zone; use common_time::Timestamp; use super::*; @@ -108,7 +109,7 @@ mod tests { // $TZ doesn't take effort #[test] fn test_date_cast() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); // timestamp -> date let ts = Value::Timestamp(Timestamp::from_str("2000-01-01 08:00:01").unwrap()); let date = ConcreteDataType::date_datatype().try_cast(ts).unwrap(); diff --git a/src/datatypes/src/types/datetime_type.rs b/src/datatypes/src/types/datetime_type.rs index abed366264fd..787a3e8a1c63 100644 --- a/src/datatypes/src/types/datetime_type.rs +++ b/src/datatypes/src/types/datetime_type.rs @@ -101,6 +101,7 @@ impl LogicalPrimitiveType for DateTimeType { #[cfg(test)] mod tests { + use common_time::timezone::set_default_time_zone; use common_time::Timestamp; use super::*; @@ -113,7 +114,7 @@ mod tests { assert_eq!(dt, Value::DateTime(DateTime::from(1000))); // cast from String - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); let val = Value::String("1970-01-01 00:00:00+0800".into()); let dt = ConcreteDataType::datetime_datatype().try_cast(val).unwrap(); assert_eq!( diff --git a/src/datatypes/src/types/timestamp_type.rs b/src/datatypes/src/types/timestamp_type.rs index ffd63228b098..0212b4214f08 100644 --- a/src/datatypes/src/types/timestamp_type.rs +++ b/src/datatypes/src/types/timestamp_type.rs @@ -203,6 +203,7 @@ impl_data_type_for_timestamp!(Microsecond); #[cfg(test)] mod tests { + use common_time::timezone::set_default_time_zone; use common_time::{Date, DateTime}; use super::*; @@ -230,7 +231,7 @@ mod tests { // $TZ doesn't take effort #[test] fn test_timestamp_cast() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); // String -> TimestampSecond let s = Value::String("2021-01-01 01:02:03".to_string().into()); let ts = ConcreteDataType::timestamp_second_datatype() diff --git a/src/datatypes/src/value.rs b/src/datatypes/src/value.rs index c198fde9a7b1..51a7e0bc37ab 100644 --- a/src/datatypes/src/value.rs +++ b/src/datatypes/src/value.rs @@ -1190,6 +1190,7 @@ impl<'a> ValueRef<'a> { #[cfg(test)] mod tests { use arrow::datatypes::DataType as ArrowDataType; + use common_time::timezone::set_default_time_zone; use num_traits::Float; use super::*; @@ -1875,7 +1876,7 @@ mod tests { #[test] fn test_display() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); assert_eq!(Value::Null.to_string(), "Null"); assert_eq!(Value::UInt8(8).to_string(), "8"); assert_eq!(Value::UInt16(16).to_string(), "16"); diff --git a/src/datatypes/src/vectors/datetime.rs b/src/datatypes/src/vectors/datetime.rs index 4ae654cc3e98..6bff70e20c6e 100644 --- a/src/datatypes/src/vectors/datetime.rs +++ b/src/datatypes/src/vectors/datetime.rs @@ -26,6 +26,7 @@ mod tests { use arrow::array::{Array, PrimitiveArray}; use arrow_array::ArrayRef; + use common_time::timezone::set_default_time_zone; use common_time::DateTime; use super::*; @@ -37,7 +38,7 @@ mod tests { #[test] fn test_datetime_vector() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); let v = DateTimeVector::new(PrimitiveArray::from(vec![1000, 2000, 3000])); assert_eq!(ConcreteDataType::datetime_datatype(), v.data_type()); assert_eq!(3, v.len()); diff --git a/src/frontend/src/frontend.rs b/src/frontend/src/frontend.rs index eddd0e73a1b6..270b053d7ccf 100644 --- a/src/frontend/src/frontend.rs +++ b/src/frontend/src/frontend.rs @@ -32,6 +32,7 @@ use crate::service_config::{ pub struct FrontendOptions { pub mode: Mode, pub node_id: Option, + pub default_time_zone: Option, pub heartbeat: HeartbeatOptions, pub http: HttpOptions, pub grpc: GrpcOptions, @@ -53,6 +54,7 @@ impl Default for FrontendOptions { Self { mode: Mode::Standalone, node_id: None, + default_time_zone: None, heartbeat: HeartbeatOptions::frontend_default(), http: HttpOptions::default(), grpc: GrpcOptions::default(), diff --git a/src/servers/src/mysql/federated.rs b/src/servers/src/mysql/federated.rs index ca4948d6657e..7eb0d1263bc5 100644 --- a/src/servers/src/mysql/federated.rs +++ b/src/servers/src/mysql/federated.rs @@ -200,10 +200,7 @@ fn select_variable(query: &str, query_context: QueryContextRef) -> Option query_context - .time_zone() - .map(|tz| tz.to_string()) - .unwrap_or_else(|| "".to_owned()), + "time_zone" => query_context.time_zone().to_string(), "system_time_zone" => system_time_zone_name(), _ => VAR_VALUES .get(var_as[0]) @@ -331,6 +328,7 @@ fn get_version() -> String { #[cfg(test)] mod test { + use common_time::timezone::set_default_time_zone; use session::context::{Channel, QueryContext}; use session::Session; @@ -390,16 +388,16 @@ mod test { +-----------------+------------------------+"; test(query, expected); - // set sysstem timezone - std::env::set_var("TZ", "Asia/Shanghai"); + // set system timezone + set_default_time_zone("Asia/Shanghai").unwrap(); // complex variables let query = "/* mysql-connector-java-8.0.17 (Revision: 16a712ddb3f826a1933ab42b0039f7fb9eebc6ec) */SELECT @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@collation_server AS collation_server, @@collation_connection AS collation_connection, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_write_timeout AS net_write_timeout, @@performance_schema AS performance_schema, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@transaction_isolation AS transaction_isolation, @@wait_timeout AS wait_timeout;"; let expected = "\ -+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+-----------+-----------------------+---------------+ -| auto_increment_increment | character_set_client | character_set_connection | character_set_results | character_set_server | collation_server | collation_connection | init_connect | interactive_timeout | license | lower_case_table_names | max_allowed_packet | net_write_timeout | performance_schema | sql_mode | system_time_zone | time_zone | transaction_isolation | wait_timeout; | -+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+-----------+-----------------------+---------------+ -| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 31536000 | 0 | 0 | 134217728 | 31536000 | 0 | 0 | +08:00 | | REPEATABLE-READ | 31536000 | -+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+-----------+-----------------------+---------------+"; ++--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+---------------+ +| auto_increment_increment | character_set_client | character_set_connection | character_set_results | character_set_server | collation_server | collation_connection | init_connect | interactive_timeout | license | lower_case_table_names | max_allowed_packet | net_write_timeout | performance_schema | sql_mode | system_time_zone | time_zone | transaction_isolation | wait_timeout; | ++--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+---------------+ +| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 31536000 | 0 | 0 | 134217728 | 31536000 | 0 | 0 | Asia/Shanghai | Asia/Shanghai | REPEATABLE-READ | 31536000 | ++--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+---------------+"; test(query, expected); let query = "show variables"; @@ -438,7 +436,16 @@ mod test { #[test] fn test_set_time_zone() { + // test default is UTC when no config in greptimedb + { + let session = Arc::new(Session::new(None, Channel::Mysql)); + let query_context = session.new_query_context(); + assert_eq!("UTC", query_context.time_zone().to_string()); + } + set_default_time_zone("Asia/Shanghai").unwrap(); let session = Arc::new(Session::new(None, Channel::Mysql)); + let query_context = session.new_query_context(); + assert_eq!("Asia/Shanghai", query_context.time_zone().to_string()); let output = check( "set time_zone = 'UTC'", QueryContext::arc(), @@ -451,7 +458,7 @@ mod test { _ => unreachable!(), } let query_context = session.new_query_context(); - assert_eq!("UTC", query_context.time_zone().unwrap().to_string()); + assert_eq!("UTC", query_context.time_zone().to_string()); let output = check("select @@time_zone", query_context.clone(), session.clone()); match output.unwrap() { diff --git a/src/servers/src/mysql/writer.rs b/src/servers/src/mysql/writer.rs index 6d92fb3804e6..c3461ab35ed5 100644 --- a/src/servers/src/mysql/writer.rs +++ b/src/servers/src/mysql/writer.rs @@ -193,10 +193,12 @@ impl<'a, W: AsyncWrite + Unpin> MysqlResultWriter<'a, W> { Value::Binary(v) => row_writer.write_col(v.deref())?, Value::Date(v) => row_writer.write_col(v.to_chrono_date())?, // convert datetime and timestamp to timezone of current connection - Value::DateTime(v) => row_writer - .write_col(v.to_chrono_datetime_with_timezone(query_context.time_zone()))?, - Value::Timestamp(v) => row_writer - .write_col(v.to_chrono_datetime_with_timezone(query_context.time_zone()))?, + Value::DateTime(v) => row_writer.write_col( + v.to_chrono_datetime_with_timezone(Some(query_context.time_zone())), + )?, + Value::Timestamp(v) => row_writer.write_col( + v.to_chrono_datetime_with_timezone(Some(query_context.time_zone())), + )?, Value::Interval(v) => row_writer.write_col(v.to_iso8601_string())?, Value::Duration(v) => row_writer.write_col(v.to_std_duration())?, Value::List(_) => { @@ -208,7 +210,7 @@ impl<'a, W: AsyncWrite + Unpin> MysqlResultWriter<'a, W> { }) } Value::Time(v) => row_writer - .write_col(v.to_timezone_aware_string(query_context.time_zone()))?, + .write_col(v.to_timezone_aware_string(Some(query_context.time_zone())))?, Value::Decimal128(v) => row_writer.write_col(v.to_string())?, } } diff --git a/src/session/src/context.rs b/src/session/src/context.rs index bfb8a6036aba..5b7ad7d219de 100644 --- a/src/session/src/context.rs +++ b/src/session/src/context.rs @@ -21,6 +21,7 @@ use arc_swap::ArcSwap; use auth::UserInfoRef; use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; use common_catalog::{build_db_string, parse_catalog_and_schema_from_db_string}; +use common_time::timezone::get_time_zone; use common_time::TimeZone; use derive_builder::Builder; use sql::dialect::{Dialect, GreptimeDbDialect, MySqlDialect, PostgreSqlDialect}; @@ -35,7 +36,7 @@ pub struct QueryContext { current_catalog: String, current_schema: String, current_user: ArcSwap>, - time_zone: Option, + time_zone: TimeZone, sql_dialect: Box, } @@ -57,7 +58,7 @@ impl From<&RegionRequestHeader> for QueryContext { current_catalog: catalog.to_string(), current_schema: schema.to_string(), current_user: Default::default(), - time_zone: Default::default(), + time_zone: get_time_zone(None), sql_dialect: Box::new(GreptimeDbDialect {}), } } @@ -115,7 +116,7 @@ impl QueryContext { } #[inline] - pub fn time_zone(&self) -> Option { + pub fn time_zone(&self) -> TimeZone { self.time_zone.clone() } @@ -142,7 +143,7 @@ impl QueryContextBuilder { current_user: self .current_user .unwrap_or_else(|| ArcSwap::new(Arc::new(None))), - time_zone: self.time_zone.unwrap_or(None), + time_zone: self.time_zone.unwrap_or(get_time_zone(None)), sql_dialect: self .sql_dialect .unwrap_or_else(|| Box::new(GreptimeDbDialect {})), diff --git a/src/session/src/lib.rs b/src/session/src/lib.rs index 2ab4e8c56ee4..eeeb8551b1ac 100644 --- a/src/session/src/lib.rs +++ b/src/session/src/lib.rs @@ -21,6 +21,7 @@ use arc_swap::ArcSwap; use auth::UserInfoRef; use common_catalog::build_db_string; use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; +use common_time::timezone::get_time_zone; use common_time::TimeZone; use context::QueryContextBuilder; @@ -33,7 +34,7 @@ pub struct Session { schema: ArcSwap, user_info: ArcSwap, conn_info: ConnInfo, - time_zone: ArcSwap>, + time_zone: ArcSwap, } pub type SessionRef = Arc; @@ -45,7 +46,7 @@ impl Session { schema: ArcSwap::new(Arc::new(DEFAULT_SCHEMA_NAME.into())), user_info: ArcSwap::new(Arc::new(auth::userinfo_by_name(None))), conn_info: ConnInfo::new(addr, channel), - time_zone: ArcSwap::new(Arc::new(None)), + time_zone: ArcSwap::new(Arc::new(get_time_zone(None))), } } @@ -73,12 +74,12 @@ impl Session { } #[inline] - pub fn time_zone(&self) -> Option { + pub fn time_zone(&self) -> TimeZone { self.time_zone.load().as_ref().clone() } #[inline] - pub fn set_time_zone(&self, tz: Option) { + pub fn set_time_zone(&self, tz: TimeZone) { let _ = self.time_zone.swap(Arc::new(tz)); } diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index be57b39856af..48ba7dda73df 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -521,6 +521,7 @@ mod tests { use api::v1::ColumnDataType; use common_time::timestamp::TimeUnit; + use common_time::timezone::set_default_time_zone; use datatypes::types::BooleanType; use datatypes::value::OrderedFloat; @@ -696,7 +697,7 @@ mod tests { #[test] pub fn test_parse_datetime_literal() { - std::env::set_var("TZ", "Asia/Shanghai"); + set_default_time_zone("Asia/Shanghai").unwrap(); let value = sql_value_to_value( "datetime_col", &ConcreteDataType::datetime_datatype(),