From f62ebef76252c497d3eb89405dde361a74071dd2 Mon Sep 17 00:00:00 2001 From: Abraham Toriz Date: Fri, 16 Jul 2021 12:45:27 -0500 Subject: [PATCH] current time management across commands --- assets/test_list_old_database.db | Bin 0 -> 16384 bytes src/commands.rs | 3 +- src/commands/display.rs | 65 ++++-------------- src/commands/in.rs | 8 +-- src/commands/list.rs | 109 ++++++++++++++++++++++--------- src/commands/month.rs | 10 ++- src/commands/sheet.rs | 5 +- src/commands/today.rs | 6 +- src/commands/week.rs | 6 +- src/commands/yesterday.rs | 13 ++-- src/database.rs | 6 ++ src/formatters/text.rs | 13 ++-- src/lib.rs | 1 + src/main.rs | 18 ++--- src/old.rs | 59 +++++++++++++++++ 15 files changed, 201 insertions(+), 121 deletions(-) create mode 100644 assets/test_list_old_database.db create mode 100644 src/old.rs diff --git a/assets/test_list_old_database.db b/assets/test_list_old_database.db new file mode 100644 index 0000000000000000000000000000000000000000..365fff30adc79d1f76494c9e5d06eb5dd1a5f60b GIT binary patch literal 16384 zcmeI(!EV|x7zc1WL@msgu=fx~%v^nI+}c#~sxmvl|oRWcPxgC#n+?NfH`Dw^!1wR=0EQqAI6ftYII;?r#Xt1;P z@u}PHvJtXx5T(LzTd+H@dyY&00Izz00bZa z0SG_<0uX=z1nxp0OEWo|?xsk$@26Y($pbB8(&e^n(s D)6>AW literal 0 HcmV?d00001 diff --git a/src/commands.rs b/src/commands.rs index 9ff785d..c384f04 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,6 +2,7 @@ use std::convert::TryFrom; use std::io::Write; use clap::ArgMatches; +use chrono::{DateTime, Utc}; use crate::error::Result; use crate::database::Database; @@ -19,5 +20,5 @@ pub mod list; pub trait Command<'a> { type Args: TryFrom<&'a ArgMatches<'a>>; - fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config) -> Result<()>; + fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime) -> Result<()>; } diff --git a/src/commands/display.rs b/src/commands/display.rs index 70d55ac..913d184 100644 --- a/src/commands/display.rs +++ b/src/commands/display.rs @@ -3,17 +3,16 @@ use std::io::Write; use std::str::FromStr; use clap::ArgMatches; -use chrono::{DateTime, Utc, Local, LocalResult, TimeZone}; -use ansi_term::Color::Yellow; +use chrono::{DateTime, Utc}; use regex::Regex; use crate::error::{Result, Error}; -use crate::database::{Database, DBVersion}; +use crate::database::Database; use crate::formatters::Formatter; use crate::config::Config; use crate::timeparse::parse_time; -use crate::models::Entry; use crate::regex::parse_regex; +use crate::old::{entries_or_warning, warn_if_needed}; use super::Command; @@ -21,33 +20,6 @@ use super::Command; // Things that are general to all commands that display in some way // ---------------------------------------------------------------- -fn local_to_utc(t: DateTime) -> Result> { - let local_time = match Local.from_local_datetime(&t.naive_utc()) { - LocalResult::None => return Err(Error::NoneLocalTime(t.naive_utc().to_string())), - LocalResult::Single(t) => t, - LocalResult::Ambiguous(t1, t2) => return Err(Error::AmbiguousLocalTime { - orig: t.naive_utc().to_string(), - t1: t1.naive_local(), - t2: t2.naive_local(), - }), - }; - - Ok(Utc.from_utc_datetime(&local_time.naive_utc())) -} - -fn local_to_utc_vec(entries: Vec) -> Result> { - entries - .into_iter() - .map(|e| { - Ok(Entry { - start: local_to_utc(e.start)?, - end: e.end.map(|t| local_to_utc(t)).transpose()?, - ..e - }) - }) - .collect() -} - pub enum Sheet { All, Full, @@ -70,6 +42,7 @@ pub fn entries_for_display( start: Option>, end: Option>, sheet: Option, db: &mut D, out: &mut O, err: &mut E, format: Formatter, ids: bool, grep: Option, + now: DateTime, ) -> Result<()> where D: Database, @@ -91,30 +64,16 @@ where entries.retain(|e| re.is_match(e.note.as_ref().unwrap_or(&String::new()))); } - let (entries, needs_warning) = if let DBVersion::Timetrap = db.version()? { - // this indicates that times in the database are specified in the - // local time and need to be converted to utc before displaying - - (local_to_utc_vec(entries)?, true) - } else { - (entries, false) - }; + let (entries, needs_warning) = entries_or_warning(entries, db)?; format.print_formatted( entries, out, - Utc::now(), + now, ids, )?; - if needs_warning { - writeln!( - err, - "{} You are using the old timetrap format, it is advised that \ - you update your database using t migrate", - Yellow.bold().paint("[WARNING]"), - )?; - } + warn_if_needed(err, needs_warning)?; Ok(()) } @@ -153,13 +112,13 @@ pub struct DisplayCommand { } impl<'a> Command<'a> for DisplayCommand { type Args = Args; - fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()> + fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime) -> Result<()> where D: Database, O: Write, E: Write, { - entries_for_display(args.start, args.end, args.sheet, db, out, err, args.format, args.ids, args.grep) + entries_for_display(args.start, args.end, args.sheet, db, out, err, args.format, args.ids, args.grep, now) } } @@ -183,7 +142,7 @@ mod tests { let config = Default::default(); std::env::set_var("TZ", "UTC"); - DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config).unwrap(); + DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap(); assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("Timesheet: default Day Start End Duration Notes @@ -222,7 +181,7 @@ mod tests { db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap(); db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap(); - DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config).unwrap(); + DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap(); assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("start,end,note,sheet 2021-06-30T10:10:00Z,,hola,default @@ -245,7 +204,7 @@ mod tests { db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap(); db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("adios".into()), "default".into()).unwrap(); - entries_for_display(None, None, None, &mut db, &mut out, &mut err, Formatter::Csv, true, Some("io".parse().unwrap())).unwrap(); + entries_for_display(None, None, None, &mut db, &mut out, &mut err, Formatter::Csv, true, Some("io".parse().unwrap()), Utc::now()).unwrap(); assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("id,start,end,note,sheet 2,2021-06-30T10:10:00Z,,adios,default diff --git a/src/commands/in.rs b/src/commands/in.rs index 4cf28c7..2934c82 100644 --- a/src/commands/in.rs +++ b/src/commands/in.rs @@ -32,13 +32,13 @@ pub struct InCommand {} impl<'a> Command<'a> for InCommand { type Args = Args; - fn handle(args: Args, db: &mut D, _out: &mut O, _err: &mut E, config: &Config) -> error::Result<()> + fn handle(args: Args, db: &mut D, _out: &mut O, _err: &mut E, config: &Config, now: DateTime) -> error::Result<()> where D: Database, O: Write, E: Write, { - let start = args.at.unwrap_or(Utc::now()); + let start = args.at.unwrap_or(now); let sheet = db.current_sheet()?.unwrap_or("default".into()); let note = if let Some(note) = args.note { note @@ -71,7 +71,7 @@ mod tests { assert!(false, "there are no entries"); - InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default()).unwrap(); + InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), Utc::now()).unwrap(); assert!(false, "there is one entry"); } @@ -89,7 +89,7 @@ mod tests { assert!(false, "there are no entries"); - InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default()).unwrap(); + InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), Utc::now()).unwrap(); assert!(false, "there are still no entries"); assert!(false, "a message is issued to the user warning a running entry in this sheet"); diff --git a/src/commands/list.rs b/src/commands/list.rs index 18b7043..0bcfe29 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::io::Write; use clap::ArgMatches; -use chrono::{Utc, Duration, Local}; +use chrono::{DateTime, Utc, Duration, Local}; use itertools::Itertools; use crate::error::{Error, Result}; @@ -10,6 +10,8 @@ use crate::database::Database; use crate::config::Config; use crate::tabulate::{Tabulate, Col, Align::*}; use crate::formatters::text::format_duration; +use crate::models::Entry; +use crate::old::{entries_or_warning, warn_if_needed}; use super::Command; @@ -33,27 +35,40 @@ pub struct ListCommand {} impl<'a> Command<'a> for ListCommand { type Args = Args; - fn handle(args: Args, db: &mut D, out: &mut O, _err: &mut E, _config: &Config) -> Result<()> + fn handle(args: Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime) -> Result<()> where D: Database, O: Write, E: Write, { // no sheet, list sheets - let now = Utc::now(); - let today = Local::now().date().and_hms(0, 0, 0).with_timezone(&Utc); - let mut sheets = if args.all { + let today = now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc); + let entries = if args.all { db.entries_full(None, None)? } else { db.entries_all_visible(None, None)? }; - let current = db.current_sheet()?.unwrap_or("".into()); - let last = db.last_sheet()?.unwrap_or("".into()); + let (mut entries, needs_warning) = entries_or_warning(entries, db)?; - sheets.sort_unstable_by_key(|e| e.sheet.clone()); + let current = db.current_sheet()?; + let last = db.last_sheet()?; - let sheets: Vec<_> = sheets + // introducte two fake entries to make both current and last show up + if let Some(ref current) = current { + entries.push(Entry { + id: 1, sheet: current.clone(), start: now, end: Some(now), note: None, + }); + } + if let Some(ref last) = last { + entries.push(Entry { + id: 2, sheet: last.clone(), start: now, end: Some(now), note: None, + }); + } + + entries.sort_unstable_by_key(|e| e.sheet.clone()); + + let sheets: Vec<_> = entries .into_iter() .group_by(|e| e.sheet.clone()) .into_iter() @@ -61,15 +76,15 @@ impl<'a> Command<'a> for ListCommand { let entries: Vec<_> = group.into_iter().collect(); ( - if current == key { + if current.as_ref() == Some(&key) { "*".into() } else { - if last == key { "-".into() } else { "".into() } + if last.as_ref() == Some(&key) { "-".into() } else { "".into() } }, key, format_duration( - now - entries.iter().filter(|e| e.end.is_none()).next().map(|e| e.end).flatten().unwrap_or(now) + now - entries.iter().filter(|e| e.end.is_none()).next().map(|e| e.start).unwrap_or(now) ), format_duration( @@ -112,6 +127,8 @@ impl<'a> Command<'a> for ListCommand { out.write_all(tabs.print().as_bytes())?; + warn_if_needed(err, needs_warning)?; + Ok(()) } } @@ -120,6 +137,7 @@ impl<'a> Command<'a> for ListCommand { mod tests { use chrono::{Utc, TimeZone}; use pretty_assertions::assert_eq; + use ansi_term::Color::Yellow; use crate::database::{SqliteDatabase, Database}; use crate::test_utils::PrettyString; @@ -128,6 +146,8 @@ mod tests { #[test] fn list_sheets() { + std::env::set_var("TZ", "UTC"); + let args = Default::default(); let mut db = SqliteDatabase::from_memory().unwrap(); let mut out = Vec::new(); @@ -135,38 +155,65 @@ mod tests { let config = Default::default(); db.init().unwrap(); db.set_current_sheet("sheet2").unwrap(); + db.set_last_sheet("sheet4").unwrap(); - db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), None, None, "_archived".into()).unwrap(); - db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), None, None, "sheet1".into()).unwrap(); - db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), None, None, "sheet2".into()).unwrap(); - db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), None, None, "sheet3".into()).unwrap(); + db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(1, 0, 0)), None, "_archived".into()).unwrap(); + db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(10,13, 55)), None, "sheet1".into()).unwrap(); + db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(7, 39, 18)), None, "sheet3".into()).unwrap(); + db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(13, 52, 45)), None, "sheet3".into()).unwrap(); + db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), None, None, "sheet4".into()).unwrap(); - ListCommand::handle(args, &mut db, &mut out, &mut err, &config).unwrap(); + let now = Utc.ymd(2021, 1, 1).and_hms(13, 52, 45); + + ListCommand::handle(args, &mut db, &mut out, &mut err, &config, now).unwrap(); assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString(" Timesheet Running Today Total Time + sheet1 0:00:00 0:00:00 10:13:55 * sheet2 0:00:00 0:00:00 0:00:00 sheet3 0:00:00 1:52:45 9:32:03 +- sheet4 1:52:45 1:52:45 1:52:45 +")); + + // now show all the sheets + let mut out = Vec::new(); + let mut err = Vec::new(); + let args = Args { + all: true, + }; + + ListCommand::handle(args, &mut db, &mut out, &mut err, &config, now).unwrap(); + + assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString(" Timesheet Running Today Total Time + + _archived 0:00:00 0:00:00 1:00:00 + sheet1 0:00:00 0:00:00 10:13:55 +* sheet2 0:00:00 0:00:00 0:00:00 + sheet3 0:00:00 1:52:45 9:32:03 +- sheet4 1:52:45 1:52:45 1:52:45 ")); } #[test] - fn show_all_sheets() { - assert!(false); - } + fn old_database() { + let args = Default::default(); + let mut db = SqliteDatabase::from_path("assets/test_list_old_database.db").unwrap(); + let mut out = Vec::new(); + let mut err = Vec::new(); + let config = Default::default(); - #[test] - fn show_warning_if_old_database() { - assert!(false); - } + let now = Local.ymd(2021, 7, 16).and_hms(11, 30, 45); - #[test] - fn list_sheets_shows_last() { - assert!(false); - } + ListCommand::handle(args, &mut db, &mut out, &mut err, &config, now.with_timezone(&Utc)).unwrap(); - #[test] - fn list_sheets_shows_running() { - assert!(false); + assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString(" Timesheet Running Today Total Time + +* default 0:10:24 0:10:26 0:10:26 +")); + + assert_eq!( + String::from_utf8_lossy(&err), + format!("{} You are using the old timetrap format, it is advised that you update your database using t migrate\n", Yellow.bold().paint("[WARNING]")), + ); } } diff --git a/src/commands/month.rs b/src/commands/month.rs index 88ca4bb..5580819 100644 --- a/src/commands/month.rs +++ b/src/commands/month.rs @@ -93,18 +93,16 @@ pub struct MonthCommand { } impl<'a> Command<'a> for MonthCommand { type Args = Args; - fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()> + fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime) -> Result<()> where D: Database, O: Write, E: Write, { - let now = Local::now(); - let (start, end) = match args.month { - MonthSpec::This => (beginning_of_month(now), Utc::now()), + MonthSpec::This => (beginning_of_month(now.with_timezone(&Local)), now), MonthSpec::Last => { - (beginning_of_previous_month(now), beginning_of_month(now)) + (beginning_of_previous_month(now.with_timezone(&Local)), beginning_of_month(now.with_timezone(&Local))) }, MonthSpec::Month(month) => { if month < now.month() { @@ -131,6 +129,6 @@ impl<'a> Command<'a> for MonthCommand { }, }; - entries_for_display(Some(start), Some(end), args.sheet, db, out, err, args.format, args.ids, args.grep) + entries_for_display(Some(start), Some(end), args.sheet, db, out, err, args.format, args.ids, args.grep, now) } } diff --git a/src/commands/sheet.rs b/src/commands/sheet.rs index 8a2e00c..15fd283 100644 --- a/src/commands/sheet.rs +++ b/src/commands/sheet.rs @@ -2,6 +2,7 @@ use std::convert::TryFrom; use std::io::Write; use clap::ArgMatches; +use chrono::{DateTime, Utc}; use crate::database::Database; use crate::error::{Error, Result}; @@ -29,7 +30,7 @@ pub struct SheetCommand {} impl<'a> Command<'a> for SheetCommand { type Args = Args; - fn handle(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config) -> Result<()> + fn handle(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime) -> Result<()> where D: Database, O: Write, @@ -40,7 +41,7 @@ impl<'a> Command<'a> for SheetCommand { unimplemented!() } else { // call list - ListCommand::handle(Default::default(), db, out, err, config) + ListCommand::handle(Default::default(), db, out, err, config, now) } } } diff --git a/src/commands/today.rs b/src/commands/today.rs index 11fffb6..dcd3f5d 100644 --- a/src/commands/today.rs +++ b/src/commands/today.rs @@ -42,14 +42,14 @@ pub struct TodayCommand { } impl<'a> Command<'a> for TodayCommand { type Args = Args; - fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()> + fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime) -> Result<()> where D: Database, O: Write, E: Write, { - let start = Some(Local::now().date().and_hms(0, 0, 0).with_timezone(&Utc)); + let start = Some(now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc)); - entries_for_display(start, args.end, args.sheet, db, out, err, args.format, args.ids, args.grep) + entries_for_display(start, args.end, args.sheet, db, out, err, args.format, args.ids, args.grep, now) } } diff --git a/src/commands/week.rs b/src/commands/week.rs index 59ca5eb..e28a8bc 100644 --- a/src/commands/week.rs +++ b/src/commands/week.rs @@ -86,15 +86,15 @@ pub struct WeekCommand { } impl<'a> Command<'a> for WeekCommand { type Args = Args; - fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config) -> Result<()> + fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime) -> Result<()> where D: Database, O: Write, E: Write, { - let start = prev_day(Local::now(), config.week_start); + let start = prev_day(now.with_timezone(&Local), config.week_start); - entries_for_display(Some(start), args.end, args.sheet, db, out, err, args.format, args.ids, args.grep) + entries_for_display(Some(start), args.end, args.sheet, db, out, err, args.format, args.ids, args.grep, now) } } diff --git a/src/commands/yesterday.rs b/src/commands/yesterday.rs index bbc959f..857dc19 100644 --- a/src/commands/yesterday.rs +++ b/src/commands/yesterday.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::io::Write; use clap::ArgMatches; -use chrono::{Utc, Local, Duration}; +use chrono::{DateTime, Utc, Local, Duration}; use regex::Regex; use crate::error::{Result, Error}; @@ -39,16 +39,17 @@ pub struct YesterdayCommand { } impl<'a> Command<'a> for YesterdayCommand { type Args = Args; - fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()> + fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime) -> Result<()> where D: Database, O: Write, E: Write, { - let start = Some((Local::today() - Duration::days(1)).and_hms(0, 0, 0).with_timezone(&Utc)); - let end = Some(Local::today().and_hms(0, 0, 0).with_timezone(&Utc)); + let today = now.with_timezone(&Local).date(); + let start = Some((today - Duration::days(1)).and_hms(0, 0, 0).with_timezone(&Utc)); + let end = Some(today.and_hms(0, 0, 0).with_timezone(&Utc)); - entries_for_display(start, end, args.sheet, db, out, err, args.format, args.ids, args.grep) + entries_for_display(start, end, args.sheet, db, out, err, args.format, args.ids, args.grep, now) } } @@ -83,7 +84,7 @@ mod tests { db.entry_insert(yesterday.and_hms(1, 2, 3).with_timezone(&Utc), None, Some("This!".into()), "default".into()).unwrap(); db.entry_insert(today.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default".into()).unwrap(); - YesterdayCommand::handle(args, &mut db, &mut out, &mut err, &config).unwrap(); + YesterdayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap(); assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString(&format!("start,end,note,sheet {},,This!,default diff --git a/src/database.rs b/src/database.rs index 28ad14d..0a322b4 100644 --- a/src/database.rs +++ b/src/database.rs @@ -159,6 +159,12 @@ pub trait Database { Ok(()) } + fn set_last_sheet(&mut self, sheet: &str) -> Result<()> { + self.execute("INSERT INTO meta (key, value) VALUES ('last_sheet', ?1)", &[&sheet])?; + + Ok(()) + } + fn version(&self) -> Result { let results = self.meta_query("select * from meta where key='database_version'", &[])?; diff --git a/src/formatters/text.rs b/src/formatters/text.rs index b4ea841..2d9ccff 100644 --- a/src/formatters/text.rs +++ b/src/formatters/text.rs @@ -2,7 +2,7 @@ use std::io::Write; use itertools::Itertools; use chrono::{ - DateTime, Utc, TimeZone, Duration, Local, NaiveTime, Timelike, + DateTime, Utc, Duration, Local, NaiveTime, Timelike, NaiveDateTime, }; @@ -58,7 +58,7 @@ pub fn print_formatted(entries: Vec, out: &mut W, now: DateTime ]); } - let entries_by_date = group.group_by(|e| Local.from_utc_datetime(&e.start.naive_utc()).date()); + let entries_by_date = group.group_by(|e| e.start.with_timezone(&Local).date()); let mut total = Duration::seconds(0); for (date, entries) in entries_by_date.into_iter() { @@ -66,11 +66,11 @@ pub fn print_formatted(entries: Vec, out: &mut W, now: DateTime for (i, entry) in entries.into_iter().enumerate() { let startend = format!("{} - {}", - format_start(Local.from_utc_datetime(&entry.start.naive_utc()).time()), + format_start(entry.start.with_timezone(&Local).time()), entry.end.map(|t| { format_end( entry.start.with_timezone(&Local).naive_local(), - Local.from_utc_datetime(&t.naive_utc()).naive_local() + t.with_timezone(&Local).naive_local() ) }).unwrap_or("".into()) ); @@ -107,6 +107,7 @@ pub fn print_formatted(entries: Vec, out: &mut W, now: DateTime #[cfg(test)] mod tests { use pretty_assertions::assert_eq; + use chrono::TimeZone; use crate::test_utils::PrettyString; @@ -165,6 +166,7 @@ mod tests { #[test] fn test_text_output_long_duration() { + std::env::set_var("TZ", "UTC"); let mut output = Vec::new(); let entries = vec![ Entry::new_sample(1, Utc.ymd(2008, 10, 1).and_hms(12, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(14, 0, 0))), @@ -188,6 +190,7 @@ mod tests { #[test] fn test_text_output_with_ids() { + std::env::set_var("TZ", "UTC"); let mut output = Vec::new(); let entries = vec![ Entry::new_sample(1, Utc.ymd(2008, 10, 3).and_hms(12, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(14, 0, 0))), @@ -215,6 +218,7 @@ mod tests { #[test] fn test_text_output_long_note_with_ids() { + std::env::set_var("TZ", "UTC"); let mut output = Vec::new(); let entries = vec![ Entry { @@ -248,6 +252,7 @@ mod tests { #[test] fn test_text_output_note_with_line_breaks() { + std::env::set_var("TZ", "UTC"); let mut output = Vec::new(); let entries = vec![ Entry { diff --git a/src/lib.rs b/src/lib.rs index d449567..131ed0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod models; pub mod timeparse; pub mod regex; pub mod tabulate; +pub mod old; #[cfg(test)] pub mod test_utils; diff --git a/src/main.rs b/src/main.rs index 1cb9a64..d69fbb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use std::process::exit; use std::io; use clap::{App, Arg, SubCommand, AppSettings, crate_version, ArgMatches}; +use chrono::Utc; use tiempo::error; use tiempo::database::SqliteDatabase; @@ -19,18 +20,19 @@ fn error_trap(matches: ArgMatches) -> error::Result<()> { let mut conn = SqliteDatabase::from_path_or_create(&config.database_file)?; let mut out = io::stdout(); let mut err = io::stderr(); + let now = Utc::now(); match matches.subcommand() { - ("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), + ("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), - ("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), - ("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), - ("yesterday", Some(matches)) => YesterdayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), - ("week", Some(matches)) => WeekCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), - ("month", Some(matches)) => MonthCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), + ("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), + ("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), + ("yesterday", Some(matches)) => YesterdayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), + ("week", Some(matches)) => WeekCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), + ("month", Some(matches)) => MonthCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), - ("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), - ("list", Some(matches)) => ListCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), + ("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), + ("list", Some(matches)) => ListCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), (cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())), } diff --git a/src/old.rs b/src/old.rs new file mode 100644 index 0000000..b6c5ac6 --- /dev/null +++ b/src/old.rs @@ -0,0 +1,59 @@ +use std::io::Write; + +use chrono::{DateTime, Utc, Local, LocalResult, TimeZone}; +use ansi_term::Color::Yellow; + +use crate::error::{Error, Result}; +use crate::models::Entry; +use crate::database::{Database, DBVersion}; + +fn local_to_utc(t: DateTime) -> Result> { + let local_time = match Local.from_local_datetime(&t.naive_utc()) { + LocalResult::None => return Err(Error::NoneLocalTime(t.naive_utc().to_string())), + LocalResult::Single(t) => t, + LocalResult::Ambiguous(t1, t2) => return Err(Error::AmbiguousLocalTime { + orig: t.naive_utc().to_string(), + t1: t1.naive_local(), + t2: t2.naive_local(), + }), + }; + + Ok(Utc.from_utc_datetime(&local_time.naive_utc())) +} + +fn local_to_utc_vec(entries: Vec) -> Result> { + entries + .into_iter() + .map(|e| { + Ok(Entry { + start: local_to_utc(e.start)?, + end: e.end.map(|t| local_to_utc(t)).transpose()?, + ..e + }) + }) + .collect() +} + +pub fn entries_or_warning(entries: Vec, db: &D) -> Result<(Vec, bool)> { + if let DBVersion::Timetrap = db.version()? { + // this indicates that times in the database are specified in the + // local time and need to be converted to utc before displaying + + Ok((local_to_utc_vec(entries)?, true)) + } else { + Ok((entries, false)) + } +} + +pub fn warn_if_needed(err: &mut E, needs_warning: bool) -> Result<()> { + if needs_warning { + writeln!( + err, + "{} You are using the old timetrap format, it is advised that \ + you update your database using t migrate", + Yellow.bold().paint("[WARNING]"), + )?; + } + + Ok(()) +}