2024-08-21 07:14:01 +00:00
|
|
|
use std::ops::Sub;
|
2024-08-06 20:01:59 +00:00
|
|
|
|
2024-08-14 18:49:36 +00:00
|
|
|
use chrono::LocalResult::Single;
|
2024-09-05 10:50:50 +00:00
|
|
|
use chrono::{DateTime, Local, NaiveTime, TimeDelta, TimeZone, Utc};
|
2024-08-06 20:01:59 +00:00
|
|
|
use log::{debug, error, info, trace, warn};
|
2024-08-14 18:49:36 +00:00
|
|
|
use nostr_sdk::Timestamp;
|
2024-08-06 20:01:59 +00:00
|
|
|
|
2024-08-25 08:16:05 +00:00
|
|
|
pub const CHARACTER_THRESHOLD: usize = 3;
|
|
|
|
|
2024-11-15 16:18:30 +00:00
|
|
|
pub fn to_string_or_default(arg: Option<impl ToString>) -> String {
|
|
|
|
arg.map(|arg| arg.to_string()).unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
2024-08-06 20:01:59 +00:00
|
|
|
pub fn some_non_empty(str: &str) -> Option<String> {
|
2024-08-08 12:09:39 +00:00
|
|
|
if str.is_empty() { None } else { Some(str.to_string()) }
|
2024-08-06 20:01:59 +00:00
|
|
|
}
|
|
|
|
|
2024-09-22 18:05:05 +00:00
|
|
|
pub fn trim_start_count(str: &str, char: char) -> (&str, usize) {
|
|
|
|
let len = str.len();
|
|
|
|
let result = str.trim_start_matches(char);
|
|
|
|
let dots = len - result.len();
|
|
|
|
(result, dots)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait ToTimestamp {
|
|
|
|
fn to_timestamp(&self) -> Timestamp;
|
|
|
|
}
|
|
|
|
impl<T: TimeZone> ToTimestamp for DateTime<T> {
|
|
|
|
fn to_timestamp(&self) -> Timestamp {
|
|
|
|
let stamp = self.to_utc().timestamp();
|
|
|
|
if let Some(t) = 0u64.checked_add_signed(stamp) {
|
|
|
|
Timestamp::from(t)
|
|
|
|
} else { Timestamp::zero() }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-21 07:14:01 +00:00
|
|
|
/// Parses the hour from a plain number in the String,
|
|
|
|
/// with max of max_future hours into the future.
|
2024-12-05 13:39:47 +00:00
|
|
|
// TODO parse HHMM as well
|
2024-08-21 07:14:01 +00:00
|
|
|
pub fn parse_hour(str: &str, max_future: i64) -> Option<DateTime<Local>> {
|
|
|
|
str.parse::<u32>().ok().and_then(|hour| {
|
|
|
|
let now = Local::now();
|
|
|
|
#[allow(deprecated)]
|
|
|
|
now.date().and_hms_opt(hour, 0, 0).map(|time| {
|
|
|
|
if time - now > TimeDelta::hours(max_future) {
|
|
|
|
time.sub(TimeDelta::days(1))
|
|
|
|
} else {
|
|
|
|
time
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-08-19 19:16:19 +00:00
|
|
|
pub fn parse_date(str: &str) -> Option<DateTime<Utc>> {
|
2024-08-19 08:45:12 +00:00
|
|
|
// Using two libraries for better exhaustiveness, see https://github.com/uutils/parse_datetime/issues/84
|
2024-08-19 19:16:19 +00:00
|
|
|
match interim::parse_date_string(str, Local::now(), interim::Dialect::Us) {
|
2024-08-19 08:45:12 +00:00
|
|
|
Ok(date) => Some(date.to_utc()),
|
|
|
|
Err(e) => {
|
2024-08-19 19:16:19 +00:00
|
|
|
match parse_datetime::parse_datetime_at_date(Local::now(), str) {
|
2024-08-19 08:45:12 +00:00
|
|
|
Ok(date) => Some(date.to_utc()),
|
|
|
|
Err(_) => {
|
2024-08-25 08:16:05 +00:00
|
|
|
warn!("Could not parse date from \"{str}\": {e}");
|
2024-08-19 08:45:12 +00:00
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-26 19:37:29 +00:00
|
|
|
}.map(|time| {
|
|
|
|
// TODO properly map date without time to day start, also support intervals
|
|
|
|
if str.chars().any(|c| c.is_numeric()) {
|
|
|
|
time
|
|
|
|
} else {
|
|
|
|
#[allow(deprecated)]
|
|
|
|
time.date().and_time(NaiveTime::default()).unwrap()
|
|
|
|
}
|
|
|
|
})
|
2024-08-19 19:16:19 +00:00
|
|
|
}
|
|
|
|
|
2024-08-22 08:13:35 +00:00
|
|
|
/// Turn a human-readable relative timestamp into a nostr Timestamp.
|
|
|
|
/// - Plain number as hour, 18 hours back or 6 hours forward
|
|
|
|
/// - Number with prefix as minute offset
|
|
|
|
/// - Otherwise try to parse a relative date
|
2024-08-19 19:16:19 +00:00
|
|
|
pub fn parse_tracking_stamp(str: &str) -> Option<Timestamp> {
|
2024-08-22 08:13:35 +00:00
|
|
|
if let Some(num) = parse_hour(str, 6) {
|
2024-09-22 18:05:05 +00:00
|
|
|
return Some(num.to_timestamp());
|
2024-08-22 08:13:35 +00:00
|
|
|
}
|
2024-08-19 19:16:19 +00:00
|
|
|
let stripped = str.trim().trim_start_matches('+').trim_start_matches("in ");
|
|
|
|
if let Ok(num) = stripped.parse::<i64>() {
|
|
|
|
return Some(Timestamp::from(Timestamp::now().as_u64().saturating_add_signed(num * 60)));
|
|
|
|
}
|
|
|
|
parse_date(str).and_then(|time| {
|
2024-08-22 08:13:35 +00:00
|
|
|
let stamp = time.to_utc().timestamp();
|
|
|
|
if stamp > 0 {
|
|
|
|
Some(Timestamp::from(stamp as u64))
|
2024-08-19 08:45:12 +00:00
|
|
|
} else {
|
|
|
|
warn!("Can only track times after 1970!");
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-08-21 08:56:15 +00:00
|
|
|
/// 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(),
|
2024-09-05 10:50:50 +00:00
|
|
|
//-3..=3 => date.format("%a ").to_string(),
|
|
|
|
-10..=10 => date.format("%d. %a ").to_string(),
|
|
|
|
-100..=100 => date.format("%a %b %d ").to_string(),
|
|
|
|
_ => date.format("%y-%m-%d %a ").to_string(),
|
2024-08-21 08:56:15 +00:00
|
|
|
};
|
|
|
|
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,
|
|
|
|
{
|
2024-11-08 10:30:08 +00:00
|
|
|
match Local.timestamp_opt(stamp.as_u64() as i64 + 1, 0) {
|
2024-08-21 08:56:15 +00:00
|
|
|
Single(time) => formatter(time),
|
2024-11-14 17:53:52 +00:00
|
|
|
_ => stamp.to_human_datetime().to_string(),
|
2024-08-14 18:49:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-21 08:56:15 +00:00
|
|
|
/// 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)
|
2024-08-14 18:49:36 +00:00
|
|
|
}
|
|
|
|
|
2024-08-21 08:56:15 +00:00
|
|
|
/// 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")
|
2024-08-14 18:49:36 +00:00
|
|
|
}
|
|
|
|
|
2024-11-20 18:34:40 +00:00
|
|
|
/// Format nostr timestamp with seconds precision.
|
|
|
|
pub fn format_timestamp_full(stamp: &Timestamp) -> String {
|
|
|
|
format_timestamp(stamp, "%y-%m-%d %a %H:%M:%S")
|
|
|
|
}
|
|
|
|
|
2024-08-21 08:56:15 +00:00
|
|
|
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),
|
|
|
|
}
|
2024-11-08 10:30:08 +00:00
|
|
|
}
|