just playing with tangled
1use chrono::format::StrftimeItems;
2use chrono::{DateTime, FixedOffset, LocalResult, TimeZone, Utc};
3use jj_lib::backend::Timestamp;
4use once_cell::sync::Lazy;
5use thiserror::Error;
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
45#[derive(Debug, Error)]
46#[error("Out-of-range date")]
47pub struct TimestampOutOfRange;
48
49fn datetime_from_timestamp(
50 context: &Timestamp,
51) -> Result<DateTime<FixedOffset>, TimestampOutOfRange> {
52 let utc = match Utc.timestamp_opt(
53 context.timestamp.0.div_euclid(1000),
54 (context.timestamp.0.rem_euclid(1000)) as u32 * 1000000,
55 ) {
56 LocalResult::None => {
57 return Err(TimestampOutOfRange);
58 }
59 LocalResult::Single(x) => x,
60 LocalResult::Ambiguous(y, _z) => y,
61 };
62
63 Ok(utc.with_timezone(
64 &FixedOffset::east_opt(context.tz_offset * 60)
65 .unwrap_or_else(|| FixedOffset::east_opt(0).unwrap()),
66 ))
67}
68
69pub fn format_absolute_timestamp(timestamp: &Timestamp) -> Result<String, TimestampOutOfRange> {
70 static DEFAULT_FORMAT: Lazy<FormattingItems> =
71 Lazy::new(|| FormattingItems::parse("%Y-%m-%d %H:%M:%S.%3f %:z").unwrap());
72 format_absolute_timestamp_with(timestamp, &DEFAULT_FORMAT)
73}
74
75pub fn format_absolute_timestamp_with(
76 timestamp: &Timestamp,
77 format: &FormattingItems,
78) -> Result<String, TimestampOutOfRange> {
79 let datetime = datetime_from_timestamp(timestamp)?;
80 Ok(datetime.format_with_items(format.items.iter()).to_string())
81}
82
83pub fn format_duration(
84 from: &Timestamp,
85 to: &Timestamp,
86 format: &timeago::Formatter,
87) -> Result<String, TimestampOutOfRange> {
88 let duration = datetime_from_timestamp(to)?
89 .signed_duration_since(datetime_from_timestamp(from)?)
90 .to_std()
91 .map_err(|_: chrono::OutOfRangeError| TimestampOutOfRange)?;
92 Ok(format.convert(duration))
93}