Compare commits
2 commits
660d7b1815
...
1b065c434f
Author | SHA1 | Date | |
---|---|---|---|
|
1b065c434f | ||
|
ae4a678d77 |
4 changed files with 89 additions and 37 deletions
|
@ -1,5 +1,3 @@
|
||||||
use std::ops::Sub;
|
|
||||||
|
|
||||||
use chrono::LocalResult::Single;
|
use chrono::LocalResult::Single;
|
||||||
use chrono::{DateTime, Local, NaiveTime, TimeDelta, TimeZone, Utc};
|
use chrono::{DateTime, Local, NaiveTime, TimeDelta, TimeZone, Utc};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
@ -34,16 +32,24 @@ impl<T: TimeZone> ToTimestamp for DateTime<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the hour from a plain number in the String,
|
|
||||||
|
/// Parses the hour optionally with minute from a plain number in a String,
|
||||||
/// with max of max_future hours into the future.
|
/// with max of max_future hours into the future.
|
||||||
// TODO parse HHMM as well
|
|
||||||
pub fn parse_hour(str: &str, max_future: i64) -> Option<DateTime<Local>> {
|
pub fn parse_hour(str: &str, max_future: i64) -> Option<DateTime<Local>> {
|
||||||
str.parse::<u32>().ok().and_then(|hour| {
|
parse_hour_after(str, Local::now() - TimeDelta::hours(24 - max_future))
|
||||||
let now = Local::now();
|
}
|
||||||
|
|
||||||
|
/// Parses the hour optionally with minute from a plain number in a String.
|
||||||
|
pub fn parse_hour_after<T: TimeZone>(str: &str, after: DateTime<T>) -> Option<DateTime<T>> {
|
||||||
|
str.parse::<u32>().ok().and_then(|number| {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
now.date().and_hms_opt(hour, 0, 0).map(|time| {
|
after.date().and_hms_opt(
|
||||||
if time - now > TimeDelta::hours(max_future) {
|
if number > 23 { number / 100 } else { number },
|
||||||
time.sub(TimeDelta::days(1))
|
if number > 23 { number % 100 } else { 0 },
|
||||||
|
0,
|
||||||
|
).map(|time| {
|
||||||
|
if time < after {
|
||||||
|
time + TimeDelta::days(1)
|
||||||
} else {
|
} else {
|
||||||
time
|
time
|
||||||
}
|
}
|
||||||
|
@ -160,3 +166,41 @@ pub fn format_timestamp_relative_to(stamp: &Timestamp, reference: &Timestamp) ->
|
||||||
_ => format_timestamp_local(stamp),
|
_ => format_timestamp_local(stamp),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use chrono::{NaiveDate, NaiveDateTime, Timelike};
|
||||||
|
use interim::datetime::DateTime;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_hours() {
|
||||||
|
let now = Local::now();
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let date = now.date();
|
||||||
|
if now.hour() > 2 {
|
||||||
|
assert_eq!(
|
||||||
|
parse_hour("23", 22).unwrap(),
|
||||||
|
date.and_hms_opt(23, 0, 0).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if now.hour() < 22 {
|
||||||
|
assert_eq!(
|
||||||
|
parse_hour("02", 2).unwrap(),
|
||||||
|
date.and_hms_opt(2, 0, 0).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_hour("2301", 1).unwrap(),
|
||||||
|
(date - TimeDelta::days(1)).and_hms_opt(23, 01, 0).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let date = NaiveDate::from_ymd_opt(2020, 10, 10).unwrap();
|
||||||
|
let time = Utc.from_utc_datetime(
|
||||||
|
&date.and_hms_opt(10, 1,0).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(parse_hour_after("2201", time).unwrap(), Utc.from_utc_datetime(&date.and_hms_opt(22, 1, 0).unwrap()));
|
||||||
|
assert_eq!(parse_hour_after("10", time).unwrap(), Utc.from_utc_datetime(&(date + TimeDelta::days(1)).and_hms_opt(10, 0, 0).unwrap()));
|
||||||
|
|
||||||
|
// TODO test timezone offset issues
|
||||||
|
}
|
||||||
|
}
|
27
src/main.rs
27
src/main.rs
|
@ -631,32 +631,17 @@ async fn main() -> Result<()> {
|
||||||
Err(e) => warn!("Ignoring extra {:?}: {}\nSyntax: ((INT", remaining, e),
|
Err(e) => warn!("Ignoring extra {:?}: {}\nSyntax: ((INT", remaining, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (label, times) = tasks.times_tracked();
|
println!("{}", tasks.times_tracked(max));
|
||||||
let vec = times.rev().take(max).collect_vec();
|
|
||||||
println!("{}\n{}",
|
|
||||||
if vec.is_empty() {
|
|
||||||
label
|
|
||||||
} else {
|
|
||||||
format!("{} {}",
|
|
||||||
if max == usize::MAX { "All".to_string() } else { format!("Latest {max} entries of") },
|
|
||||||
label)
|
|
||||||
}.italic(),
|
|
||||||
vec.iter().rev().join("\n"));
|
|
||||||
} else if let Some((key, _)) = tasks.find_user(arg) {
|
} else if let Some((key, _)) = tasks.find_user(arg) {
|
||||||
let (label, mut times) = tasks.times_tracked_for(&key);
|
let (label, mut times) = tasks.times_tracked_for(&key);
|
||||||
println!("{}\n{}", label.italic(),
|
println!("{}\n{}", label.italic(), times.join("\n"));
|
||||||
times.join("\n"));
|
|
||||||
} else {
|
} else {
|
||||||
if tasks.track_from(arg) {
|
if tasks.track_from(arg) {
|
||||||
let (label, times) = tasks.times_tracked();
|
println!("{}", tasks.times_tracked(15));
|
||||||
println!("{}\n{}", label.italic(),
|
|
||||||
times.rev().take(15).collect_vec().iter().rev().join("\n"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let (label, times) = tasks.times_tracked();
|
println!("{}", tasks.times_tracked(60));
|
||||||
println!("{}\n{}", label.italic(),
|
|
||||||
times.rev().take(80).collect_vec().iter().rev().join("\n"));
|
|
||||||
}
|
}
|
||||||
continue 'repl;
|
continue 'repl;
|
||||||
}
|
}
|
||||||
|
@ -666,9 +651,7 @@ async fn main() -> Result<()> {
|
||||||
None => tasks.move_to(None),
|
None => tasks.move_to(None),
|
||||||
Some(arg) => {
|
Some(arg) => {
|
||||||
if parse_tracking_stamp(arg).and_then(|stamp| tasks.track_at(stamp, None)).is_some() {
|
if parse_tracking_stamp(arg).and_then(|stamp| tasks.track_at(stamp, None)).is_some() {
|
||||||
let (label, times) = tasks.times_tracked();
|
println!("{}", tasks.times_tracked(15));
|
||||||
println!("{}\n{}", label.italic(),
|
|
||||||
times.rev().take(15).collect_vec().iter().rev().join("\n"));
|
|
||||||
}
|
}
|
||||||
// So the error message is not covered up
|
// So the error message is not covered up
|
||||||
continue 'repl;
|
continue 'repl;
|
||||||
|
|
23
src/tasks.rs
23
src/tasks.rs
|
@ -269,8 +269,20 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dynamic time tracking overview for current task or current user.
|
/// Dynamic time tracking overview for current task or current user.
|
||||||
pub(crate) fn times_tracked(&self) -> (String, Box<dyn DoubleEndedIterator<Item=String> + '_>) {
|
pub(crate) fn times_tracked(&self, limit: usize) -> String {
|
||||||
self.times_tracked_with(&self.sender.pubkey())
|
let (label, times) = self.times_tracked_with(&self.sender.pubkey());
|
||||||
|
let times = times.collect_vec();
|
||||||
|
format!("{}\n{}",
|
||||||
|
if times.is_empty() {
|
||||||
|
label
|
||||||
|
} else {
|
||||||
|
format!("{}{}",
|
||||||
|
if limit > times.len() || limit == usize::MAX { "All ".to_string() }
|
||||||
|
else if limit < 20 { "Recent ".to_string() }
|
||||||
|
else { format!("Latest {limit} Entries of ") },
|
||||||
|
label)
|
||||||
|
}.italic(),
|
||||||
|
×[times.len().saturating_sub(limit)..].join("\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn history_for(
|
pub(crate) fn history_for(
|
||||||
|
@ -318,6 +330,8 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Time tracked for current position or key
|
||||||
|
/// Note: reversing the iterator skips most recent timetracking stops
|
||||||
pub(crate) fn times_tracked_with(
|
pub(crate) fn times_tracked_with(
|
||||||
&self,
|
&self,
|
||||||
key: &PublicKey,
|
key: &PublicKey,
|
||||||
|
@ -1454,10 +1468,7 @@ impl Display for TasksRelay {
|
||||||
if self.tasks.children_for(self.get_position()).next().is_some() {
|
if self.tasks.children_for(self.get_position()).next().is_some() {
|
||||||
writeln!(lock, "No tasks here matching{}", self.get_prompt_suffix())?;
|
writeln!(lock, "No tasks here matching{}", self.get_prompt_suffix())?;
|
||||||
}
|
}
|
||||||
let (label, times) = self.times_tracked();
|
writeln!(lock, "{}", self.times_tracked(6))?;
|
||||||
let mut times_recent = times.rev().take(6).collect_vec();
|
|
||||||
times_recent.reverse();
|
|
||||||
writeln!(lock, "{}\n{}", format!("Recent {}", label).italic(), times_recent.join("\n"))?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -281,6 +281,20 @@ fn test_filter_or_create() {
|
||||||
assert_eq!(tasks.len(), 3);
|
assert_eq!(tasks.len(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history() {
|
||||||
|
let mut tasks = stub_tasks();
|
||||||
|
let zero = EventId::all_zeros();
|
||||||
|
|
||||||
|
tasks.track_at(Timestamp::now() - 3, Some(zero));
|
||||||
|
tasks.move_to(None);
|
||||||
|
assert_eq!(tasks.times_tracked(1).len(), 121);
|
||||||
|
let all = tasks.times_tracked(10);
|
||||||
|
assert_eq!(all.len(), 202, "{}", all);
|
||||||
|
assert!(all.contains(" 0000000000000000000000000000000000000000000000000000000000000000"), "{}", all);
|
||||||
|
assert!(all.ends_with(" ---"), "{}", all);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tracking() {
|
fn test_tracking() {
|
||||||
let mut tasks = stub_tasks();
|
let mut tasks = stub_tasks();
|
||||||
|
|
Loading…
Add table
Reference in a new issue