use crate::{LocalizationError, Localize};
use fixed_decimal::{Decimal, FloatPrecision};
use icu_experimental::relativetime::{
options::Numeric, RelativeTimeFormatter, RelativeTimeFormatterOptions,
};
use icu_locale::{langid, LanguageIdentifier};
use jiff::{tz::TimeZone, SpanRound, Timestamp, Unit};
const FORMATTER_OPTIONS: RelativeTimeFormatterOptions = RelativeTimeFormatterOptions {
numeric: Numeric::Auto,
};
impl<W: std::io::Write> Localize<W> for Timestamp {
const CANONICAL_LOCALE: LanguageIdentifier = langid!("en-US");
fn available_locales(&self) -> Vec<LanguageIdentifier> {
vec![<Self as Localize<W>>::CANONICAL_LOCALE]
}
fn message_for_locale(
&self,
writer: &mut W,
locale: &LanguageIdentifier,
) -> Result<(), LocalizationError> {
let current_timestamp = Timestamp::now();
let current_datetime = current_timestamp.to_zoned(TimeZone::UTC).datetime();
let unformatted_span = current_timestamp
.since(*self)
.unwrap()
.round(
SpanRound::new()
.largest(Unit::Year)
.relative(current_datetime),
)
.unwrap();
let units: [(Unit, i64); 7] = [
(Unit::Year, unformatted_span.get_years() as i64),
(Unit::Month, unformatted_span.get_months() as i64),
(Unit::Week, unformatted_span.get_weeks() as i64),
(Unit::Day, unformatted_span.get_days() as i64),
(Unit::Hour, unformatted_span.get_hours() as i64),
(Unit::Minute, unformatted_span.get_minutes()),
(Unit::Second, unformatted_span.get_seconds()),
];
let selected_unit = units
.iter()
.find(|(_unit, value)| value.abs() > 0)
.map(|(unit, _value)| *unit)
.unwrap_or(Unit::Second);
let rounding_options = SpanRound::new()
.smallest(selected_unit)
.largest(selected_unit)
.relative(current_datetime);
let formatted_span = current_timestamp
.since(*self)
.unwrap()
.round(rounding_options)
.unwrap();
let selected_value = match selected_unit {
Unit::Year => formatted_span.get_years() as i64,
Unit::Month => formatted_span.get_months() as i64,
Unit::Week => formatted_span.get_weeks() as i64,
Unit::Day => formatted_span.get_days() as i64,
Unit::Hour => formatted_span.get_hours() as i64,
Unit::Minute => formatted_span.get_minutes(),
Unit::Second => formatted_span.get_seconds(),
_ => unreachable!(),
};
let formatter = match selected_unit {
Unit::Year => {
RelativeTimeFormatter::try_new_long_year(locale.into(), FORMATTER_OPTIONS)
}
Unit::Month => {
RelativeTimeFormatter::try_new_long_month(locale.into(), FORMATTER_OPTIONS)
}
Unit::Week => {
RelativeTimeFormatter::try_new_long_week(locale.into(), FORMATTER_OPTIONS)
}
Unit::Day => RelativeTimeFormatter::try_new_long_day(locale.into(), FORMATTER_OPTIONS),
Unit::Hour => {
RelativeTimeFormatter::try_new_long_hour(locale.into(), FORMATTER_OPTIONS)
}
Unit::Minute => {
RelativeTimeFormatter::try_new_long_minute(locale.into(), FORMATTER_OPTIONS)
}
Unit::Second => {
RelativeTimeFormatter::try_new_long_second(locale.into(), FORMATTER_OPTIONS)
}
_ => unreachable!(),
}
.unwrap();
let decimal =
Decimal::try_from_f64(selected_value as f64, FloatPrecision::Integer).unwrap();
let formatted_text = formatter.format(decimal);
writer.write_all(formatted_text.to_string().as_bytes())?;
writer.flush()?;
Ok(())
}
}