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 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<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 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<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 {
|
||||
All,
|
||||
Full,
|
||||
|
@ -70,6 +42,7 @@ pub fn entries_for_display<D, O, E>(
|
|||
start: Option<DateTime<Utc>>, end: Option<DateTime<Utc>>,
|
||||
sheet: Option<Sheet>, db: &mut D, out: &mut O, err: &mut E,
|
||||
format: Formatter, ids: bool, grep: Option<Regex>,
|
||||
now: DateTime<Utc>,
|
||||
) -> 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<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
|
||||
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
|
||||
|
|
|
@ -32,13 +32,13 @@ pub struct InCommand {}
|
|||
impl<'a> Command<'a> for InCommand {
|
||||
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
|
||||
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");
|
||||
|
|
|
@ -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<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
|
||||
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]")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,18 +93,16 @@ pub struct MonthCommand { }
|
|||
impl<'a> Command<'a> for MonthCommand {
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,14 +42,14 @@ pub struct TodayCommand { }
|
|||
impl<'a> Command<'a> for TodayCommand {
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,15 +86,15 @@ pub struct WeekCommand { }
|
|||
impl<'a> Command<'a> for WeekCommand {
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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
|
||||
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
|
||||
|
|
|
@ -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<DBVersion> {
|
||||
let results = self.meta_query("select * from meta where key='database_version'", &[])?;
|
||||
|
||||
|
|
|
@ -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<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);
|
||||
|
||||
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() {
|
||||
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<W: Write>(entries: Vec<Entry>, 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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
18
src/main.rs
18
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())),
|
||||
}
|
||||
|
|
|
@ -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