mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-24 21:13:47 +00:00
templater: propagate <out-of-range date> as an error
Since we've added runtime error handling, it makes sense to not stringify an error in time_util.
This commit is contained in:
parent
2007b9be53
commit
1d4860c9b5
3 changed files with 41 additions and 28 deletions
|
@ -668,7 +668,7 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a>>(
|
|||
let now = Timestamp::now();
|
||||
let format = timeago::Formatter::new();
|
||||
let out_property = TemplateFunction::new(self_property, move |timestamp| {
|
||||
Ok(time_util::format_duration(×tamp, &now, &format))
|
||||
Ok(time_util::format_duration(×tamp, &now, &format)?)
|
||||
});
|
||||
Ok(language.wrap_string(out_property))
|
||||
});
|
||||
|
@ -684,7 +684,7 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a>>(
|
|||
let out_property = TemplateFunction::new(self_property, move |timestamp| {
|
||||
Ok(time_util::format_absolute_timestamp_with(
|
||||
×tamp, &format,
|
||||
))
|
||||
)?)
|
||||
});
|
||||
Ok(language.wrap_string(out_property))
|
||||
});
|
||||
|
@ -728,7 +728,7 @@ fn builtin_timestamp_range_methods<'a, L: TemplateLanguage<'a>>(
|
|||
|language, _build_ctx, self_property, function| {
|
||||
template_parser::expect_no_arguments(function)?;
|
||||
let out_property =
|
||||
TemplateFunction::new(self_property, |time_range| Ok(time_range.duration()));
|
||||
TemplateFunction::new(self_property, |time_range| Ok(time_range.duration()?));
|
||||
Ok(language.wrap_string(out_property))
|
||||
},
|
||||
);
|
||||
|
|
|
@ -84,7 +84,10 @@ impl Template<()> for &str {
|
|||
|
||||
impl Template<()> for Timestamp {
|
||||
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
|
||||
formatter.write_str(&time_util::format_absolute_timestamp(self))
|
||||
match time_util::format_absolute_timestamp(self) {
|
||||
Ok(formatted) => formatter.write_str(&formatted),
|
||||
Err(err) => format_error_inline(formatter, &err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,14 +100,14 @@ pub struct TimestampRange {
|
|||
|
||||
impl TimestampRange {
|
||||
// TODO: Introduce duration type, and move formatting to it.
|
||||
pub fn duration(&self) -> String {
|
||||
pub fn duration(&self) -> Result<String, time_util::TimestampOutOfRange> {
|
||||
let mut f = timeago::Formatter::new();
|
||||
f.min_unit(timeago::TimeUnit::Microseconds).ago("");
|
||||
let duration = time_util::format_duration(&self.start, &self.end, &f);
|
||||
let duration = time_util::format_duration(&self.start, &self.end, &f)?;
|
||||
if duration == "now" {
|
||||
"less than a microsecond".to_owned()
|
||||
Ok("less than a microsecond".to_owned())
|
||||
} else {
|
||||
duration
|
||||
Ok(duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use chrono::format::StrftimeItems;
|
|||
use chrono::{DateTime, FixedOffset, LocalResult, TimeZone, Utc};
|
||||
use jj_lib::backend::Timestamp;
|
||||
use once_cell::sync::Lazy;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Parsed formatting items which should never contain an error.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
@ -41,43 +42,52 @@ impl<'a> FormattingItems<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn datetime_from_timestamp(context: &Timestamp) -> Option<DateTime<FixedOffset>> {
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Out-of-range date")]
|
||||
pub struct TimestampOutOfRange;
|
||||
|
||||
fn datetime_from_timestamp(
|
||||
context: &Timestamp,
|
||||
) -> Result<DateTime<FixedOffset>, TimestampOutOfRange> {
|
||||
let utc = match Utc.timestamp_opt(
|
||||
context.timestamp.0.div_euclid(1000),
|
||||
(context.timestamp.0.rem_euclid(1000)) as u32 * 1000000,
|
||||
) {
|
||||
LocalResult::None => {
|
||||
return None;
|
||||
return Err(TimestampOutOfRange);
|
||||
}
|
||||
LocalResult::Single(x) => x,
|
||||
LocalResult::Ambiguous(y, _z) => y,
|
||||
};
|
||||
|
||||
Some(
|
||||
utc.with_timezone(
|
||||
&FixedOffset::east_opt(context.tz_offset * 60)
|
||||
.unwrap_or_else(|| FixedOffset::east_opt(0).unwrap()),
|
||||
),
|
||||
)
|
||||
Ok(utc.with_timezone(
|
||||
&FixedOffset::east_opt(context.tz_offset * 60)
|
||||
.unwrap_or_else(|| FixedOffset::east_opt(0).unwrap()),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn format_absolute_timestamp(timestamp: &Timestamp) -> String {
|
||||
pub fn format_absolute_timestamp(timestamp: &Timestamp) -> Result<String, TimestampOutOfRange> {
|
||||
static DEFAULT_FORMAT: Lazy<FormattingItems> =
|
||||
Lazy::new(|| FormattingItems::parse("%Y-%m-%d %H:%M:%S.%3f %:z").unwrap());
|
||||
format_absolute_timestamp_with(timestamp, &DEFAULT_FORMAT)
|
||||
}
|
||||
|
||||
pub fn format_absolute_timestamp_with(timestamp: &Timestamp, format: &FormattingItems) -> String {
|
||||
match datetime_from_timestamp(timestamp) {
|
||||
Some(datetime) => datetime.format_with_items(format.items.iter()).to_string(),
|
||||
None => "<out-of-range date>".to_string(),
|
||||
}
|
||||
pub fn format_absolute_timestamp_with(
|
||||
timestamp: &Timestamp,
|
||||
format: &FormattingItems,
|
||||
) -> Result<String, TimestampOutOfRange> {
|
||||
let datetime = datetime_from_timestamp(timestamp)?;
|
||||
Ok(datetime.format_with_items(format.items.iter()).to_string())
|
||||
}
|
||||
|
||||
pub fn format_duration(from: &Timestamp, to: &Timestamp, format: &timeago::Formatter) -> String {
|
||||
datetime_from_timestamp(from)
|
||||
.zip(datetime_from_timestamp(to))
|
||||
.and_then(|(from, to)| to.signed_duration_since(from).to_std().ok())
|
||||
.map(|duration| format.convert(duration))
|
||||
.unwrap_or_else(|| "<out-of-range date>".to_string())
|
||||
pub fn format_duration(
|
||||
from: &Timestamp,
|
||||
to: &Timestamp,
|
||||
format: &timeago::Formatter,
|
||||
) -> Result<String, TimestampOutOfRange> {
|
||||
let duration = datetime_from_timestamp(to)?
|
||||
.signed_duration_since(datetime_from_timestamp(from)?)
|
||||
.to_std()
|
||||
.map_err(|_: chrono::OutOfRangeError| TimestampOutOfRange)?;
|
||||
Ok(format.convert(duration))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue