jj_cli/
time_util.rs

1use std::sync::LazyLock;
2
3use chrono::format::StrftimeItems;
4use jj_lib::backend::Timestamp;
5use jj_lib::backend::TimestampOutOfRange;
6
7/// Parsed formatting items which should never contain an error.
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct FormattingItems<'a> {
10    items: Vec<chrono::format::Item<'a>>,
11}
12
13impl<'a> FormattingItems<'a> {
14    /// Parses strftime-like format string.
15    pub fn parse(format: &'a str) -> Option<Self> {
16        // If the parsed format contained an error, format().to_string() would panic.
17        let items = StrftimeItems::new(format)
18            .map(|item| match item {
19                chrono::format::Item::Error => None,
20                _ => Some(item),
21            })
22            .collect::<Option<_>>()?;
23        Some(FormattingItems { items })
24    }
25
26    pub fn into_owned(self) -> FormattingItems<'static> {
27        use chrono::format::Item;
28        let items = self
29            .items
30            .into_iter()
31            .map(|item| match item {
32                Item::Literal(s) => Item::OwnedLiteral(s.into()),
33                Item::OwnedLiteral(s) => Item::OwnedLiteral(s),
34                Item::Space(s) => Item::OwnedSpace(s.into()),
35                Item::OwnedSpace(s) => Item::OwnedSpace(s),
36                Item::Numeric(spec, pad) => Item::Numeric(spec, pad),
37                Item::Fixed(spec) => Item::Fixed(spec),
38                Item::Error => Item::Error, // shouldn't exist, but just copy
39            })
40            .collect();
41        FormattingItems { items }
42    }
43}
44
45pub fn format_absolute_timestamp(timestamp: &Timestamp) -> Result<String, TimestampOutOfRange> {
46    static DEFAULT_FORMAT: LazyLock<FormattingItems> =
47        LazyLock::new(|| FormattingItems::parse("%Y-%m-%d %H:%M:%S.%3f %:z").unwrap());
48    format_absolute_timestamp_with(timestamp, &DEFAULT_FORMAT)
49}
50
51pub fn format_absolute_timestamp_with(
52    timestamp: &Timestamp,
53    format: &FormattingItems,
54) -> Result<String, TimestampOutOfRange> {
55    let datetime = timestamp.to_datetime()?;
56    Ok(datetime.format_with_items(format.items.iter()).to_string())
57}
58
59pub fn format_duration(
60    from: &Timestamp,
61    to: &Timestamp,
62    format: &timeago::Formatter,
63) -> Result<String, TimestampOutOfRange> {
64    let duration = to
65        .to_datetime()?
66        .signed_duration_since(from.to_datetime()?)
67        .to_std()
68        .map_err(|_: chrono::OutOfRangeError| TimestampOutOfRange)?;
69    Ok(format.convert(duration))
70}