From 26bfffb366c01fd8b3ab85312b82f4583c575608 Mon Sep 17 00:00:00 2001 From: Zack Date: Mon, 16 Dec 2024 09:47:10 +0800 Subject: [PATCH] Feat/datetime pretty serde (#350) * feat: add pretty formatting alongside datecelldata * feat: add iana timezone as default * feat: add timezone * fix: test case * feat: add timezone info for datetime serde --- Cargo.lock | 5 +- collab-database/Cargo.toml | 1 + .../fields/type_option/date_type_option.rs | 86 +++++++++++++++---- .../type_option/timestamp_type_option.rs | 10 ++- 4 files changed, 78 insertions(+), 24 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 4a56932d6..035c27811 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,17 +63,52 @@ 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 cell_data = DateCellData::from(cell); - json!(cell_data) + let tz: Tz = self.timezone_id.parse().unwrap_or_default(); + let date_cell = DateCellData::from(cell); + + let dt_start: Option> = date_cell + .timestamp + .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()); + let dt_start_time = dt_start.map(|dt| dt.time().to_string()); + + let dt_end: Option> = date_cell + .end_timestamp + .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()); + let dt_end_time = dt_end.map(|dt| dt.time().to_string()); + + 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, + "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 { @@ -159,10 +194,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, } } @@ -627,11 +666,15 @@ 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, + "timezone": "Etc/UTC", + "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", }) ); } @@ -700,22 +743,27 @@ mod tests { #[test] fn date_cell_to_serde() { - let date_type_option = DateTypeOption::default_utc(); + 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(), "1672531200".into()); + 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", + "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", + "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() ); } } 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 {