Compare commits
No commits in common. "dev" and "main" have entirely different histories.
|
@ -1,3 +1,5 @@
|
||||||
|
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};
|
||||||
|
@ -32,24 +34,16 @@ 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>> {
|
||||||
parse_hour_after(str, Local::now() - TimeDelta::hours(24 - max_future))
|
str.parse::<u32>().ok().and_then(|hour| {
|
||||||
}
|
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)]
|
||||||
after.date().and_hms_opt(
|
now.date().and_hms_opt(hour, 0, 0).map(|time| {
|
||||||
if number > 23 { number / 100 } else { number },
|
if time - now > TimeDelta::hours(max_future) {
|
||||||
if number > 23 { number % 100 } else { 0 },
|
time.sub(TimeDelta::days(1))
|
||||||
0,
|
|
||||||
).map(|time| {
|
|
||||||
if time < after {
|
|
||||||
time + TimeDelta::days(1)
|
|
||||||
} else {
|
} else {
|
||||||
time
|
time
|
||||||
}
|
}
|
||||||
|
@ -58,15 +52,11 @@ pub fn parse_hour_after<T: TimeZone>(str: &str, after: DateTime<T>) -> Option<Da
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_date(str: &str) -> Option<DateTime<Utc>> {
|
pub fn parse_date(str: &str) -> Option<DateTime<Utc>> {
|
||||||
parse_date_with_ref(str, Local::now())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_date_with_ref(str: &str, reference: DateTime<Local>) -> Option<DateTime<Utc>> {
|
|
||||||
// Using two libraries for better exhaustiveness, see https://github.com/uutils/parse_datetime/issues/84
|
// Using two libraries for better exhaustiveness, see https://github.com/uutils/parse_datetime/issues/84
|
||||||
match interim::parse_date_string(str, reference, interim::Dialect::Us) {
|
match interim::parse_date_string(str, Local::now(), interim::Dialect::Us) {
|
||||||
Ok(date) => Some(date.to_utc()),
|
Ok(date) => Some(date.to_utc()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
match parse_datetime::parse_datetime_at_date(reference, str) {
|
match parse_datetime::parse_datetime_at_date(Local::now(), str) {
|
||||||
Ok(date) => Some(date.to_utc()),
|
Ok(date) => Some(date.to_utc()),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!("Could not parse date from \"{str}\": {e}");
|
warn!("Could not parse date from \"{str}\": {e}");
|
||||||
|
@ -89,8 +79,8 @@ pub fn parse_date_with_ref(str: &str, reference: DateTime<Local>) -> Option<Date
|
||||||
/// - Plain number as hour, 18 hours back or 6 hours forward
|
/// - Plain number as hour, 18 hours back or 6 hours forward
|
||||||
/// - Number with prefix as minute offset
|
/// - Number with prefix as minute offset
|
||||||
/// - Otherwise try to parse a relative date
|
/// - Otherwise try to parse a relative date
|
||||||
pub fn parse_tracking_stamp(str: &str, after: Option<DateTime<Local>>) -> Option<Timestamp> {
|
pub fn parse_tracking_stamp(str: &str) -> Option<Timestamp> {
|
||||||
if let Some(num) = parse_hour_after(str, after.unwrap_or(Local::now() - TimeDelta::hours(18))) {
|
if let Some(num) = parse_hour(str, 6) {
|
||||||
return Some(num.to_timestamp());
|
return Some(num.to_timestamp());
|
||||||
}
|
}
|
||||||
let stripped = str.trim().trim_start_matches('+').trim_start_matches("in ");
|
let stripped = str.trim().trim_start_matches('+').trim_start_matches("in ");
|
||||||
|
@ -170,49 +160,3 @@ pub fn format_timestamp_relative_to(stamp: &Timestamp, reference: &Timestamp) ->
|
||||||
_ => format_timestamp_local(stamp),
|
_ => format_timestamp_local(stamp),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use chrono::{FixedOffset, NaiveDate, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_timezone() {
|
|
||||||
assert_eq!(
|
|
||||||
FixedOffset::east_opt(7200).unwrap().timestamp_millis_opt(1000).unwrap().time(),
|
|
||||||
NaiveTime::from_hms_opt(2, 0, 1).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,7 +14,7 @@ use crate::helpers::*;
|
||||||
use crate::kinds::{format_tag_basic, match_event_tag, Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS};
|
use crate::kinds::{format_tag_basic, match_event_tag, Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS};
|
||||||
use crate::task::{State, StateChange, Task, MARKER_PROPERTY};
|
use crate::task::{State, StateChange, Task, MARKER_PROPERTY};
|
||||||
use crate::tasks::{referenced_event, PropertyCollection, StateFilter, TasksRelay};
|
use crate::tasks::{referenced_event, PropertyCollection, StateFilter, TasksRelay};
|
||||||
use chrono::{DateTime, Local, TimeZone, Utc};
|
use chrono::Local;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use env_logger::{Builder, Target, WriteStyle};
|
use env_logger::{Builder, Target, WriteStyle};
|
||||||
|
@ -650,8 +650,7 @@ async fn main() -> Result<()> {
|
||||||
match arg {
|
match arg {
|
||||||
None => tasks.move_to(None),
|
None => tasks.move_to(None),
|
||||||
Some(arg) => {
|
Some(arg) => {
|
||||||
if parse_tracking_stamp(arg, Local.timestamp_millis_opt(tasks.get_position_timestamped().0.as_u64() as i64 * 1000).earliest())
|
if parse_tracking_stamp(arg).and_then(|stamp| tasks.track_at(stamp, None)).is_some() {
|
||||||
.and_then(|stamp| tasks.track_at(stamp, None)).is_some() {
|
|
||||||
println!("{}", tasks.times_tracked(15));
|
println!("{}", tasks.times_tracked(15));
|
||||||
}
|
}
|
||||||
// So the error message is not covered up
|
// So the error message is not covered up
|
||||||
|
|
25
src/tasks.rs
25
src/tasks.rs
|
@ -4,6 +4,13 @@ mod tests;
|
||||||
mod children_traversal;
|
mod children_traversal;
|
||||||
mod durations;
|
mod durations;
|
||||||
|
|
||||||
|
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::iter::{empty, once, FusedIterator};
|
||||||
|
use std::ops::{Deref, Div, Rem};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::event_sender::{EventSender, MostrMessage};
|
use crate::event_sender::{EventSender, MostrMessage};
|
||||||
use crate::hashtag::Hashtag;
|
use crate::hashtag::Hashtag;
|
||||||
use crate::helpers::{
|
use crate::helpers::{
|
||||||
|
@ -15,8 +22,6 @@ use crate::task::{State, StateChange, Task, MARKER_DEPENDS, MARKER_PARENT, MARKE
|
||||||
use crate::tasks::children_traversal::ChildrenTraversal;
|
use crate::tasks::children_traversal::ChildrenTraversal;
|
||||||
use crate::tasks::durations::{referenced_events, timestamps, Durations};
|
use crate::tasks::durations::{referenced_events, timestamps, Durations};
|
||||||
pub use crate::tasks::nostr_users::NostrUsers;
|
pub use crate::tasks::nostr_users::NostrUsers;
|
||||||
|
|
||||||
use chrono::{Local, TimeDelta};
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
@ -25,12 +30,6 @@ use nostr_sdk::{
|
||||||
SingleLetterTag, Tag, TagKind, Timestamp, Url,
|
SingleLetterTag, Tag, TagKind, Timestamp, Url,
|
||||||
};
|
};
|
||||||
use regex::bytes::Regex;
|
use regex::bytes::Regex;
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::iter::{empty, once, FusedIterator};
|
|
||||||
use std::ops::{Deref, Div, Rem};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::Duration;
|
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
|
||||||
const DEFAULT_PRIO: Prio = 25;
|
const DEFAULT_PRIO: Prio = 25;
|
||||||
|
@ -241,10 +240,6 @@ impl TasksRelay {
|
||||||
self.get_position_at(now()).1
|
self.get_position_at(now()).1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_position_timestamped(&self) -> (Timestamp, Option<EventId>) {
|
|
||||||
self.get_position_at(now())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sorting_key(&self, task: &Task) -> impl Ord {
|
fn sorting_key(&self, task: &Task) -> impl Ord {
|
||||||
self.sorting
|
self.sorting
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -282,7 +277,9 @@ impl TasksRelay {
|
||||||
label
|
label
|
||||||
} else {
|
} else {
|
||||||
format!("{}{}",
|
format!("{}{}",
|
||||||
if limit > times.len() || limit == usize::MAX { "All ".to_string() } else if limit < 20 { "Recent ".to_string() } else { format!("Latest {limit} Entries of ") },
|
if limit > times.len() || limit == usize::MAX { "All ".to_string() }
|
||||||
|
else if limit < 20 { "Recent ".to_string() }
|
||||||
|
else { format!("Latest {limit} Entries of ") },
|
||||||
label)
|
label)
|
||||||
}.italic(),
|
}.italic(),
|
||||||
×[times.len().saturating_sub(limit)..].join("\n"))
|
×[times.len().saturating_sub(limit)..].join("\n"))
|
||||||
|
@ -1163,7 +1160,7 @@ impl TasksRelay {
|
||||||
///
|
///
|
||||||
/// Returns false and prints a message if parsing failed
|
/// Returns false and prints a message if parsing failed
|
||||||
pub(crate) fn track_from(&mut self, str: &str) -> bool {
|
pub(crate) fn track_from(&mut self, str: &str) -> bool {
|
||||||
parse_tracking_stamp(str, None)
|
parse_tracking_stamp(str)
|
||||||
.and_then(|stamp| self.track_at(stamp, self.get_position()))
|
.and_then(|stamp| self.track_at(stamp, self.get_position()))
|
||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue