From 7ba758b325e1c167c00cecd10475b50a9fb42791 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 19 Feb 2024 13:27:19 -0500 Subject: [PATCH] Build an application that can read the legacy record data structure --- Cargo.lock | 1 + emseries/src/date_time_tz.rs | 198 ------------------ fitnesstrax/core/Cargo.toml | 1 + fitnesstrax/core/src/bin/legacy-importer.rs | 219 ++++++++++++++++++++ 4 files changed, 221 insertions(+), 198 deletions(-) delete mode 100644 emseries/src/date_time_tz.rs create mode 100644 fitnesstrax/core/src/bin/legacy-importer.rs diff --git a/Cargo.lock b/Cargo.lock index 723ce30..733973c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,6 +1069,7 @@ dependencies = [ "dimensioned 0.8.0", "emseries", "serde 1.0.193", + "serde_json", "tempfile", ] diff --git a/emseries/src/date_time_tz.rs b/emseries/src/date_time_tz.rs deleted file mode 100644 index 1022767..0000000 --- a/emseries/src/date_time_tz.rs +++ /dev/null @@ -1,198 +0,0 @@ -/* -Copyright 2020-2023, Savanni D'Gerinel - -This file is part of the Luminescent Dreams Tools. - -Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - -Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with Lumeto. If not, see . -*/ - -extern crate chrono; -extern crate chrono_tz; - -use chrono::SecondsFormat; -use chrono_tz::Etc::UTC; -use serde::de::{self, Deserialize, Deserializer, Visitor}; -use serde::ser::{Serialize, Serializer}; -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., -/// "2019-05-15T14:30:00Z US/Central". The to_string method, and serde serialization will -/// produce a string of this format. The parser will accept an RFC3339-only string of the forms -/// "2019-05-15T14:30:00Z", "2019-05-15T14:30:00+00:00", and also an "RFC3339 Timezone Name" -/// string. -/// -/// The function here is to generate as close to unambiguous time/date strings, (for earth's -/// gravitational frame of reference), as possible. Clumping together the time, offset from UTC, -/// and the named time zone allows future parsers to know the exact interpretation of the time in -/// the frame of reference of the original recording. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DateTimeTz(pub chrono::DateTime); - -impl fmt::Display for DateTimeTz { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - if self.0.timezone() == UTC { - write!(f, "{}", self.0.to_rfc3339_opts(SecondsFormat::Secs, true)) - } else { - write!( - f, - "{} {}", - self.0 - .with_timezone(&chrono_tz::Etc::UTC) - .to_rfc3339_opts(SecondsFormat::Secs, true,), - self.0.timezone().name() - ) - } - } -} - -impl DateTimeTz { - pub fn map(&self, f: F) -> DateTimeTz - where - F: FnOnce(chrono::DateTime) -> chrono::DateTime, - { - DateTimeTz(f(self.0)) - } -} - -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(); - chrono::DateTime::parse_from_rfc3339(v[0]).map(|ts| DateTimeTz(ts.with_timezone(&tz))) - } else { - chrono::DateTime::parse_from_rfc3339(v[0]).map(|ts| DateTimeTz(ts.with_timezone(&UTC))) - } - } -} - -impl From> for DateTimeTz { - fn from(dt: chrono::DateTime) -> DateTimeTz { - DateTimeTz(dt) - } -} - -struct DateTimeTzVisitor; - -impl<'de> Visitor<'de> for DateTimeTzVisitor { - type Value = DateTimeTz; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string date time representation that can be parsed") - } - - fn visit_str(self, s: &str) -> Result { - DateTimeTz::from_str(s).or(Err(E::custom( - "string is not a parsable datetime representation".to_owned(), - ))) - } -} - -impl Serialize for DateTimeTz { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for DateTimeTz { - fn deserialize>(deserializer: D) -> Result { - deserializer.deserialize_str(DateTimeTzVisitor) - } -} - -#[cfg(test)] -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 std::str::FromStr; - - #[test] - fn it_creates_timestamp_with_z() { - let t = DateTimeTz(UTC.with_ymd_and_hms(2019, 5, 15, 12, 0, 0).unwrap()); - assert_eq!(t.to_string(), "2019-05-15T12:00:00Z"); - } - - #[test] - fn it_parses_utc_rfc3339_z() { - let t = DateTimeTz::from_str("2019-05-15T12:00:00Z").unwrap(); - assert_eq!( - t, - DateTimeTz(UTC.with_ymd_and_hms(2019, 5, 15, 12, 0, 0).unwrap()) - ); - } - - #[test] - fn it_parses_rfc3339_with_offset() { - let t = DateTimeTz::from_str("2019-05-15T12:00:00-06:00").unwrap(); - assert_eq!( - t, - DateTimeTz(UTC.with_ymd_and_hms(2019, 5, 15, 18, 0, 0).unwrap()) - ); - } - - #[test] - fn it_parses_rfc3339_with_tz() { - let t = DateTimeTz::from_str("2019-06-15T19:00:00Z US/Arizona").unwrap(); - assert_eq!( - t, - DateTimeTz(UTC.with_ymd_and_hms(2019, 6, 15, 19, 0, 0).unwrap()) - ); - assert_eq!( - t, - DateTimeTz(Arizona.with_ymd_and_hms(2019, 6, 15, 12, 0, 0).unwrap()) - ); - assert_eq!( - t, - DateTimeTz(Central.with_ymd_and_hms(2019, 6, 15, 14, 0, 0).unwrap()) - ); - assert_eq!(t.to_string(), "2019-06-15T19:00:00Z US/Arizona"); - } - - #[derive(Serialize)] - struct DemoStruct { - id: String, - dt: DateTimeTz, - } - - // I used Arizona here specifically because large parts of Arizona do not honor DST, and so - // that adds in more ambiguity of the -0700 offset with Pacific time. - #[test] - fn it_json_serializes() { - let t = DateTimeTz::from_str("2019-06-15T19:00:00Z America/Phoenix").unwrap(); - assert_eq!( - serde_json::to_string(&t).unwrap(), - "\"2019-06-15T19:00:00Z America/Phoenix\"" - ); - - let demo = DemoStruct { - id: String::from("abcdefg"), - dt: t, - }; - assert_eq!( - serde_json::to_string(&demo).unwrap(), - "{\"id\":\"abcdefg\",\"dt\":\"2019-06-15T19:00:00Z America/Phoenix\"}" - ); - } - - #[test] - fn it_json_parses() { - let t = - serde_json::from_str::("\"2019-06-15T19:00:00Z America/Phoenix\"").unwrap(); - assert_eq!( - t, - DateTimeTz(Phoenix.with_ymd_and_hms(2019, 6, 15, 12, 0, 0).unwrap()) - ); - } -} diff --git a/fitnesstrax/core/Cargo.toml b/fitnesstrax/core/Cargo.toml index 520b69c..3e7a8ed 100644 --- a/fitnesstrax/core/Cargo.toml +++ b/fitnesstrax/core/Cargo.toml @@ -11,6 +11,7 @@ chrono-tz = { version = "0.8" } dimensioned = { version = "0.8", features = [ "serde" ] } emseries = { path = "../../emseries" } serde = { version = "1", features = [ "derive" ] } +serde_json = { version = "1" } [dev-dependencies] tempfile = "*" diff --git a/fitnesstrax/core/src/bin/legacy-importer.rs b/fitnesstrax/core/src/bin/legacy-importer.rs new file mode 100644 index 0000000..1f29c99 --- /dev/null +++ b/fitnesstrax/core/src/bin/legacy-importer.rs @@ -0,0 +1,219 @@ +/* +Copyright 2024, Savanni D'Gerinel + +This file is part of FitnessTrax. + +FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU +General Public License as published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see . +*/ + +use std::{ + fs::File, + io::{BufRead, BufReader, Read}, + fmt, + str::FromStr, +}; +use chrono::SecondsFormat; +use chrono_tz::Etc::UTC; +use emseries::{Record, Series, Timestamp, RecordId}; +use ft_core::TraxRecord; +use serde::{Deserialize, Serialize, Deserializer, Serializer, de::{self, Visitor}}; + + + +/// 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., +/// "2019-05-15T14:30:00Z US/Central". The to_string method, and serde serialization will +/// produce a string of this format. The parser will accept an RFC3339-only string of the forms +/// "2019-05-15T14:30:00Z", "2019-05-15T14:30:00+00:00", and also an "RFC3339 Timezone Name" +/// string. +/// +/// The function here is to generate as close to unambiguous time/date strings, (for earth's +/// gravitational frame of reference), as possible. Clumping together the time, offset from UTC, +/// and the named time zone allows future parsers to know the exact interpretation of the time in +/// the frame of reference of the original recording. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct DateTimeTz(pub chrono::DateTime); + +impl fmt::Display for DateTimeTz { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + if self.0.timezone() == UTC { + write!(f, "{}", self.0.to_rfc3339_opts(SecondsFormat::Secs, true)) + } else { + write!( + f, + "{} {}", + self.0 + .with_timezone(&chrono_tz::Etc::UTC) + .to_rfc3339_opts(SecondsFormat::Secs, true,), + self.0.timezone().name() + ) + } + } +} + +impl DateTimeTz { + pub fn map(&self, f: F) -> DateTimeTz + where + F: FnOnce(chrono::DateTime) -> chrono::DateTime, + { + DateTimeTz(f(self.0)) + } +} + +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(); + chrono::DateTime::parse_from_rfc3339(v[0]).map(|ts| DateTimeTz(ts.with_timezone(&tz))) + } else { + chrono::DateTime::parse_from_rfc3339(v[0]).map(|ts| DateTimeTz(ts.with_timezone(&UTC))) + } + } +} + +impl From> for DateTimeTz { + fn from(dt: chrono::DateTime) -> DateTimeTz { + DateTimeTz(dt) + } +} + +struct DateTimeTzVisitor; + +impl<'de> Visitor<'de> for DateTimeTzVisitor { + type Value = DateTimeTz; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string date time representation that can be parsed") + } + + fn visit_str(self, s: &str) -> Result { + DateTimeTz::from_str(s).or(Err(E::custom( + "string is not a parsable datetime representation".to_owned(), + ))) + } +} + +impl Serialize for DateTimeTz { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for DateTimeTz { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(DateTimeTzVisitor) + } +} + + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct Steps { + date: DateTimeTz, + steps: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct Weight { + date: DateTimeTz, + weight: f64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum TDActivity { + Cycling, + Running, + Walking, + Swimming, + Rowing, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct TimeDistance { + date: DateTimeTz, + activity: TDActivity, + comments: Option, + distance: Option, + duration: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum SRActivity { + Pushups, + Situps, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct SetRep { + date: DateTimeTz, + activity: SRActivity, + sets: Vec, + comments: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum RDActivity { + MartialArts, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct RepDuration { + date: DateTimeTz, + activity: RDActivity, + sets: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum LegacyData { + Steps(Steps), + Weight(Weight), + TimeDistance(TimeDistance), + SetRep(SetRep), + RepDuration(RepDuration), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct LegacyRecord { + id: RecordId, + data: LegacyData, +} + +fn main() { + let mut args = std::env::args(); + let _ = args.next().unwrap(); + let input_filename = args.next().unwrap(); + println!("input filename: {}", input_filename); + + let input_file = File::open(input_filename).unwrap(); + let mut buf_reader = BufReader::new(input_file); + // let mut contents = String::new(); + // buf_reader.read_(&mut contents).unwrap(); + + let mut count = 0; + + loop { + let mut line = String::new(); + let res = buf_reader.read_line(&mut line); + match res { + Err(err) => { + panic!("failed after {} lines: {:?}", count, err); + } + Ok(0) => std::process::exit(0), + Ok(_) => { + let record = serde_json::from_str::(&line).unwrap(); + println!("[{}] {:?}", count, record); + count += 1; + } + } + } +}