just playing with tangled
at diffedit3 93 lines 3.2 kB view raw
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}