diff --git a/emseries/Cargo.lock b/emseries/Cargo.lock index 9d1166f..4435e02 100644 --- a/emseries/Cargo.lock +++ b/emseries/Cargo.lock @@ -36,9 +36,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.6.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" +checksum = "cf9cc2b23599e6d7479755f3594285efb3f74a1bdca7a7374948bc831e23a552" dependencies = [ "chrono", "chrono-tz-build", @@ -48,9 +48,9 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.0.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" +checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751" dependencies = [ "parse-zoneinfo", "phf", @@ -82,7 +82,6 @@ dependencies = [ "tempfile", "thiserror", "uuid", - "yaml-rust", ] [[package]] @@ -135,12 +134,6 @@ version = "0.2.124" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" -[[package]] -name = "linked-hash-map" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" - [[package]] name = "num-integer" version = "0.1.44" @@ -171,18 +164,18 @@ dependencies = [ [[package]] name = "phf" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" dependencies = [ "phf_generator", "phf_shared", @@ -190,9 +183,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" dependencies = [ "phf_shared", "rand", @@ -200,20 +193,13 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" dependencies = [ "siphasher", - "uncased", ] -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" version = "1.0.37" @@ -238,18 +224,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", "rand_core", ] @@ -258,9 +232,6 @@ name = "rand_core" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] [[package]] name = "redox_syscall" @@ -392,18 +363,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "uncased" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" -dependencies = [ - "version_check", -] +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-xid" @@ -421,12 +383,6 @@ dependencies = [ "serde", ] -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" @@ -454,12 +410,3 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/emseries/Cargo.toml b/emseries/Cargo.toml index c9971fb..b92e660 100644 --- a/emseries/Cargo.toml +++ b/emseries/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "emseries" -version = "0.5.1" +version = "0.6.0" authors = ["Savanni D'Gerinel "] description = "an Embedded Time Series database" license = "GPL-3.0-only" -license-file = "../COPYING" +# license-file = "../COPYING" documentation = "https://docs.rs/emseries" homepage = "https://github.com/luminescent-dreams/emseries" repository = "https://github.com/luminescent-dreams/emseries" @@ -18,14 +18,13 @@ include = [ [dependencies] chrono = { version = "0.4", features = ["serde"] } -chrono-tz = { version = "0.6", features = ["serde"] } -dimensioned = { version = "0.7", features = ["serde"] } +chrono-tz = { version = "0.8", features = ["serde"] } serde = "1" serde_derive = "1" serde_json = "1.0" thiserror = "1.0" uuid = { version = "0.8", features = ["v4", "serde"] } -yaml-rust = "0.4" [dev-dependencies] tempfile = "3.1" +dimensioned = { version = "0.7", features = ["serde"] } diff --git a/emseries/fixtures/weight.json b/emseries/fixtures/weight.json deleted file mode 100644 index 8548963..0000000 --- a/emseries/fixtures/weight.json +++ /dev/null @@ -1,2 +0,0 @@ -{"data":{"weight":77.79109,"date":"2003-11-10T06:00:00.000000000000Z"},"id":"3330c5b0-783f-4919-b2c4-8169c38f65ff"} -{"data":{"weight":77.56429,"date":"2003-11-11T06:00:00.000000000000Z"},"id":"54c10502-030e-43d2-9ca6-df2f9a5a5ddf"} diff --git a/emseries/src/criteria.rs b/emseries/src/criteria.rs index 1a40c0f..fab7945 100644 --- a/emseries/src/criteria.rs +++ b/emseries/src/criteria.rs @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with Lum */ use date_time_tz::DateTimeTz; -use types::Recordable; +use types::{Recordable, Timestamp}; /// This trait is used for constructing queries for searching the database. pub trait Criteria { @@ -45,7 +45,7 @@ pub struct Or { /// Specify the starting time for a search. This consists of a UTC timestamp and a specifier as to /// whether the exact time is included in the search criteria. pub struct StartTime { - pub time: DateTimeTz, + pub time: Timestamp, pub incl: bool, } @@ -62,7 +62,7 @@ impl Criteria for StartTime { /// Specify the ending time for a search. This consists of a UTC timestamp and a specifier as to /// whether the exact time is included in the search criteria. pub struct EndTime { - pub time: DateTimeTz, + pub time: Timestamp, pub incl: bool, } @@ -89,7 +89,7 @@ impl Criteria for Tags { } /// Specify a criteria that searches for records matching an exact time. -pub fn exact_time(time: DateTimeTz) -> And { +pub fn exact_time(time: Timestamp) -> And { And { lside: StartTime { time: time.clone(), @@ -101,9 +101,9 @@ pub fn exact_time(time: DateTimeTz) -> And { /// Specify a criteria that searches for all records within a time range. pub fn time_range( - start: DateTimeTz, + start: Timestamp, start_incl: bool, - end: DateTimeTz, + end: Timestamp, end_incl: bool, ) -> And { And { diff --git a/emseries/src/date_time_tz.rs b/emseries/src/date_time_tz.rs index 287e2e1..2ef3607 100644 --- a/emseries/src/date_time_tz.rs +++ b/emseries/src/date_time_tz.rs @@ -17,7 +17,7 @@ use chrono::SecondsFormat; use chrono_tz::Etc::UTC; use serde::de::{self, Deserialize, Deserializer, Visitor}; use serde::ser::{Serialize, Serializer}; -use std::fmt; +use std::{fmt, str::FromStr}; /// This is a wrapper around date time objects, using timezones from the chroon-tz database and /// providing string representation and parsing of the form " ", i.e., @@ -54,8 +54,12 @@ impl DateTimeTz { ) } } +} - pub fn from_str(s: &str) -> Result { +impl std::str::FromStr for DateTimeTz { + type Err = chrono::ParseError; + + fn from_str(s: &str) -> Result { let v: Vec<&str> = s.split_terminator(" ").collect(); if v.len() == 2 { let tz = v[1].parse::().unwrap(); @@ -66,6 +70,12 @@ impl DateTimeTz { } } +impl From> for DateTimeTz { + fn from(dt: chrono::DateTime) -> DateTimeTz { + DateTimeTz(dt) + } +} + struct DateTimeTzVisitor; impl<'de> Visitor<'de> for DateTimeTzVisitor { @@ -98,11 +108,12 @@ impl<'de> Deserialize<'de> for DateTimeTz { mod test { extern crate serde_json; + use super::*; use chrono::TimeZone; use chrono_tz::America::Phoenix; use chrono_tz::Etc::UTC; use chrono_tz::US::{Arizona, Central}; - use date_time_tz::DateTimeTz; + use std::str::FromStr; #[test] fn it_creates_timestamp_with_z() { diff --git a/emseries/src/lib.rs b/emseries/src/lib.rs index 79d1885..876050a 100644 --- a/emseries/src/lib.rs +++ b/emseries/src/lib.rs @@ -78,4 +78,4 @@ mod types; pub use criteria::*; pub use date_time_tz::DateTimeTz; pub use series::Series; -pub use types::{EmseriesReadError, EmseriesWriteError, Recordable, UniqueId}; +pub use types::{EmseriesReadError, EmseriesWriteError, Recordable, Timestamp, UniqueId}; diff --git a/emseries/src/types.rs b/emseries/src/types.rs index afebcfb..a2a1606 100644 --- a/emseries/src/types.rs +++ b/emseries/src/types.rs @@ -10,10 +10,11 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but You should have received a copy of the GNU General Public License along with Lumeto. If not, see . */ +use chrono::NaiveDate; use date_time_tz::DateTimeTz; use serde::de::DeserializeOwned; use serde::ser::Serialize; -use std::{fmt, io, str}; +use std::{cmp::Ordering, fmt, io, str}; use thiserror::Error; use uuid::Uuid; @@ -43,11 +44,56 @@ pub enum EmseriesWriteError { JSONWriteError(serde_json::error::Error), } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Timestamp { + DateTime(DateTimeTz), + Date(NaiveDate), +} + +impl str::FromStr for Timestamp { + type Err = chrono::ParseError; + fn from_str(line: &str) -> Result { + DateTimeTz::from_str(line) + .map(|dtz| Timestamp::DateTime(dtz)) + .or(NaiveDate::from_str(line).map(|d| Timestamp::Date(d))) + } +} + +impl PartialOrd for Timestamp { + fn partial_cmp(&self, other: &Timestamp) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Timestamp { + fn cmp(&self, other: &Timestamp) -> Ordering { + match (self, other) { + (Timestamp::DateTime(dt1), Timestamp::DateTime(dt2)) => dt1.cmp(dt2), + (Timestamp::DateTime(dt1), Timestamp::Date(dt2)) => dt1.0.date().naive_utc().cmp(&dt2), + (Timestamp::Date(dt1), Timestamp::DateTime(dt2)) => dt1.cmp(&dt2.0.date().naive_utc()), + (Timestamp::Date(dt1), Timestamp::Date(dt2)) => dt1.cmp(dt2), + } + } +} + +impl From for Timestamp { + fn from(d: DateTimeTz) -> Self { + Self::DateTime(d) + } +} + +impl From for Timestamp { + fn from(d: NaiveDate) -> Self { + Self::Date(d) + } +} + /// Any element to be put into the database needs to be Recordable. This is the common API that /// will aid in searching and later in indexing records. pub trait Recordable { /// The timestamp for the record. - fn timestamp(&self) -> DateTimeTz; + fn timestamp(&self) -> Timestamp; /// A list of string tags that can be used for indexing. This list defined per-type. fn tags(&self) -> Vec; @@ -110,10 +156,9 @@ mod test { extern crate serde_json; use self::dimensioned::si::{Kilogram, KG}; - use super::{Record, Recordable}; + use super::*; use chrono::TimeZone; - use chrono_tz::Etc::UTC; - use chrono_tz::US::Central; + use chrono_tz::{Etc::UTC, US::Central}; use date_time_tz::DateTimeTz; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] @@ -121,12 +166,12 @@ mod test { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct WeightRecord { - pub date: DateTimeTz, + pub date: Timestamp, pub weight: Weight, } impl Recordable for WeightRecord { - fn timestamp(&self) -> DateTimeTz { + fn timestamp(&self) -> Timestamp { self.date.clone() } @@ -135,10 +180,26 @@ mod test { } } - const WEIGHT_ENTRY: &str = "{\"data\":{\"weight\":77.79109,\"date\":\"2003-11-10T06:00:00.000000000000Z\"},\"id\":\"3330c5b0-783f-4919-b2c4-8169c38f65ff\"}"; + #[test] + fn timestamp_parses_datetimetz_without_timezone() { + assert_eq!( + "2003-11-10T06:00:00Z".parse::().unwrap(), + Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))), + ); + } #[test] - pub fn legacy_deserialization() { + fn timestamp_parses_date() { + assert_eq!( + "2023-11-10".parse::().unwrap(), + Timestamp::Date(NaiveDate::from_ymd_opt(2023, 11, 10).unwrap()) + ); + } + + #[test] + fn v_alpha_serialization() { + const WEIGHT_ENTRY: &str = "{\"data\":{\"weight\":77.79109,\"date\":\"2003-11-10T06:00:00.000000000000Z\"},\"id\":\"3330c5b0-783f-4919-b2c4-8169c38f65ff\"}"; + let rec: Record = WEIGHT_ENTRY .parse() .expect("should successfully parse the record"); @@ -149,16 +210,16 @@ mod test { assert_eq!( rec.data, Some(WeightRecord { - date: DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0)), + date: Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))), weight: Weight(77.79109 * KG), }) ); } #[test] - pub fn serialization_output() { + fn serialization_output() { let rec = WeightRecord { - date: DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0)), + date: Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))), weight: Weight(77.0 * KG), }; assert_eq!( @@ -167,7 +228,7 @@ mod test { ); let rec2 = WeightRecord { - date: DateTimeTz(Central.ymd(2003, 11, 10).and_hms(0, 0, 0)), + date: Timestamp::DateTime(Central.ymd(2003, 11, 10).and_hms(0, 0, 0).into()), weight: Weight(77.0 * KG), }; assert_eq!( @@ -175,4 +236,25 @@ mod test { "{\"date\":\"2003-11-10T06:00:00Z US/Central\",\"weight\":77.0}" ); } + + #[test] + fn two_datetimes_can_be_compared() { + let time1 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))); + let time2 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 11).and_hms(6, 0, 0))); + assert!(time1 < time2); + } + + #[test] + fn two_dates_can_be_compared() { + let time1 = Timestamp::Date(NaiveDate::from_ymd(2003, 11, 10)); + let time2 = Timestamp::Date(NaiveDate::from_ymd(2003, 11, 11)); + assert!(time1 < time2); + } + + #[test] + fn datetime_and_date_can_be_compared() { + let time1 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))); + let time2 = Timestamp::Date(NaiveDate::from_ymd(2003, 11, 11)); + assert!(time1 < time2) + } } diff --git a/emseries/tests/test_io.rs b/emseries/tests/test_io.rs index 4c7aff9..92b1a8b 100644 --- a/emseries/tests/test_io.rs +++ b/emseries/tests/test_io.rs @@ -41,8 +41,8 @@ mod test { } impl Recordable for BikeTrip { - fn timestamp(&self) -> DateTimeTz { - self.datetime.clone() + fn timestamp(&self) -> Timestamp { + self.datetime.clone().into() } fn tags(&self) -> Vec { Vec::new() @@ -122,7 +122,7 @@ mod test { Some(tr) => { assert_eq!( tr.timestamp(), - DateTimeTz(UTC.ymd(2011, 10, 29).and_hms(0, 0, 0)) + DateTimeTz(UTC.ymd(2011, 10, 29).and_hms(0, 0, 0)).into() ); assert_eq!(tr.duration, Duration(11040.0 * S)); assert_eq!(tr.comments, String::from("long time ago")); @@ -144,9 +144,9 @@ mod test { } let v: Vec<(&UniqueId, &BikeTrip)> = ts - .search(exact_time(DateTimeTz( - UTC.ymd(2011, 10, 31).and_hms(0, 0, 0), - ))) + .search(exact_time( + DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(), + )) .collect(); assert_eq!(v.len(), 1); assert_eq!(*v[0].1, trips[1]); @@ -166,9 +166,9 @@ mod test { let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted( time_range( - DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)), + DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(), true, - DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)), + DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).into(), true, ), |l, r| l.1.timestamp().cmp(&r.1.timestamp()), @@ -199,9 +199,9 @@ mod test { .expect("expect the time series to open correctly"); let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted( time_range( - DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)), + DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(), true, - DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)), + DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).into(), true, ), |l, r| l.1.timestamp().cmp(&r.1.timestamp()), @@ -233,9 +233,9 @@ mod test { .expect("expect the time series to open correctly"); let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted( time_range( - DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)), + DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(), true, - DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)), + DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).into(), true, ), |l, r| l.1.timestamp().cmp(&r.1.timestamp()), @@ -252,9 +252,9 @@ mod test { .expect("expect the time series to open correctly"); let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted( time_range( - DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)), + DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(), true, - DateTimeTz(UTC.ymd(2011, 11, 05).and_hms(0, 0, 0)), + DateTimeTz(UTC.ymd(2011, 11, 05).and_hms(0, 0, 0)).into(), true, ), |l, r| l.1.timestamp().cmp(&r.1.timestamp()), @@ -334,9 +334,9 @@ mod test { assert_eq!(trips.len(), 3); let trips: Vec<(&UniqueId, &BikeTrip)> = ts - .search(exact_time(DateTimeTz( - UTC.ymd(2011, 11, 02).and_hms(0, 0, 0), - ))) + .search(exact_time( + DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0)).into(), + )) .collect(); assert_eq!(trips.len(), 1); assert_eq!( @@ -382,13 +382,13 @@ mod test { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct WeightRecord { - pub date: DateTimeTz, + pub date: chrono::NaiveDate, pub weight: Weight, } impl Recordable for WeightRecord { - fn timestamp(&self) -> DateTimeTz { - self.date.clone() + fn timestamp(&self) -> Timestamp { + self.date.into() } fn tags(&self) -> Vec { @@ -396,6 +396,7 @@ mod test { } } + /* #[test] pub fn legacy_file_load() { let ts: Series = @@ -410,4 +411,5 @@ mod test { Some(rec) => assert_eq!(rec.weight, Weight(77.79109 * KG)), } } + */ }