mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2025-12-22 22:20:02 +00:00
feat: support set timezone in db (#2992)
* feat: support set timezone in db * chore: fix ci * chore: fix code advice * fix: rename `time_zone` to `timezone`
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1533,6 +1533,7 @@ dependencies = [
|
||||
"common-recordbatch",
|
||||
"common-telemetry",
|
||||
"common-test-util",
|
||||
"common-time",
|
||||
"common-version",
|
||||
"config",
|
||||
"datanode",
|
||||
@@ -1987,6 +1988,7 @@ dependencies = [
|
||||
"chrono-tz 0.8.4",
|
||||
"common-error",
|
||||
"common-macro",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Node running mode, see `standalone.example.toml`.
|
||||
mode = "distributed"
|
||||
# The default timezone of the server
|
||||
# default_timezone = "UTC"
|
||||
|
||||
[heartbeat]
|
||||
# Interval for sending heartbeat task to the Metasrv, 5 seconds by default.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
mode = "standalone"
|
||||
# Whether to enable greptimedb telemetry, true by default.
|
||||
enable_telemetry = true
|
||||
# The default timezone of the server
|
||||
# default_timezone = "UTC"
|
||||
|
||||
# HTTP server options.
|
||||
[http]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -43,6 +43,12 @@ pub enum Error {
|
||||
source: common_meta::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to init default timezone"))]
|
||||
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 { .. }
|
||||
|
||||
@@ -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_timezone;
|
||||
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,8 @@ impl StartCommand {
|
||||
logging::info!("Frontend start command: {:#?}", self);
|
||||
logging::info!("Frontend options: {:#?}", opts);
|
||||
|
||||
set_default_timezone(opts.default_timezone.as_deref()).context(InitTimezoneSnafu)?;
|
||||
|
||||
let meta_client_options = opts.meta_client.as_ref().context(MissingConfigSnafu {
|
||||
msg: "'meta_client'",
|
||||
})?;
|
||||
|
||||
@@ -32,6 +32,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_timezone;
|
||||
use datanode::config::{DatanodeOptions, ProcedureConfig, RegionEngineConfig, StorageConfig};
|
||||
use datanode::datanode::{Datanode, DatanodeBuilder};
|
||||
use file_engine::config::EngineConfig as FileEngineConfig;
|
||||
@@ -51,8 +52,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};
|
||||
@@ -98,6 +99,7 @@ impl SubCommand {
|
||||
pub struct StandaloneOptions {
|
||||
pub mode: Mode,
|
||||
pub enable_telemetry: bool,
|
||||
pub default_timezone: Option<String>,
|
||||
pub http: HttpOptions,
|
||||
pub grpc: GrpcOptions,
|
||||
pub mysql: MysqlOptions,
|
||||
@@ -121,6 +123,7 @@ impl Default for StandaloneOptions {
|
||||
Self {
|
||||
mode: Mode::Standalone,
|
||||
enable_telemetry: true,
|
||||
default_timezone: None,
|
||||
http: HttpOptions::default(),
|
||||
grpc: GrpcOptions::default(),
|
||||
mysql: MysqlOptions::default(),
|
||||
@@ -147,6 +150,7 @@ impl StandaloneOptions {
|
||||
fn frontend_options(self) -> FrontendOptions {
|
||||
FrontendOptions {
|
||||
mode: self.mode,
|
||||
default_timezone: self.default_timezone,
|
||||
http: self.http,
|
||||
grpc: self.grpc,
|
||||
mysql: self.mysql,
|
||||
@@ -369,6 +373,9 @@ impl StartCommand {
|
||||
|
||||
info!("Building standalone instance with {opts:#?}");
|
||||
|
||||
set_default_timezone(opts.frontend.default_timezone.as_deref())
|
||||
.context(InitTimezoneSnafu)?;
|
||||
|
||||
// Ensure the data_home directory exists.
|
||||
fs::create_dir_all(path::Path::new(&opts.data_home)).context(CreateDirSnafu {
|
||||
dir: &opts.data_home,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,7 +20,7 @@ use chrono::{Days, LocalResult, Months, NaiveDateTime, TimeZone as ChronoTimeZon
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::{Error, InvalidDateStrSnafu, Result};
|
||||
use crate::timezone::TimeZone;
|
||||
use crate::timezone::Timezone;
|
||||
use crate::util::{format_utc_datetime, local_datetime_to_utc};
|
||||
use crate::{Date, Interval};
|
||||
|
||||
@@ -110,11 +110,11 @@ impl DateTime {
|
||||
NaiveDateTime::from_timestamp_millis(self.0)
|
||||
}
|
||||
|
||||
pub fn to_chrono_datetime_with_timezone(&self, tz: Option<TimeZone>) -> Option<NaiveDateTime> {
|
||||
pub fn to_chrono_datetime_with_timezone(&self, tz: Option<Timezone>) -> Option<NaiveDateTime> {
|
||||
let datetime = self.to_chrono_datetime();
|
||||
datetime.map(|v| match tz {
|
||||
Some(TimeZone::Offset(offset)) => offset.from_utc_datetime(&v).naive_local(),
|
||||
Some(TimeZone::Named(tz)) => tz.from_utc_datetime(&v).naive_local(),
|
||||
Some(Timezone::Offset(offset)) => offset.from_utc_datetime(&v).naive_local(),
|
||||
Some(Timezone::Named(tz)) => tz.from_utc_datetime(&v).naive_local(),
|
||||
None => Utc.from_utc_datetime(&v).naive_local(),
|
||||
})
|
||||
}
|
||||
@@ -155,10 +155,11 @@ impl DateTime {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::timezone::set_default_timezone;
|
||||
|
||||
#[test]
|
||||
pub fn test_new_date_time() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
set_default_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
assert_eq!("1970-01-01 08:00:00+0800", DateTime::new(0).to_string());
|
||||
assert_eq!("1970-01-01 08:00:01+0800", DateTime::new(1000).to_string());
|
||||
assert_eq!("1970-01-01 07:59:59+0800", DateTime::new(-1000).to_string());
|
||||
@@ -166,7 +167,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn test_parse_from_string() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
set_default_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
let time = "1970-01-01 00:00:00+0800";
|
||||
let dt = DateTime::from_str(time).unwrap();
|
||||
assert_eq!(time, &dt.to_string());
|
||||
@@ -194,7 +195,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_local_date_time() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
set_default_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
assert_eq!(
|
||||
-28800000,
|
||||
DateTime::from_str("1970-01-01 00:00:00").unwrap().val()
|
||||
|
||||
@@ -51,8 +51,8 @@ pub enum Error {
|
||||
#[snafu(display("Timestamp arithmetic overflow, msg: {}", msg))]
|
||||
ArithmeticOverflow { msg: String, location: Location },
|
||||
|
||||
#[snafu(display("Invalid time zone offset: {hours}:{minutes}"))]
|
||||
InvalidTimeZoneOffset {
|
||||
#[snafu(display("Invalid timezone offset: {hours}:{minutes}"))]
|
||||
InvalidTimezoneOffset {
|
||||
hours: i32,
|
||||
minutes: u32,
|
||||
location: Location,
|
||||
@@ -66,8 +66,8 @@ pub enum Error {
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid time zone string {raw}"))]
|
||||
ParseTimeZoneName { raw: String, location: Location },
|
||||
#[snafu(display("Invalid timezone string {raw}"))]
|
||||
ParseTimezoneName { raw: String, location: Location },
|
||||
}
|
||||
|
||||
impl ErrorExt for Error {
|
||||
@@ -75,9 +75,9 @@ impl ErrorExt for Error {
|
||||
match self {
|
||||
Error::ParseDateStr { .. }
|
||||
| Error::ParseTimestamp { .. }
|
||||
| Error::InvalidTimeZoneOffset { .. }
|
||||
| Error::InvalidTimezoneOffset { .. }
|
||||
| Error::ParseOffsetStr { .. }
|
||||
| Error::ParseTimeZoneName { .. } => StatusCode::InvalidArguments,
|
||||
| Error::ParseTimezoneName { .. } => StatusCode::InvalidArguments,
|
||||
Error::TimestampOverflow { .. } => StatusCode::Internal,
|
||||
Error::InvalidDateStr { .. } | Error::ArithmeticOverflow { .. } => {
|
||||
StatusCode::InvalidArguments
|
||||
@@ -96,9 +96,9 @@ impl ErrorExt for Error {
|
||||
| Error::TimestampOverflow { location, .. }
|
||||
| Error::ArithmeticOverflow { location, .. } => Some(*location),
|
||||
Error::ParseDateStr { .. }
|
||||
| Error::InvalidTimeZoneOffset { .. }
|
||||
| Error::InvalidTimezoneOffset { .. }
|
||||
| Error::ParseOffsetStr { .. }
|
||||
| Error::ParseTimeZoneName { .. } => None,
|
||||
| Error::ParseTimezoneName { .. } => None,
|
||||
Error::InvalidDateStr { location, .. } => Some(*location),
|
||||
Error::ParseInterval { location, .. } => Some(*location),
|
||||
}
|
||||
|
||||
@@ -31,4 +31,4 @@ pub use interval::Interval;
|
||||
pub use range::RangeMillis;
|
||||
pub use timestamp::Timestamp;
|
||||
pub use timestamp_millis::TimestampMillis;
|
||||
pub use timezone::TimeZone;
|
||||
pub use timezone::Timezone;
|
||||
|
||||
@@ -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_timezone, Timezone};
|
||||
|
||||
/// Time value, represents the elapsed time since midnight in the unit of `TimeUnit`.
|
||||
#[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)]
|
||||
@@ -109,30 +108,28 @@ impl Time {
|
||||
self.as_formatted_string("%H:%M:%S%.f%z", None)
|
||||
}
|
||||
|
||||
/// Format Time for local timezone.
|
||||
pub fn to_local_string(&self) -> String {
|
||||
/// Format Time for system timeszone.
|
||||
pub fn to_system_tz_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.
|
||||
pub fn to_timezone_aware_string(&self, tz: Option<TimeZone>) -> String {
|
||||
/// When timezone is None, using system timezone by default.
|
||||
pub fn to_timezone_aware_string(&self, tz: Option<Timezone>) -> String {
|
||||
self.as_formatted_string("%H:%M:%S%.f", tz)
|
||||
}
|
||||
|
||||
fn as_formatted_string(self, pattern: &str, timezone: Option<TimeZone>) -> String {
|
||||
fn as_formatted_string(self, pattern: &str, timezone: Option<Timezone>) -> String {
|
||||
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_timezone(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_timezone;
|
||||
|
||||
#[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_timezone(Some("+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_timezone(Some("+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_timezone(Some("+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()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ 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_timezone, Timezone};
|
||||
use crate::util::div_ceil;
|
||||
use crate::{error, Interval};
|
||||
|
||||
/// Timestamp represents the value of units(seconds/milliseconds/microseconds/nanoseconds) elapsed
|
||||
/// since UNIX epoch. The valid value range of [Timestamp] depends on it's unit (all in UTC time zone):
|
||||
/// since UNIX epoch. The valid value range of [Timestamp] depends on it's unit (all in UTC timezone):
|
||||
/// - for [TimeUnit::Second]: [-262144-01-01 00:00:00, +262143-12-31 23:59:59]
|
||||
/// - for [TimeUnit::Millisecond]: [-262144-01-01 00:00:00.000, +262143-12-31 23:59:59.999]
|
||||
/// - for [TimeUnit::Microsecond]: [-262144-01-01 00:00:00.000000, +262143-12-31 23:59:59.999999]
|
||||
@@ -293,26 +293,26 @@ impl Timestamp {
|
||||
self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f%z", None)
|
||||
}
|
||||
|
||||
/// Format timestamp use **system timezone**.
|
||||
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.
|
||||
pub fn to_timezone_aware_string(&self, tz: Option<TimeZone>) -> String {
|
||||
/// If `tz==None`, the server default timezone will used.
|
||||
pub fn to_timezone_aware_string(&self, tz: Option<Timezone>) -> String {
|
||||
self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f", tz)
|
||||
}
|
||||
|
||||
fn as_formatted_string(self, pattern: &str, timezone: Option<TimeZone>) -> String {
|
||||
fn as_formatted_string(self, pattern: &str, timezone: Option<Timezone>) -> String {
|
||||
if let Some(v) = self.to_chrono_datetime() {
|
||||
match timezone {
|
||||
Some(TimeZone::Offset(offset)) => {
|
||||
match get_timezone(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)
|
||||
@@ -324,11 +324,11 @@ impl Timestamp {
|
||||
NaiveDateTime::from_timestamp_opt(sec, nsec)
|
||||
}
|
||||
|
||||
pub fn to_chrono_datetime_with_timezone(&self, tz: Option<TimeZone>) -> Option<NaiveDateTime> {
|
||||
pub fn to_chrono_datetime_with_timezone(&self, tz: Option<Timezone>) -> Option<NaiveDateTime> {
|
||||
let datetime = self.to_chrono_datetime();
|
||||
datetime.map(|v| match tz {
|
||||
Some(TimeZone::Offset(offset)) => offset.from_utc_datetime(&v).naive_local(),
|
||||
Some(TimeZone::Named(tz)) => tz.from_utc_datetime(&v).naive_local(),
|
||||
Some(Timezone::Offset(offset)) => offset.from_utc_datetime(&v).naive_local(),
|
||||
Some(Timezone::Named(tz)) => tz.from_utc_datetime(&v).naive_local(),
|
||||
None => Utc.from_utc_datetime(&v).naive_local(),
|
||||
})
|
||||
}
|
||||
@@ -560,6 +560,7 @@ mod tests {
|
||||
use serde_json::Value;
|
||||
|
||||
use super::*;
|
||||
use crate::timezone::set_default_timezone;
|
||||
|
||||
#[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_timezone(Some("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_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
assert_eq!(
|
||||
"1970-01-01 08:00:01+0800",
|
||||
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Second)) {
|
||||
@@ -1054,7 +1055,7 @@ mod tests {
|
||||
|
||||
// $TZ doesn't take effort.
|
||||
#[test]
|
||||
fn test_parse_in_time_zone() {
|
||||
fn test_parse_in_timezone() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
assert_eq!(
|
||||
Timestamp::new(28800, TimeUnit::Second),
|
||||
@@ -1074,7 +1075,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_to_local_string() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
set_default_timezone(Some("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_timezone(Some("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()))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,24 +15,52 @@
|
||||
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::{
|
||||
InvalidTimeZoneOffsetSnafu, ParseOffsetStrSnafu, ParseTimeZoneNameSnafu, Result,
|
||||
InvalidTimezoneOffsetSnafu, ParseOffsetStrSnafu, ParseTimezoneNameSnafu, Result,
|
||||
};
|
||||
use crate::util::find_tz_from_env;
|
||||
|
||||
/// System timezone in `frontend`/`standalone`,
|
||||
/// config by option `default_timezone` in toml,
|
||||
/// default value is `UTC` when `default_timezone` is not set.
|
||||
static DEFAULT_TIMEZONE: OnceCell<Timezone> = OnceCell::new();
|
||||
|
||||
// Set the System timezone by `tz_str`
|
||||
pub fn set_default_timezone(tz_str: Option<&str>) -> Result<()> {
|
||||
let tz = match tz_str {
|
||||
None | Some("") => Timezone::Named(Tz::UTC),
|
||||
Some(tz) => Timezone::from_tz_string(tz)?,
|
||||
};
|
||||
DEFAULT_TIMEZONE.get_or_init(|| tz);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
/// If the `tz=Some(timezone)`, return `timezone` directly,
|
||||
/// or return current system timezone.
|
||||
pub fn get_timezone(tz: Option<Timezone>) -> Timezone {
|
||||
tz.unwrap_or_else(|| {
|
||||
DEFAULT_TIMEZONE
|
||||
.get()
|
||||
.cloned()
|
||||
.unwrap_or(Timezone::Named(Tz::UTC))
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TimeZone {
|
||||
pub enum Timezone {
|
||||
Offset(FixedOffset),
|
||||
Named(Tz),
|
||||
}
|
||||
|
||||
impl 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<Self> {
|
||||
let offset_secs = if offset_hours > 0 {
|
||||
offset_hours * 3600 + offset_mins as i32 * 60
|
||||
@@ -42,7 +70,7 @@ impl TimeZone {
|
||||
|
||||
FixedOffset::east_opt(offset_secs)
|
||||
.map(Self::Offset)
|
||||
.context(InvalidTimeZoneOffsetSnafu {
|
||||
.context(InvalidTimezoneOffsetSnafu {
|
||||
hours: offset_hours,
|
||||
minutes: offset_mins,
|
||||
})
|
||||
@@ -57,10 +85,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<Option<Self>> {
|
||||
pub fn from_tz_string(tz_string: &str) -> Result<Self> {
|
||||
// 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::<i32>()
|
||||
@@ -68,16 +96,16 @@ impl TimeZone {
|
||||
let mins = mins
|
||||
.parse::<u32>()
|
||||
.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()
|
||||
ParseTimezoneNameSnafu { raw: tz_string }.fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TimeZone {
|
||||
impl Display for Timezone {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Named(tz) => write!(f, "{}", tz.name()),
|
||||
@@ -87,12 +115,9 @@ impl Display for TimeZone {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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()
|
||||
}
|
||||
/// Return current system config timezone, default config is UTC
|
||||
pub fn system_timezone_name() -> String {
|
||||
format!("{}", get_timezone(None))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -101,61 +126,56 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_from_tz_string() {
|
||||
assert_eq!(None, TimeZone::from_tz_string("SYSTEM").unwrap());
|
||||
|
||||
let utc_plus_8 = Some(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()));
|
||||
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(),
|
||||
));
|
||||
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()));
|
||||
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(),
|
||||
));
|
||||
assert_eq!(utc_minus_max, TimeZone::from_tz_string("-13:59").unwrap());
|
||||
|
||||
assert_eq!(
|
||||
Some(TimeZone::Named(Tz::Asia__Shanghai)),
|
||||
TimeZone::from_tz_string("Asia/Shanghai").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Some(TimeZone::Named(Tz::UTC)),
|
||||
TimeZone::from_tz_string("UTC").unwrap()
|
||||
Timezone::Named(Tz::UTC),
|
||||
Timezone::from_tz_string("SYSTEM").unwrap()
|
||||
);
|
||||
|
||||
assert!(TimeZone::from_tz_string("WORLD_PEACE").is_err());
|
||||
assert!(TimeZone::from_tz_string("A0:01").is_err());
|
||||
assert!(TimeZone::from_tz_string("20:0A").is_err());
|
||||
assert!(TimeZone::from_tz_string(":::::").is_err());
|
||||
assert!(TimeZone::from_tz_string("Asia/London").is_err());
|
||||
assert!(TimeZone::from_tz_string("Unknown").is_err());
|
||||
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 = 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 = 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 = Timezone::Offset(FixedOffset::east_opt(3600 * 14).unwrap());
|
||||
assert_eq!(utc_plus_max, Timezone::from_tz_string("14:00").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!(
|
||||
Timezone::Named(Tz::Asia__Shanghai),
|
||||
Timezone::from_tz_string("Asia/Shanghai").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Timezone::Named(Tz::UTC),
|
||||
Timezone::from_tz_string("UTC").unwrap()
|
||||
);
|
||||
|
||||
assert!(Timezone::from_tz_string("WORLD_PEACE").is_err());
|
||||
assert!(Timezone::from_tz_string("A0:01").is_err());
|
||||
assert!(Timezone::from_tz_string("20:0A").is_err());
|
||||
assert!(Timezone::from_tz_string(":::::").is_err());
|
||||
assert!(Timezone::from_tz_string("Asia/London").is_err());
|
||||
assert!(Timezone::from_tz_string("Unknown").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timezone_to_string() {
|
||||
assert_eq!("UTC", TimeZone::Named(Tz::UTC).to_string());
|
||||
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()
|
||||
Timezone::from_tz_string("Asia/Shanghai")
|
||||
.unwrap()
|
||||
.to_string()
|
||||
);
|
||||
|
||||
@@ -14,23 +14,24 @@
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::offset::Local;
|
||||
use chrono::{LocalResult, NaiveDateTime, TimeZone};
|
||||
use chrono_tz::Tz;
|
||||
|
||||
use crate::timezone::get_timezone;
|
||||
|
||||
pub fn format_utc_datetime(utc: &NaiveDateTime, pattern: &str) -> String {
|
||||
if let Some(tz) = find_tz_from_env() {
|
||||
format!("{}", tz.from_utc_datetime(utc).format(pattern))
|
||||
} else {
|
||||
format!("{}", Local.from_utc_datetime(utc).format(pattern))
|
||||
match get_timezone(None) {
|
||||
crate::Timezone::Offset(offset) => {
|
||||
offset.from_utc_datetime(utc).format(pattern).to_string()
|
||||
}
|
||||
crate::Timezone::Named(tz) => tz.from_utc_datetime(utc).format(pattern).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn local_datetime_to_utc(local: &NaiveDateTime) -> LocalResult<NaiveDateTime> {
|
||||
if let Some(tz) = find_tz_from_env() {
|
||||
tz.from_local_datetime(local).map(|x| x.naive_utc())
|
||||
} else {
|
||||
Local.from_local_datetime(local).map(|x| x.naive_utc())
|
||||
match get_timezone(None) {
|
||||
crate::Timezone::Offset(offset) => offset.from_local_datetime(local).map(|x| x.naive_utc()),
|
||||
crate::Timezone::Named(tz) => tz.from_local_datetime(local).map(|x| x.naive_utc()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,11 +120,13 @@ define_time_with_unit!(Nanosecond, i64);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use common_time::timezone::set_default_timezone;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_to_serde_json_value() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
set_default_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
let time = TimeSecond::new(123);
|
||||
let val = serde_json::Value::from(time);
|
||||
match val {
|
||||
|
||||
@@ -122,11 +122,13 @@ define_timestamp_with_unit!(Nanosecond);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use common_time::timezone::set_default_timezone;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_to_serde_json_value() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
set_default_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
let ts = TimestampSecond::new(123);
|
||||
let val = serde_json::Value::from(ts);
|
||||
match val {
|
||||
|
||||
@@ -176,6 +176,7 @@ mod tests {
|
||||
|
||||
use common_base::bytes::StringBytes;
|
||||
use common_time::time::Time;
|
||||
use common_time::timezone::set_default_timezone;
|
||||
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_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
// non-strict mode
|
||||
let cast_option = CastOption { strict: false };
|
||||
let src_value = Value::Int8(-1);
|
||||
|
||||
@@ -101,6 +101,7 @@ impl LogicalPrimitiveType for DateType {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use common_base::bytes::StringBytes;
|
||||
use common_time::timezone::set_default_timezone;
|
||||
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_timezone(Some("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();
|
||||
|
||||
@@ -101,6 +101,7 @@ impl LogicalPrimitiveType for DateTimeType {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use common_time::timezone::set_default_timezone;
|
||||
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_timezone(Some("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!(
|
||||
|
||||
@@ -203,6 +203,7 @@ impl_data_type_for_timestamp!(Microsecond);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use common_time::timezone::set_default_timezone;
|
||||
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_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
// String -> TimestampSecond
|
||||
let s = Value::String("2021-01-01 01:02:03".to_string().into());
|
||||
let ts = ConcreteDataType::timestamp_second_datatype()
|
||||
|
||||
@@ -1190,6 +1190,7 @@ impl<'a> ValueRef<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use arrow::datatypes::DataType as ArrowDataType;
|
||||
use common_time::timezone::set_default_timezone;
|
||||
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_timezone(Some("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");
|
||||
|
||||
@@ -26,6 +26,7 @@ mod tests {
|
||||
|
||||
use arrow::array::{Array, PrimitiveArray};
|
||||
use arrow_array::ArrayRef;
|
||||
use common_time::timezone::set_default_timezone;
|
||||
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_timezone(Some("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());
|
||||
|
||||
@@ -32,6 +32,7 @@ use crate::service_config::{
|
||||
pub struct FrontendOptions {
|
||||
pub mode: Mode,
|
||||
pub node_id: Option<String>,
|
||||
pub default_timezone: Option<String>,
|
||||
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_timezone: None,
|
||||
heartbeat: HeartbeatOptions::frontend_default(),
|
||||
http: HttpOptions::default(),
|
||||
grpc: GrpcOptions::default(),
|
||||
|
||||
@@ -21,8 +21,8 @@ use std::sync::Arc;
|
||||
|
||||
use common_query::Output;
|
||||
use common_recordbatch::RecordBatches;
|
||||
use common_time::timezone::system_time_zone_name;
|
||||
use common_time::TimeZone;
|
||||
use common_time::timezone::system_timezone_name;
|
||||
use common_time::Timezone;
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use datatypes::schema::{ColumnSchema, Schema};
|
||||
use datatypes::vectors::StringVector;
|
||||
@@ -55,7 +55,7 @@ static SELECT_TIME_DIFF_FUNC_PATTERN: Lazy<Regex> =
|
||||
static SHOW_SQL_MODE_PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES LIKE 'sql_mode'(.*))").unwrap());
|
||||
|
||||
// Time zone settings
|
||||
// Timezone settings
|
||||
static SET_TIME_ZONE_PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"(?i)^SET TIME_ZONE\s*=\s*'(\S+)'").unwrap());
|
||||
|
||||
@@ -200,11 +200,8 @@ fn select_variable(query: &str, query_context: QueryContextRef) -> Option<Output
|
||||
|
||||
// get value of variables from known sources or fallback to defaults
|
||||
let value = match var_as[0] {
|
||||
"time_zone" => query_context
|
||||
.time_zone()
|
||||
.map(|tz| tz.to_string())
|
||||
.unwrap_or_else(|| "".to_owned()),
|
||||
"system_time_zone" => system_time_zone_name(),
|
||||
"time_zone" => query_context.timezone().to_string(),
|
||||
"system_time_zone" => system_timezone_name(),
|
||||
_ => VAR_VALUES
|
||||
.get(var_as[0])
|
||||
.map(|v| v.to_string())
|
||||
@@ -271,8 +268,8 @@ fn check_set_variables(query: &str, session: SessionRef) -> Option<Output> {
|
||||
if let Some(captures) = SET_TIME_ZONE_PATTERN.captures(query) {
|
||||
// get the capture
|
||||
let tz = captures.get(1).unwrap();
|
||||
if let Ok(timezone) = TimeZone::from_tz_string(tz.as_str()) {
|
||||
session.set_time_zone(timezone);
|
||||
if let Ok(timezone) = Timezone::from_tz_string(tz.as_str()) {
|
||||
session.set_timezone(timezone);
|
||||
return Some(Output::AffectedRows(0));
|
||||
}
|
||||
}
|
||||
@@ -331,6 +328,7 @@ fn get_version() -> String {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use common_time::timezone::set_default_timezone;
|
||||
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_timezone(Some("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";
|
||||
@@ -437,8 +435,17 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_time_zone() {
|
||||
fn test_set_timezone() {
|
||||
// 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.timezone().to_string());
|
||||
}
|
||||
set_default_timezone(Some("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.timezone().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.timezone().to_string());
|
||||
|
||||
let output = check("select @@time_zone", query_context.clone(), session.clone());
|
||||
match output.unwrap() {
|
||||
|
||||
@@ -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.timezone())),
|
||||
)?,
|
||||
Value::Timestamp(v) => row_writer.write_col(
|
||||
v.to_chrono_datetime_with_timezone(Some(query_context.timezone())),
|
||||
)?,
|
||||
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.timezone())))?,
|
||||
Value::Decimal128(v) => row_writer.write_col(v.to_string())?,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ 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;
|
||||
use common_time::timezone::get_timezone;
|
||||
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<Option<UserInfoRef>>,
|
||||
time_zone: Option<TimeZone>,
|
||||
timezone: Timezone,
|
||||
sql_dialect: Box<dyn Dialect + Send + Sync>,
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
timezone: get_timezone(None),
|
||||
sql_dialect: Box::new(GreptimeDbDialect {}),
|
||||
}
|
||||
}
|
||||
@@ -115,8 +116,8 @@ impl QueryContext {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn time_zone(&self) -> Option<TimeZone> {
|
||||
self.time_zone.clone()
|
||||
pub fn timezone(&self) -> Timezone {
|
||||
self.timezone.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -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),
|
||||
timezone: self.timezone.unwrap_or(get_timezone(None)),
|
||||
sql_dialect: self
|
||||
.sql_dialect
|
||||
.unwrap_or_else(|| Box::new(GreptimeDbDialect {})),
|
||||
|
||||
@@ -21,7 +21,8 @@ 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;
|
||||
use common_time::timezone::get_timezone;
|
||||
use common_time::Timezone;
|
||||
use context::QueryContextBuilder;
|
||||
|
||||
use crate::context::{Channel, ConnInfo, QueryContextRef};
|
||||
@@ -33,7 +34,7 @@ pub struct Session {
|
||||
schema: ArcSwap<String>,
|
||||
user_info: ArcSwap<UserInfoRef>,
|
||||
conn_info: ConnInfo,
|
||||
time_zone: ArcSwap<Option<TimeZone>>,
|
||||
timezone: ArcSwap<Timezone>,
|
||||
}
|
||||
|
||||
pub type SessionRef = Arc<Session>;
|
||||
@@ -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)),
|
||||
timezone: ArcSwap::new(Arc::new(get_timezone(None))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ impl Session {
|
||||
.current_catalog(self.catalog.load().to_string())
|
||||
.current_schema(self.schema.load().to_string())
|
||||
.sql_dialect(self.conn_info.channel.dialect())
|
||||
.time_zone((**self.time_zone.load()).clone())
|
||||
.timezone((**self.timezone.load()).clone())
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -73,13 +74,13 @@ impl Session {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn time_zone(&self) -> Option<TimeZone> {
|
||||
self.time_zone.load().as_ref().clone()
|
||||
pub fn timezone(&self) -> Timezone {
|
||||
self.timezone.load().as_ref().clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_time_zone(&self, tz: Option<TimeZone>) {
|
||||
let _ = self.time_zone.swap(Arc::new(tz));
|
||||
pub fn set_timezone(&self, tz: Timezone) {
|
||||
let _ = self.timezone.swap(Arc::new(tz));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -521,6 +521,7 @@ mod tests {
|
||||
|
||||
use api::v1::ColumnDataType;
|
||||
use common_time::timestamp::TimeUnit;
|
||||
use common_time::timezone::set_default_timezone;
|
||||
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_timezone(Some("Asia/Shanghai")).unwrap();
|
||||
let value = sql_value_to_value(
|
||||
"datetime_col",
|
||||
&ConcreteDataType::datetime_datatype(),
|
||||
|
||||
@@ -219,8 +219,8 @@ pub async fn test_mysql_timezone(store_type: StorageType) {
|
||||
.unwrap();
|
||||
|
||||
let _ = conn.execute("SET time_zone = 'UTC'").await.unwrap();
|
||||
let time_zone = conn.fetch_all("SELECT @@time_zone").await.unwrap();
|
||||
assert_eq!(time_zone[0].get::<String, usize>(0), "UTC");
|
||||
let timezone = conn.fetch_all("SELECT @@time_zone").await.unwrap();
|
||||
assert_eq!(timezone[0].get::<String, usize>(0), "UTC");
|
||||
|
||||
// test data
|
||||
let _ = conn
|
||||
|
||||
Reference in New Issue
Block a user