correctly format long durations and long dates
This commit is contained in:
parent
fc5698dcfd
commit
950adfbe44
|
@ -3,7 +3,10 @@ use std::io::Write;
|
|||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use itertools::Itertools;
|
||||
use chrono::{DateTime, Utc, Offset, TimeZone, Duration, NaiveTime, Timelike};
|
||||
use chrono::{
|
||||
DateTime, Utc, Offset, TimeZone, Duration, NaiveTime, Timelike,
|
||||
NaiveDateTime,
|
||||
};
|
||||
|
||||
use crate::error;
|
||||
use crate::models::Entry;
|
||||
|
@ -12,8 +15,33 @@ fn format_duration(dur: Duration) -> String {
|
|||
format!("{}:{:02}:{:02}", dur.num_hours(), dur.num_minutes() % 60, dur.num_seconds() % 60)
|
||||
}
|
||||
|
||||
fn format_time(t: NaiveTime) -> String {
|
||||
format!("{:02}:{:02}:{:02}", t.hour(), t.minute(), t.second())
|
||||
fn format_start(t: NaiveTime) -> String {
|
||||
format!("{:02}:{:02}:{:02} -", t.hour(), t.minute(), t.second())
|
||||
}
|
||||
|
||||
fn lpad(s: &str, len: usize) -> String {
|
||||
let padding = " ".repeat(len.saturating_sub(s.len()));
|
||||
|
||||
padding + s
|
||||
}
|
||||
|
||||
fn rpad(s: &str, len: usize) -> String {
|
||||
let padding = " ".repeat(len.saturating_sub(s.len()));
|
||||
|
||||
s.to_string() + &padding
|
||||
}
|
||||
|
||||
fn format_end(start: NaiveDateTime, end: NaiveDateTime) -> String {
|
||||
let extra_days = (end - start).num_days();
|
||||
let d = if extra_days > 0 { format!("+{}d", extra_days) } else { "".into() };
|
||||
|
||||
format!(
|
||||
"{:02}:{:02}:{:02}{}",
|
||||
end.hour(),
|
||||
end.minute() % 60,
|
||||
end.second() % 60,
|
||||
d
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -52,7 +80,10 @@ impl Formatter {
|
|||
|
||||
for (key, group) in grouped_entries.into_iter() {
|
||||
writeln!(out, "Timesheet: {}", key)?;
|
||||
writeln!(out, " Day Start End Duration Notes")?;
|
||||
|
||||
// End Duration Notes")?;
|
||||
// A vector of lines to be printed, with all the components
|
||||
let mut lines = Vec::new();
|
||||
|
||||
let entries_by_date = group.group_by(|e| fixed_offset.from_utc_datetime(&e.start.naive_utc()).date());
|
||||
let mut total = Duration::seconds(0);
|
||||
|
@ -61,8 +92,13 @@ impl Formatter {
|
|||
let mut daily = Duration::seconds(0);
|
||||
|
||||
for (i,entry) in entries.into_iter().enumerate() {
|
||||
let start = format_time(fixed_offset.from_utc_datetime(&entry.start.naive_utc()).time());
|
||||
let end = entry.end.map(|t| format_time(fixed_offset.from_utc_datetime(&t.naive_utc()).time())).unwrap_or(" ".into());
|
||||
let start = format_start(fixed_offset.from_utc_datetime(&entry.start.naive_utc()).time());
|
||||
let end = entry.end.map(|t| {
|
||||
format_end(
|
||||
fixed_offset.from_utc_datetime(&entry.start.naive_utc()).naive_local(),
|
||||
fixed_offset.from_utc_datetime(&t.naive_utc()).naive_local()
|
||||
)
|
||||
}).unwrap_or(" ".into());
|
||||
let duration = entry.end.unwrap_or(now) - entry.start;
|
||||
daily = daily + duration;
|
||||
let duration = format_duration(duration);
|
||||
|
@ -70,27 +106,66 @@ impl Formatter {
|
|||
if i == 0 {
|
||||
let date = date.format("%a %b %d, %Y").to_string();
|
||||
|
||||
writeln!(
|
||||
out,
|
||||
" {date} {start} - {end} {duration} {note}",
|
||||
date=date, start=start, end=end, duration=duration, note=entry.note,
|
||||
)?;
|
||||
lines.push(["".into(), date, start, end, duration, entry.note]);
|
||||
} else {
|
||||
writeln!(
|
||||
out,
|
||||
" {start} - {end} {duration} {note}",
|
||||
start=start, end=end, duration=duration, note=entry.note,
|
||||
)?;
|
||||
lines.push(["".into(), "".into(), start, end, duration, entry.note]);
|
||||
}
|
||||
}
|
||||
|
||||
total = total + daily;
|
||||
|
||||
writeln!(out, " {}", format_duration(daily))?;
|
||||
lines.push(["".into(), "".into(), "".into(), "".into(), format_duration(daily), "".into()]);
|
||||
}
|
||||
|
||||
writeln!(out, " -----------------------------------------------------------")?;
|
||||
writeln!(out, " Total {}", format_duration(total))?;
|
||||
// compute some column widths before printing
|
||||
// When array_map is stabilized this can be shortened
|
||||
let lengths = lines
|
||||
.iter()
|
||||
.map(|[i, d, s, e, du, n]| [i.len(), d.len(), s.len(), e.len(), du.len(), n.len()])
|
||||
.reduce(|[a, b, c, d, e, f], [g, h, i, j, k, l]| {
|
||||
[a.max(g), b.max(h), c.max(i), d.max(j), e.max(k), f.max(l)]
|
||||
}).unwrap();
|
||||
|
||||
writeln!(out,
|
||||
"{} {} {} {} {} {}",
|
||||
lpad(" ", 3.max(lengths[0])),
|
||||
rpad("Day", 18),
|
||||
rpad("Start", 10),
|
||||
rpad("End", 8.max(lengths[3])),
|
||||
rpad("Duration", 8.max(lengths[4])),
|
||||
"Notes",
|
||||
)?;
|
||||
|
||||
for [id, date, start, end, duration, note] in lines {
|
||||
writeln!(
|
||||
out,
|
||||
"{} {} {} {} {} {}",
|
||||
lpad(&id, 3.max(lengths[0])),
|
||||
rpad(&date, 18),
|
||||
rpad(&start, 10),
|
||||
rpad(&end, 8.max(lengths[3])),
|
||||
lpad(&duration, lengths[4]),
|
||||
note,
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(out,
|
||||
"{} {}-{}-{}-{}-{}",
|
||||
lpad(" ", 3.max(lengths[0])),
|
||||
"-".repeat(18),
|
||||
"-".repeat(10),
|
||||
"-".repeat(8.max(lengths[3])),
|
||||
"-".repeat(8.max(lengths[4])),
|
||||
"-".repeat(4.max(lengths[5])),
|
||||
)?;
|
||||
writeln!(out,
|
||||
"{} {} {} {} {}",
|
||||
lpad(" ", 3.max(lengths[0])),
|
||||
rpad("Total", 18),
|
||||
rpad("", 10),
|
||||
rpad("", 8.max(lengths[3])),
|
||||
rpad(&format_duration(total), 8.max(lengths[4])),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -176,6 +251,31 @@ mod tests {
|
|||
1:59:59
|
||||
-----------------------------------------------------------
|
||||
Total 1:59:59
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_output_long_duration() {
|
||||
let formatter = Formatter::Text;
|
||||
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))),
|
||||
Entry::new_sample(2, Utc.ymd(2008, 10, 3).and_hms(12, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(14, 0, 0))),
|
||||
];
|
||||
|
||||
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
|
||||
let offset = Utc;
|
||||
|
||||
formatter.print_formatted(entries, &mut output, now, offset).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default
|
||||
Day Start End Duration Notes
|
||||
Wed Oct 01, 2008 12:00:00 - 14:00:00+2d 50:00:00 entry 1
|
||||
50:00:00
|
||||
Fri Oct 03, 2008 12:00:00 - 14:00:00 2:00:00 entry 2
|
||||
2:00:00
|
||||
----------------------------------------------------------
|
||||
Total 52:00:00
|
||||
"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue