From 5ed149b62aa80b9eba64f2fa38394f5903915f9e Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 11 Dec 2024 06:19:58 +0800 Subject: [PATCH 1/5] feat: add pretty formatting alongside datecelldata --- .../src/fields/type_option/date_type_option.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/collab-database/src/fields/type_option/date_type_option.rs b/collab-database/src/fields/type_option/date_type_option.rs index 4a56932d6..1b191659f 100644 --- a/collab-database/src/fields/type_option/date_type_option.rs +++ b/collab-database/src/fields/type_option/date_type_option.rs @@ -72,8 +72,12 @@ pub struct DateTypeOption { impl TypeOptionCellReader for DateTypeOption { fn json_cell(&self, cell: &Cell) -> Value { - let cell_data = DateCellData::from(cell); - json!(cell_data) + let date_cell = DateCellData::from(cell); + let mut js_val = json!(date_cell); + if let Some(obj) = js_val.as_object_mut() { + obj.insert("pretty".to_string(), json!(self.stringify_cell(cell))); + } + js_val } fn stringify_cell(&self, cell_data: &Cell) -> String { From a41f640810631e2cdf7a916d770ee4c098016ab6 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 11 Dec 2024 12:03:21 +0800 Subject: [PATCH 2/5] feat: add iana timezone as default --- Cargo.lock | 5 +++-- collab-database/Cargo.toml | 1 + .../src/fields/type_option/date_type_option.rs | 6 +++++- .../src/fields/type_option/timestamp_type_option.rs | 10 +++++++--- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d815d4fd1..4388e8d1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -527,6 +527,7 @@ dependencies = [ "fancy-regex", "futures", "getrandom", + "iana-time-zone", "js-sys", "lazy_static", "nanoid", @@ -1197,9 +1198,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", diff --git a/collab-database/Cargo.toml b/collab-database/Cargo.toml index 7e8e4b060..a2f2e7d46 100644 --- a/collab-database/Cargo.toml +++ b/collab-database/Cargo.toml @@ -38,6 +38,7 @@ chrono-tz = "0.10.0" percent-encoding = "2.3.1" sha2 = "0.10.8" base64 = "0.22.1" +iana-time-zone = "0.1.61" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/collab-database/src/fields/type_option/date_type_option.rs b/collab-database/src/fields/type_option/date_type_option.rs index 1b191659f..bfe60706c 100644 --- a/collab-database/src/fields/type_option/date_type_option.rs +++ b/collab-database/src/fields/type_option/date_type_option.rs @@ -163,10 +163,14 @@ impl TypeOptionCellWriter for DateTypeOption { impl DateTypeOption { pub fn new() -> Self { + let timezone_id = iana_time_zone::get_timezone().unwrap_or_else(|err| { + error!("Failed to get local timezone: {}", err); + "Etc/UTC".to_owned() + }); Self { date_format: DateFormat::default(), time_format: TimeFormat::default(), - timezone_id: String::new(), + timezone_id, } } diff --git a/collab-database/src/fields/type_option/timestamp_type_option.rs b/collab-database/src/fields/type_option/timestamp_type_option.rs index 129ba7c3f..f8a5b5b6e 100644 --- a/collab-database/src/fields/type_option/timestamp_type_option.rs +++ b/collab-database/src/fields/type_option/timestamp_type_option.rs @@ -9,7 +9,7 @@ use chrono::{DateTime, Local, Offset, TimeZone}; use chrono_tz::Tz; use collab::util::AnyMapExt; use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::{json, Value}; use std::str::FromStr; use yrs::Any; @@ -26,12 +26,16 @@ pub struct TimestampTypeOption { impl TypeOptionCellReader for TimestampTypeOption { /// Return formated date and time string for the cell fn json_cell(&self, cell: &Cell) -> Value { - TimestampCellData::from(cell) + let mut js_val: serde_json::Value = TimestampCellData::from(cell) .timestamp .and_then(|ts| DateTime::from_timestamp(ts, 0)) .map(|dt| dt.to_rfc3339()) .unwrap_or_default() - .into() + .into(); + if let Some(obj) = js_val.as_object_mut() { + obj.insert("pretty".to_string(), json!(self.stringify_cell(cell))); + } + js_val } fn numeric_cell(&self, _cell: &Cell) -> Option { From 3b36a2dfa489a9db3fd6d2a89a0b1d6e10ba2064 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 11 Dec 2024 18:46:23 +0800 Subject: [PATCH 3/5] feat: add timezone --- .../fields/type_option/date_type_option.rs | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/collab-database/src/fields/type_option/date_type_option.rs b/collab-database/src/fields/type_option/date_type_option.rs index bfe60706c..5d1cc8652 100644 --- a/collab-database/src/fields/type_option/date_type_option.rs +++ b/collab-database/src/fields/type_option/date_type_option.rs @@ -1,7 +1,7 @@ use crate::entity::FieldType; use crate::error::DatabaseError; -use chrono::Timelike; +use chrono::{DateTime, Timelike}; use chrono::{Datelike, Local, TimeZone}; use crate::fields::{ @@ -63,21 +63,53 @@ impl From for TypeOptionData { } } -#[derive(Clone, Debug, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct DateTypeOption { pub date_format: DateFormat, pub time_format: TimeFormat, pub timezone_id: String, } +impl Default for DateTypeOption { + fn default() -> Self { + DateTypeOption::new() + } +} + impl TypeOptionCellReader for DateTypeOption { fn json_cell(&self, cell: &Cell) -> Value { + let tz: Tz = self.timezone_id.parse().unwrap_or_default(); let date_cell = DateCellData::from(cell); - let mut js_val = json!(date_cell); - if let Some(obj) = js_val.as_object_mut() { - obj.insert("pretty".to_string(), json!(self.stringify_cell(cell))); - } - js_val + + let dt_start: Option> = date_cell + .timestamp + .map(|ts| DateTime::from_timestamp(ts, 0).map(|dt| dt.with_timezone(&tz))) + .flatten(); + let dt_start_rfc3339 = dt_start.map(|dt| dt.to_rfc3339()); + let dt_start_datetime = dt_start.map(|dt| dt.to_string()); + let dt_start_date = dt_start.map(|dt| dt.date_naive().to_string()); + let dt_start_time = dt_start.map(|dt| dt.time().to_string()); + + let dt_end: Option> = date_cell + .end_timestamp + .map(|ts| DateTime::from_timestamp(ts, 0).map(|dt| dt.with_timezone(&tz))) + .flatten(); + let dt_end_rfc3339 = dt_end.map(|dt| dt.to_rfc3339()); + let dt_end_datetime = dt_end.map(|dt| dt.to_string()); + let dt_end_date = dt_end.map(|dt| dt.date_naive().to_string()); + let dt_end_time = dt_end.map(|dt| dt.time().to_string()); + + json!({ + "start": dt_start_rfc3339, + "end": dt_end_rfc3339, + + "pretty_start_datetime": dt_start_datetime, + "pretty_start_date": dt_start_date, + "pretty_start_time": dt_start_time, + "pretty_end_datetime": dt_end_datetime, + "pretty_end_date": dt_end_date, + "pretty_end_time": dt_end_time + }) } fn stringify_cell(&self, cell_data: &Cell) -> String { @@ -708,11 +740,11 @@ mod tests { #[test] fn date_cell_to_serde() { - let date_type_option = DateTypeOption::default_utc(); + let date_type_option = DateTypeOption::new(); let cell_writer: Box = Box::new(date_type_option); { let mut cell: Cell = new_cell_builder(FieldType::DateTime); - cell.insert(CELL_DATA.into(), "1672531200".into()); + cell.insert(CELL_DATA.into(), "1675343111".into()); let serde_val = cell_writer.json_cell(&cell); assert_eq!( serde_val, From 26e4a3b98539c2d90b62ed877300b090adeaa364 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 11 Dec 2024 21:16:49 +0800 Subject: [PATCH 4/5] fix: test case --- .../fields/type_option/date_type_option.rs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/collab-database/src/fields/type_option/date_type_option.rs b/collab-database/src/fields/type_option/date_type_option.rs index 5d1cc8652..f29e11464 100644 --- a/collab-database/src/fields/type_option/date_type_option.rs +++ b/collab-database/src/fields/type_option/date_type_option.rs @@ -83,8 +83,7 @@ impl TypeOptionCellReader for DateTypeOption { let dt_start: Option> = date_cell .timestamp - .map(|ts| DateTime::from_timestamp(ts, 0).map(|dt| dt.with_timezone(&tz))) - .flatten(); + .and_then(|ts| DateTime::from_timestamp(ts, 0).map(|dt| dt.with_timezone(&tz))); let dt_start_rfc3339 = dt_start.map(|dt| dt.to_rfc3339()); let dt_start_datetime = dt_start.map(|dt| dt.to_string()); let dt_start_date = dt_start.map(|dt| dt.date_naive().to_string()); @@ -92,8 +91,7 @@ impl TypeOptionCellReader for DateTypeOption { let dt_end: Option> = date_cell .end_timestamp - .map(|ts| DateTime::from_timestamp(ts, 0).map(|dt| dt.with_timezone(&tz))) - .flatten(); + .and_then(|ts| DateTime::from_timestamp(ts, 0).map(|dt| dt.with_timezone(&tz))); let dt_end_rfc3339 = dt_end.map(|dt| dt.to_rfc3339()); let dt_end_datetime = dt_end.map(|dt| dt.to_string()); let dt_end_date = dt_end.map(|dt| dt.date_naive().to_string()); @@ -667,11 +665,14 @@ mod tests { assert_eq!( json_value, json!({ - "timestamp": 1672531200, - "end_timestamp": null, - "include_time": false, - "is_range": false, - "reminder_id": "" + "end": serde_json::Value::Null, + "pretty_end_date": serde_json::Value::Null, + "pretty_end_datetime": serde_json::Value::Null, + "pretty_end_time": serde_json::Value::Null, + "pretty_start_date": "2023-01-01", + "pretty_start_datetime": "2023-01-01 00:00:00 UTC", + "pretty_start_time": "00:00:00", + "start": "2023-01-01T00:00:00+00:00", }) ); } @@ -740,22 +741,26 @@ mod tests { #[test] fn date_cell_to_serde() { - let date_type_option = DateTypeOption::new(); + let mut date_type_option = DateTypeOption::new(); + date_type_option.timezone_id = "Asia/Singapore".to_string(); let cell_writer: Box = Box::new(date_type_option); { let mut cell: Cell = new_cell_builder(FieldType::DateTime); cell.insert(CELL_DATA.into(), "1675343111".into()); + cell.insert("end_timestamp".into(), "1685543121".into()); let serde_val = cell_writer.json_cell(&cell); assert_eq!( serde_val, - serde_json::to_value(DateCellData { - timestamp: Some(1672531200), - end_timestamp: None, - include_time: false, - is_range: false, - reminder_id: "".to_string(), + json!({ + "start": "2023-02-02T21:05:11+08:00", + "end": "2023-05-31T22:25:21+08:00", + "pretty_start_datetime": "2023-02-02 21:05:11 +08", + "pretty_start_date": "2023-02-02", + "pretty_start_time": "21:05:11", + "pretty_end_datetime": "2023-05-31 22:25:21 +08", + "pretty_end_date": "2023-05-31", + "pretty_end_time": "22:25:21", }) - .unwrap() ); } } From e7c34485533b48daacee94f2febc463f70537694 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Thu, 12 Dec 2024 03:39:25 +0800 Subject: [PATCH 5/5] feat: add timezone info for datetime serde --- collab-database/src/fields/type_option/date_type_option.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/collab-database/src/fields/type_option/date_type_option.rs b/collab-database/src/fields/type_option/date_type_option.rs index f29e11464..035c27811 100644 --- a/collab-database/src/fields/type_option/date_type_option.rs +++ b/collab-database/src/fields/type_option/date_type_option.rs @@ -100,6 +100,7 @@ impl TypeOptionCellReader for DateTypeOption { json!({ "start": dt_start_rfc3339, "end": dt_end_rfc3339, + "timezone": tz.to_string(), "pretty_start_datetime": dt_start_datetime, "pretty_start_date": dt_start_date, @@ -666,6 +667,7 @@ mod tests { json_value, json!({ "end": serde_json::Value::Null, + "timezone": "Etc/UTC", "pretty_end_date": serde_json::Value::Null, "pretty_end_datetime": serde_json::Value::Null, "pretty_end_time": serde_json::Value::Null, @@ -753,6 +755,7 @@ mod tests { serde_val, json!({ "start": "2023-02-02T21:05:11+08:00", + "timezone": "Asia/Singapore", "end": "2023-05-31T22:25:21+08:00", "pretty_start_datetime": "2023-02-02 21:05:11 +08", "pretty_start_date": "2023-02-02",