feat: revamp timestamp formatting helpers
This commit is contained in:
parent
3dca6a4b23
commit
ed1f482707
|
@ -57,41 +57,60 @@ pub fn parse_tracking_stamp(str: &str) -> Option<Timestamp> {
|
|||
})
|
||||
}
|
||||
|
||||
// For use in format strings but not possible, so need global find-replace
|
||||
pub const MAX_TIMESTAMP_WIDTH: u8 = 15;
|
||||
/// Format nostr Timestamp relative to local time
|
||||
/// with optional day specifier or full date depending on distance to today
|
||||
pub fn relative_datetimestamp(stamp: &Timestamp) -> String {
|
||||
/// Format DateTime easily comprehensible for human but unambiguous.
|
||||
/// Length may vary.
|
||||
pub fn format_datetime_relative(time: DateTime<Local>) -> String {
|
||||
let date = time.date_naive();
|
||||
let prefix =
|
||||
match Local::now()
|
||||
.date_naive()
|
||||
.signed_duration_since(date)
|
||||
.num_days() {
|
||||
-1 => "tomorrow ".into(),
|
||||
0 => "".into(),
|
||||
1 => "yesterday ".into(),
|
||||
-3..=3 => date.format("%a ").to_string(),
|
||||
//-10..=10 => date.format("%d. %a ").to_string(),
|
||||
-100..=100 => date.format("%b %d ").to_string(),
|
||||
_ => date.format("%y-%m-%d ").to_string(),
|
||||
};
|
||||
format!("{}{}", prefix, time.format("%H:%M"))
|
||||
}
|
||||
|
||||
/// Format a nostr timestamp with the given formatting function.
|
||||
pub fn format_as_datetime<F>(stamp: &Timestamp, formatter: F) -> String
|
||||
where
|
||||
F: Fn(DateTime<Local>) -> String,
|
||||
{
|
||||
match Local.timestamp_opt(stamp.as_u64() as i64, 0) {
|
||||
Single(time) => {
|
||||
let date = time.date_naive();
|
||||
let prefix = match Local::now()
|
||||
.date_naive()
|
||||
.signed_duration_since(date)
|
||||
.num_days()
|
||||
{
|
||||
-1 => "tomorrow ".into(),
|
||||
0 => "".into(),
|
||||
1 => "yesterday ".into(),
|
||||
2..=6 => date.format("last %a ").to_string(),
|
||||
_ => date.format("%y-%m-%d ").to_string(),
|
||||
};
|
||||
format!("{}{}", prefix, time.format("%H:%M"))
|
||||
}
|
||||
Single(time) => formatter(time),
|
||||
_ => stamp.to_human_datetime(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a nostr timestamp in a sensible comprehensive format
|
||||
pub fn local_datetimestamp(stamp: &Timestamp) -> String {
|
||||
format_stamp(stamp, "%y-%m-%d %a %H:%M")
|
||||
/// Format nostr Timestamp relative to local time
|
||||
/// with optional day specifier or full date depending on distance to today.
|
||||
pub fn format_timestamp_relative(stamp: &Timestamp) -> String {
|
||||
format_as_datetime(stamp, format_datetime_relative)
|
||||
}
|
||||
|
||||
/// Format a nostr timestamp with the given format
|
||||
pub fn format_stamp(stamp: &Timestamp, format: &str) -> String {
|
||||
match Local.timestamp_opt(stamp.as_u64() as i64, 0) {
|
||||
Single(time) => time.format(format).to_string(),
|
||||
_ => stamp.to_human_datetime(),
|
||||
/// Format nostr timestamp with the given format.
|
||||
pub fn format_timestamp(stamp: &Timestamp, format: &str) -> String {
|
||||
format_as_datetime(stamp, |time| time.format(format).to_string())
|
||||
}
|
||||
|
||||
/// Format nostr timestamp in a sensible comprehensive format with consistent length and consistent sorting.
|
||||
///
|
||||
/// Currently: 18 characters
|
||||
pub fn format_timestamp_local(stamp: &Timestamp) -> String {
|
||||
format_timestamp(stamp, "%y-%m-%d %a %H:%M")
|
||||
}
|
||||
|
||||
pub fn format_timestamp_relative_to(stamp: &Timestamp, reference: &Timestamp) -> String {
|
||||
// Rough difference in days
|
||||
match (stamp.as_u64() as i64 - reference.as_u64() as i64) / 80_000 {
|
||||
0 => format_timestamp(stamp, "%H:%M"),
|
||||
-3..=3 => format_timestamp(stamp, "%a %H:%M"),
|
||||
_ => format_timestamp_local(stamp),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -392,7 +392,7 @@ async fn main() -> Result<()> {
|
|||
None => {
|
||||
tasks.get_current_task().map_or_else(
|
||||
|| info!("With a task selected, use ,NOTE to attach NOTE and , to list all its notes"),
|
||||
|task| println!("{}", task.description_events().map(|e| format!("{} {}", local_datetimestamp(&e.created_at), e.content)).join("\n")),
|
||||
|task| println!("{}", task.description_events().map(|e| format!("{} {}", format_timestamp_local(&e.created_at), e.content)).join("\n")),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
@ -448,10 +448,11 @@ async fn main() -> Result<()> {
|
|||
parse_hour(arg, 1)
|
||||
.or_else(|| parse_date(arg).map(|utc| utc.with_timezone(&Local)))
|
||||
.map(|time| {
|
||||
info!("Filtering for tasks created after {}", time);
|
||||
info!("Filtering for tasks created after {}", format_datetime_relative(time));
|
||||
let threshold = time.to_utc().timestamp();
|
||||
tasks.set_filter(
|
||||
tasks.filtered_tasks(tasks.get_position_ref())
|
||||
.filter(|t| t.event.created_at.as_u64() as i64 > time.to_utc().timestamp())
|
||||
.filter(|t| t.event.created_at.as_u64() as i64 > threshold)
|
||||
.map(|t| t.event.id)
|
||||
.collect()
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ use itertools::Itertools;
|
|||
use log::{debug, error, info, trace, warn};
|
||||
use nostr_sdk::{Event, EventId, Kind, Tag, TagStandard, Timestamp};
|
||||
|
||||
use crate::helpers::{local_datetimestamp, some_non_empty};
|
||||
use crate::helpers::{format_timestamp_local, some_non_empty};
|
||||
use crate::kinds::{is_hashtag, TASK_KIND};
|
||||
|
||||
pub static MARKER_PARENT: &str = "parent";
|
||||
|
@ -156,7 +156,7 @@ impl Task {
|
|||
"parentid" => self.parent_id().map(|i| i.to_string()),
|
||||
"name" => Some(self.event.content.clone()),
|
||||
"pubkey" => Some(self.event.pubkey.to_string()),
|
||||
"created" => Some(local_datetimestamp(&self.event.created_at)),
|
||||
"created" => Some(format_timestamp_local(&self.event.created_at)),
|
||||
"kind" => Some(self.event.kind.to_string()),
|
||||
// Dynamic
|
||||
"status" => self.state_label().map(|c| c.to_string()),
|
||||
|
|
22
src/tasks.rs
22
src/tasks.rs
|
@ -6,7 +6,6 @@ use std::ops::{Div, Rem};
|
|||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Local;
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
|
@ -15,7 +14,7 @@ use nostr_sdk::prelude::Marker;
|
|||
use TagStandard::Hashtag;
|
||||
|
||||
use crate::{EventSender, MostrMessage};
|
||||
use crate::helpers::{format_stamp, local_datetimestamp, parse_tracking_stamp, relative_datetimestamp, some_non_empty};
|
||||
use crate::helpers::{format_timestamp_local, format_timestamp_relative, format_timestamp_relative_to, parse_tracking_stamp, some_non_empty};
|
||||
use crate::kinds::*;
|
||||
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State, Task, TaskState};
|
||||
|
||||
|
@ -189,7 +188,7 @@ impl Tasks {
|
|||
.join(" "));
|
||||
if new != last {
|
||||
// TODO alternate color with grey between days
|
||||
full.push(format!("{:>15} {}", relative_datetimestamp(&event.created_at), new.as_ref().unwrap_or(&"---".to_string())));
|
||||
full.push(format!("{} {}", format_timestamp_local(&event.created_at), new.as_ref().unwrap_or(&"---".to_string())));
|
||||
last = new;
|
||||
}
|
||||
}
|
||||
|
@ -206,18 +205,13 @@ impl Tasks {
|
|||
let mut iter = timestamps(set.iter(), &ids).tuples();
|
||||
while let Some(((start, _), (end, _))) = iter.next() {
|
||||
vec.push(format!("{} - {} by {}",
|
||||
local_datetimestamp(start),
|
||||
// Only use full stamp when ambiguous (>1day)
|
||||
if end.as_u64() - start.as_u64() > 80_000 {
|
||||
local_datetimestamp(end)
|
||||
} else {
|
||||
format_stamp(end, "%H:%M")
|
||||
},
|
||||
format_timestamp_local(start),
|
||||
format_timestamp_relative_to(end, start),
|
||||
self.get_author(key)))
|
||||
}
|
||||
iter.into_buffer()
|
||||
.for_each(|(stamp, _)|
|
||||
vec.push(format!("{} started by {}", local_datetimestamp(stamp), self.get_author(key))));
|
||||
vec.push(format!("{} started by {}", format_timestamp_local(stamp), self.get_author(key))));
|
||||
vec
|
||||
}).sorted_unstable(); // TODO sorting depends on timestamp format - needed to interleave different people
|
||||
(format!("Times Tracked on {:?}", self.get_task_title(&id)), Box::from(history))
|
||||
|
@ -409,10 +403,10 @@ impl Tasks {
|
|||
writeln!(
|
||||
lock,
|
||||
"Tracking since {} (total tracked time {}m) - {} since {}",
|
||||
tracking_stamp.map_or("?".to_string(), |t| relative_datetimestamp(&t)),
|
||||
tracking_stamp.map_or("?".to_string(), |t| format_timestamp_relative(&t)),
|
||||
self.time_tracked(*t.get_id()) / 60,
|
||||
state.get_label(),
|
||||
relative_datetimestamp(&state.time)
|
||||
format_timestamp_relative(&state.time)
|
||||
)?;
|
||||
writeln!(lock, "{}", t.descriptions().join("\n"))?;
|
||||
}
|
||||
|
@ -736,7 +730,7 @@ impl Tasks {
|
|||
}
|
||||
|
||||
pub(crate) fn track_at(&mut self, time: Timestamp, task: Option<EventId>) -> EventId {
|
||||
info!("{} from {}", task.map_or(String::from("Stopping time-tracking"), |id| format!("Tracking \"{}\"", self.get_task_title(&id))), relative_datetimestamp(&time));
|
||||
info!("{} from {}", task.map_or(String::from("Stopping time-tracking"), |id| format!("Tracking \"{}\"", self.get_task_title(&id))), format_timestamp_relative(&time));
|
||||
self.submit(
|
||||
build_tracking(task)
|
||||
.custom_created_at(time)
|
||||
|
|
Loading…
Reference in New Issue