just playing with tangled
at ig/vimdiffwarn 97 lines 3.3 kB view raw
1use chrono::format::StrftimeItems; 2use chrono::DateTime; 3use chrono::FixedOffset; 4use chrono::LocalResult; 5use chrono::TimeZone as _; 6use chrono::Utc; 7use jj_lib::backend::Timestamp; 8use once_cell::sync::Lazy; 9use thiserror::Error; 10 11/// Parsed formatting items which should never contain an error. 12#[derive(Clone, Debug, Eq, PartialEq)] 13pub struct FormattingItems<'a> { 14 items: Vec<chrono::format::Item<'a>>, 15} 16 17impl<'a> FormattingItems<'a> { 18 /// Parses strftime-like format string. 19 pub fn parse(format: &'a str) -> Option<Self> { 20 // If the parsed format contained an error, format().to_string() would panic. 21 let items = StrftimeItems::new(format) 22 .map(|item| match item { 23 chrono::format::Item::Error => None, 24 _ => Some(item), 25 }) 26 .collect::<Option<_>>()?; 27 Some(FormattingItems { items }) 28 } 29 30 pub fn into_owned(self) -> FormattingItems<'static> { 31 use chrono::format::Item; 32 let items = self 33 .items 34 .into_iter() 35 .map(|item| match item { 36 Item::Literal(s) => Item::OwnedLiteral(s.into()), 37 Item::OwnedLiteral(s) => Item::OwnedLiteral(s), 38 Item::Space(s) => Item::OwnedSpace(s.into()), 39 Item::OwnedSpace(s) => Item::OwnedSpace(s), 40 Item::Numeric(spec, pad) => Item::Numeric(spec, pad), 41 Item::Fixed(spec) => Item::Fixed(spec), 42 Item::Error => Item::Error, // shouldn't exist, but just copy 43 }) 44 .collect(); 45 FormattingItems { items } 46 } 47} 48 49#[derive(Debug, Error)] 50#[error("Out-of-range date")] 51pub struct TimestampOutOfRange; 52 53fn datetime_from_timestamp( 54 context: &Timestamp, 55) -> Result<DateTime<FixedOffset>, TimestampOutOfRange> { 56 let utc = match Utc.timestamp_opt( 57 context.timestamp.0.div_euclid(1000), 58 (context.timestamp.0.rem_euclid(1000)) as u32 * 1000000, 59 ) { 60 LocalResult::None => { 61 return Err(TimestampOutOfRange); 62 } 63 LocalResult::Single(x) => x, 64 LocalResult::Ambiguous(y, _z) => y, 65 }; 66 67 Ok(utc.with_timezone( 68 &FixedOffset::east_opt(context.tz_offset * 60) 69 .unwrap_or_else(|| FixedOffset::east_opt(0).unwrap()), 70 )) 71} 72 73pub fn format_absolute_timestamp(timestamp: &Timestamp) -> Result<String, TimestampOutOfRange> { 74 static DEFAULT_FORMAT: Lazy<FormattingItems> = 75 Lazy::new(|| FormattingItems::parse("%Y-%m-%d %H:%M:%S.%3f %:z").unwrap()); 76 format_absolute_timestamp_with(timestamp, &DEFAULT_FORMAT) 77} 78 79pub fn format_absolute_timestamp_with( 80 timestamp: &Timestamp, 81 format: &FormattingItems, 82) -> Result<String, TimestampOutOfRange> { 83 let datetime = datetime_from_timestamp(timestamp)?; 84 Ok(datetime.format_with_items(format.items.iter()).to_string()) 85} 86 87pub fn format_duration( 88 from: &Timestamp, 89 to: &Timestamp, 90 format: &timeago::Formatter, 91) -> Result<String, TimestampOutOfRange> { 92 let duration = datetime_from_timestamp(to)? 93 .signed_duration_since(datetime_from_timestamp(from)?) 94 .to_std() 95 .map_err(|_: chrono::OutOfRangeError| TimestampOutOfRange)?; 96 Ok(format.convert(duration)) 97}