Skip to content

Commit

Permalink
templater: propagate <out-of-range date> as an error
Browse files Browse the repository at this point in the history
Since we've added runtime error handling, it makes sense to not stringify an
error in time_util.
  • Loading branch information
yuja committed Feb 28, 2024
1 parent 2007b9b commit 1d4860c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 28 deletions.
6 changes: 3 additions & 3 deletions cli/src/template_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(&timestamp, &now, &format))
Ok(time_util::format_duration(&timestamp, &now, &format)?)
});
Ok(language.wrap_string(out_property))
});
Expand All @@ -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(
&timestamp, &format,
))
)?)
});
Ok(language.wrap_string(out_property))
});
Expand Down Expand Up @@ -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))
},
);
Expand Down
13 changes: 8 additions & 5 deletions cli/src/templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
}

Expand All @@ -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)
}
}
}
Expand Down
50 changes: 30 additions & 20 deletions cli/src/time_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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))
}

0 comments on commit 1d4860c

Please sign in to comment.