current time management across commands
This commit is contained in:
parent
da124869ca
commit
f62ebef762
Binary file not shown.
|
@ -2,6 +2,7 @@ use std::convert::TryFrom;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
|
@ -19,5 +20,5 @@ pub mod list;
|
||||||
pub trait Command<'a> {
|
pub trait Command<'a> {
|
||||||
type Args: TryFrom<&'a ArgMatches<'a>>;
|
type Args: TryFrom<&'a ArgMatches<'a>>;
|
||||||
|
|
||||||
fn handle<D: Database, O: Write, E: Write>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config) -> Result<()>;
|
fn handle<D: Database, O: Write, E: Write>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,16 @@ use std::io::Write;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use chrono::{DateTime, Utc, Local, LocalResult, TimeZone};
|
use chrono::{DateTime, Utc};
|
||||||
use ansi_term::Color::Yellow;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::error::{Result, Error};
|
use crate::error::{Result, Error};
|
||||||
use crate::database::{Database, DBVersion};
|
use crate::database::Database;
|
||||||
use crate::formatters::Formatter;
|
use crate::formatters::Formatter;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::timeparse::parse_time;
|
use crate::timeparse::parse_time;
|
||||||
use crate::models::Entry;
|
|
||||||
use crate::regex::parse_regex;
|
use crate::regex::parse_regex;
|
||||||
|
use crate::old::{entries_or_warning, warn_if_needed};
|
||||||
|
|
||||||
use super::Command;
|
use super::Command;
|
||||||
|
|
||||||
|
@ -21,33 +20,6 @@ use super::Command;
|
||||||
// Things that are general to all commands that display in some way
|
// Things that are general to all commands that display in some way
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
fn local_to_utc(t: DateTime<Utc>) -> Result<DateTime<Utc>> {
|
|
||||||
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<Entry>) -> Result<Vec<Entry>> {
|
|
||||||
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 {
|
pub enum Sheet {
|
||||||
All,
|
All,
|
||||||
Full,
|
Full,
|
||||||
|
@ -70,6 +42,7 @@ pub fn entries_for_display<D, O, E>(
|
||||||
start: Option<DateTime<Utc>>, end: Option<DateTime<Utc>>,
|
start: Option<DateTime<Utc>>, end: Option<DateTime<Utc>>,
|
||||||
sheet: Option<Sheet>, db: &mut D, out: &mut O, err: &mut E,
|
sheet: Option<Sheet>, db: &mut D, out: &mut O, err: &mut E,
|
||||||
format: Formatter, ids: bool, grep: Option<Regex>,
|
format: Formatter, ids: bool, grep: Option<Regex>,
|
||||||
|
now: DateTime<Utc>,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
|
@ -91,30 +64,16 @@ where
|
||||||
entries.retain(|e| re.is_match(e.note.as_ref().unwrap_or(&String::new())));
|
entries.retain(|e| re.is_match(e.note.as_ref().unwrap_or(&String::new())));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (entries, needs_warning) = if let DBVersion::Timetrap = db.version()? {
|
let (entries, needs_warning) = entries_or_warning(entries, db)?;
|
||||||
// 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)
|
|
||||||
};
|
|
||||||
|
|
||||||
format.print_formatted(
|
format.print_formatted(
|
||||||
entries,
|
entries,
|
||||||
out,
|
out,
|
||||||
Utc::now(),
|
now,
|
||||||
ids,
|
ids,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if needs_warning {
|
warn_if_needed(err, 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -153,13 +112,13 @@ pub struct DisplayCommand { }
|
||||||
impl<'a> Command<'a> for DisplayCommand {
|
impl<'a> Command<'a> for DisplayCommand {
|
||||||
type Args = Args;
|
type Args = Args;
|
||||||
|
|
||||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()>
|
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
O: Write,
|
O: Write,
|
||||||
E: 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();
|
let config = Default::default();
|
||||||
std::env::set_var("TZ", "UTC");
|
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
|
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("Timesheet: default
|
||||||
Day Start End Duration Notes
|
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, 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();
|
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
|
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("start,end,note,sheet
|
||||||
2021-06-30T10:10:00Z,,hola,default
|
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, 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();
|
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
|
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("id,start,end,note,sheet
|
||||||
2,2021-06-30T10:10:00Z,,adios,default
|
2,2021-06-30T10:10:00Z,,adios,default
|
||||||
|
|
|
@ -32,13 +32,13 @@ pub struct InCommand {}
|
||||||
impl<'a> Command<'a> for InCommand {
|
impl<'a> Command<'a> for InCommand {
|
||||||
type Args = Args;
|
type Args = Args;
|
||||||
|
|
||||||
fn handle<D, O, E>(args: Args, db: &mut D, _out: &mut O, _err: &mut E, config: &Config) -> error::Result<()>
|
fn handle<D, O, E>(args: Args, db: &mut D, _out: &mut O, _err: &mut E, config: &Config, now: DateTime<Utc>) -> error::Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
O: Write,
|
O: Write,
|
||||||
E: 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 sheet = db.current_sheet()?.unwrap_or("default".into());
|
||||||
let note = if let Some(note) = args.note {
|
let note = if let Some(note) = args.note {
|
||||||
note
|
note
|
||||||
|
@ -71,7 +71,7 @@ mod tests {
|
||||||
|
|
||||||
assert!(false, "there are no entries");
|
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");
|
assert!(false, "there is one entry");
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ mod tests {
|
||||||
|
|
||||||
assert!(false, "there are no entries");
|
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, "there are still no entries");
|
||||||
assert!(false, "a message is issued to the user warning a running entry in this sheet");
|
assert!(false, "a message is issued to the user warning a running entry in this sheet");
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::convert::TryFrom;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use chrono::{Utc, Duration, Local};
|
use chrono::{DateTime, Utc, Duration, Local};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
@ -10,6 +10,8 @@ use crate::database::Database;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::tabulate::{Tabulate, Col, Align::*};
|
use crate::tabulate::{Tabulate, Col, Align::*};
|
||||||
use crate::formatters::text::format_duration;
|
use crate::formatters::text::format_duration;
|
||||||
|
use crate::models::Entry;
|
||||||
|
use crate::old::{entries_or_warning, warn_if_needed};
|
||||||
|
|
||||||
use super::Command;
|
use super::Command;
|
||||||
|
|
||||||
|
@ -33,27 +35,40 @@ pub struct ListCommand {}
|
||||||
impl<'a> Command<'a> for ListCommand {
|
impl<'a> Command<'a> for ListCommand {
|
||||||
type Args = Args;
|
type Args = Args;
|
||||||
|
|
||||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, _err: &mut E, _config: &Config) -> Result<()>
|
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
O: Write,
|
O: Write,
|
||||||
E: Write,
|
E: Write,
|
||||||
{
|
{
|
||||||
// no sheet, list sheets
|
// no sheet, list sheets
|
||||||
let now = Utc::now();
|
let today = now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc);
|
||||||
let today = Local::now().date().and_hms(0, 0, 0).with_timezone(&Utc);
|
let entries = if args.all {
|
||||||
let mut sheets = if args.all {
|
|
||||||
db.entries_full(None, None)?
|
db.entries_full(None, None)?
|
||||||
} else {
|
} else {
|
||||||
db.entries_all_visible(None, None)?
|
db.entries_all_visible(None, None)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let current = db.current_sheet()?.unwrap_or("".into());
|
let (mut entries, needs_warning) = entries_or_warning(entries, db)?;
|
||||||
let last = db.last_sheet()?.unwrap_or("".into());
|
|
||||||
|
|
||||||
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()
|
.into_iter()
|
||||||
.group_by(|e| e.sheet.clone())
|
.group_by(|e| e.sheet.clone())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -61,15 +76,15 @@ impl<'a> Command<'a> for ListCommand {
|
||||||
let entries: Vec<_> = group.into_iter().collect();
|
let entries: Vec<_> = group.into_iter().collect();
|
||||||
|
|
||||||
(
|
(
|
||||||
if current == key {
|
if current.as_ref() == Some(&key) {
|
||||||
"*".into()
|
"*".into()
|
||||||
} else {
|
} else {
|
||||||
if last == key { "-".into() } else { "".into() }
|
if last.as_ref() == Some(&key) { "-".into() } else { "".into() }
|
||||||
},
|
},
|
||||||
key,
|
key,
|
||||||
|
|
||||||
format_duration(
|
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(
|
format_duration(
|
||||||
|
@ -112,6 +127,8 @@ impl<'a> Command<'a> for ListCommand {
|
||||||
|
|
||||||
out.write_all(tabs.print().as_bytes())?;
|
out.write_all(tabs.print().as_bytes())?;
|
||||||
|
|
||||||
|
warn_if_needed(err, needs_warning)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +137,7 @@ impl<'a> Command<'a> for ListCommand {
|
||||||
mod tests {
|
mod tests {
|
||||||
use chrono::{Utc, TimeZone};
|
use chrono::{Utc, TimeZone};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use ansi_term::Color::Yellow;
|
||||||
|
|
||||||
use crate::database::{SqliteDatabase, Database};
|
use crate::database::{SqliteDatabase, Database};
|
||||||
use crate::test_utils::PrettyString;
|
use crate::test_utils::PrettyString;
|
||||||
|
@ -128,6 +146,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_sheets() {
|
fn list_sheets() {
|
||||||
|
std::env::set_var("TZ", "UTC");
|
||||||
|
|
||||||
let args = Default::default();
|
let args = Default::default();
|
||||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
|
@ -135,38 +155,65 @@ mod tests {
|
||||||
let config = Default::default();
|
let config = Default::default();
|
||||||
db.init().unwrap();
|
db.init().unwrap();
|
||||||
db.set_current_sheet("sheet2").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), 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), None, 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(10,13, 55)), 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), 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(0, 0, 0), None, 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
|
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString(" Timesheet Running Today Total Time
|
||||||
|
|
||||||
sheet1 0:00:00 0:00:00 10:13:55
|
sheet1 0:00:00 0:00:00 10:13:55
|
||||||
* sheet2 0:00:00 0:00:00 0:00:00
|
* sheet2 0:00:00 0:00:00 0:00:00
|
||||||
sheet3 0:00:00 1:52:45 9:32:03
|
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]
|
#[test]
|
||||||
fn show_all_sheets() {
|
fn old_database() {
|
||||||
assert!(false);
|
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]
|
let now = Local.ymd(2021, 7, 16).and_hms(11, 30, 45);
|
||||||
fn show_warning_if_old_database() {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
ListCommand::handle(args, &mut db, &mut out, &mut err, &config, now.with_timezone(&Utc)).unwrap();
|
||||||
fn list_sheets_shows_last() {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString(" Timesheet Running Today Total Time
|
||||||
fn list_sheets_shows_running() {
|
|
||||||
assert!(false);
|
* 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]")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,18 +93,16 @@ pub struct MonthCommand { }
|
||||||
impl<'a> Command<'a> for MonthCommand {
|
impl<'a> Command<'a> for MonthCommand {
|
||||||
type Args = Args;
|
type Args = Args;
|
||||||
|
|
||||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()>
|
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
O: Write,
|
O: Write,
|
||||||
E: Write,
|
E: Write,
|
||||||
{
|
{
|
||||||
let now = Local::now();
|
|
||||||
|
|
||||||
let (start, end) = match args.month {
|
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 => {
|
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) => {
|
MonthSpec::Month(month) => {
|
||||||
if month < now.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::convert::TryFrom;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
@ -29,7 +30,7 @@ pub struct SheetCommand {}
|
||||||
impl<'a> Command<'a> for SheetCommand {
|
impl<'a> Command<'a> for SheetCommand {
|
||||||
type Args = Args;
|
type Args = Args;
|
||||||
|
|
||||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config) -> Result<()>
|
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
O: Write,
|
O: Write,
|
||||||
|
@ -40,7 +41,7 @@ impl<'a> Command<'a> for SheetCommand {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
} else {
|
} else {
|
||||||
// call list
|
// call list
|
||||||
ListCommand::handle(Default::default(), db, out, err, config)
|
ListCommand::handle(Default::default(), db, out, err, config, now)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,14 +42,14 @@ pub struct TodayCommand { }
|
||||||
impl<'a> Command<'a> for TodayCommand {
|
impl<'a> Command<'a> for TodayCommand {
|
||||||
type Args = Args;
|
type Args = Args;
|
||||||
|
|
||||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()>
|
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
O: Write,
|
O: Write,
|
||||||
E: 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,15 +86,15 @@ pub struct WeekCommand { }
|
||||||
impl<'a> Command<'a> for WeekCommand {
|
impl<'a> Command<'a> for WeekCommand {
|
||||||
type Args = Args;
|
type Args = Args;
|
||||||
|
|
||||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config) -> Result<()>
|
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
O: Write,
|
O: Write,
|
||||||
E: 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::convert::TryFrom;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use chrono::{Utc, Local, Duration};
|
use chrono::{DateTime, Utc, Local, Duration};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::error::{Result, Error};
|
use crate::error::{Result, Error};
|
||||||
|
@ -39,16 +39,17 @@ pub struct YesterdayCommand { }
|
||||||
impl<'a> Command<'a> for YesterdayCommand {
|
impl<'a> Command<'a> for YesterdayCommand {
|
||||||
type Args = Args;
|
type Args = Args;
|
||||||
|
|
||||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()>
|
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||||
where
|
where
|
||||||
D: Database,
|
D: Database,
|
||||||
O: Write,
|
O: Write,
|
||||||
E: Write,
|
E: Write,
|
||||||
{
|
{
|
||||||
let start = Some((Local::today() - Duration::days(1)).and_hms(0, 0, 0).with_timezone(&Utc));
|
let today = now.with_timezone(&Local).date();
|
||||||
let end = Some(Local::today().and_hms(0, 0, 0).with_timezone(&Utc));
|
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(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();
|
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
|
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString(&format!("start,end,note,sheet
|
||||||
{},,This!,default
|
{},,This!,default
|
||||||
|
|
|
@ -159,6 +159,12 @@ pub trait Database {
|
||||||
Ok(())
|
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<DBVersion> {
|
fn version(&self) -> Result<DBVersion> {
|
||||||
let results = self.meta_query("select * from meta where key='database_version'", &[])?;
|
let results = self.meta_query("select * from meta where key='database_version'", &[])?;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::Write;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use chrono::{
|
use chrono::{
|
||||||
DateTime, Utc, TimeZone, Duration, Local, NaiveTime, Timelike,
|
DateTime, Utc, Duration, Local, NaiveTime, Timelike,
|
||||||
NaiveDateTime,
|
NaiveDateTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ pub fn print_formatted<W: Write>(entries: Vec<Entry>, 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);
|
let mut total = Duration::seconds(0);
|
||||||
|
|
||||||
for (date, entries) in entries_by_date.into_iter() {
|
for (date, entries) in entries_by_date.into_iter() {
|
||||||
|
@ -66,11 +66,11 @@ pub fn print_formatted<W: Write>(entries: Vec<Entry>, out: &mut W, now: DateTime
|
||||||
|
|
||||||
for (i, entry) in entries.into_iter().enumerate() {
|
for (i, entry) in entries.into_iter().enumerate() {
|
||||||
let startend = format!("{} - {}",
|
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| {
|
entry.end.map(|t| {
|
||||||
format_end(
|
format_end(
|
||||||
entry.start.with_timezone(&Local).naive_local(),
|
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())
|
}).unwrap_or("".into())
|
||||||
);
|
);
|
||||||
|
@ -107,6 +107,7 @@ pub fn print_formatted<W: Write>(entries: Vec<Entry>, out: &mut W, now: DateTime
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use chrono::TimeZone;
|
||||||
|
|
||||||
use crate::test_utils::PrettyString;
|
use crate::test_utils::PrettyString;
|
||||||
|
|
||||||
|
@ -165,6 +166,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_text_output_long_duration() {
|
fn test_text_output_long_duration() {
|
||||||
|
std::env::set_var("TZ", "UTC");
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
let entries = vec![
|
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))),
|
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]
|
#[test]
|
||||||
fn test_text_output_with_ids() {
|
fn test_text_output_with_ids() {
|
||||||
|
std::env::set_var("TZ", "UTC");
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
let entries = vec![
|
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))),
|
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]
|
#[test]
|
||||||
fn test_text_output_long_note_with_ids() {
|
fn test_text_output_long_note_with_ids() {
|
||||||
|
std::env::set_var("TZ", "UTC");
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
let entries = vec![
|
let entries = vec![
|
||||||
Entry {
|
Entry {
|
||||||
|
@ -248,6 +252,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_text_output_note_with_line_breaks() {
|
fn test_text_output_note_with_line_breaks() {
|
||||||
|
std::env::set_var("TZ", "UTC");
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
let entries = vec![
|
let entries = vec![
|
||||||
Entry {
|
Entry {
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub mod models;
|
||||||
pub mod timeparse;
|
pub mod timeparse;
|
||||||
pub mod regex;
|
pub mod regex;
|
||||||
pub mod tabulate;
|
pub mod tabulate;
|
||||||
|
pub mod old;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test_utils;
|
pub mod test_utils;
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -3,6 +3,7 @@ use std::process::exit;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use clap::{App, Arg, SubCommand, AppSettings, crate_version, ArgMatches};
|
use clap::{App, Arg, SubCommand, AppSettings, crate_version, ArgMatches};
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
use tiempo::error;
|
use tiempo::error;
|
||||||
use tiempo::database::SqliteDatabase;
|
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 conn = SqliteDatabase::from_path_or_create(&config.database_file)?;
|
||||||
let mut out = io::stdout();
|
let mut out = io::stdout();
|
||||||
let mut err = io::stderr();
|
let mut err = io::stderr();
|
||||||
|
let now = Utc::now();
|
||||||
|
|
||||||
match matches.subcommand() {
|
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),
|
("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),
|
("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),
|
("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),
|
("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),
|
("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),
|
("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),
|
("list", Some(matches)) => ListCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||||
|
|
||||||
(cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())),
|
(cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Utc>) -> Result<DateTime<Utc>> {
|
||||||
|
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<Entry>) -> Result<Vec<Entry>> {
|
||||||
|
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<D: Database>(entries: Vec<Entry>, db: &D) -> Result<(Vec<Entry>, 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<E: Write>(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(())
|
||||||
|
}
|
Loading…
Reference in New Issue