use mf2_runtime::{
DateFields, DateLength, DateStyle, DateTime, DateTimeOptions, Dir, ErrorSink, FnContext,
FormatError, Function, Options, Sink, SubPartSink, TimePrecision, Value,
};
use crate::literal::parse_literal;
use crate::options::{self, Kind, Own};
use crate::plan::{Backend, Plan};
use crate::zone;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum Role {
Function(Kind),
Unannotated,
}
#[derive(Clone, Copy, Debug)]
pub struct DateTimeFunction<B = crate::DefaultBackend> {
role: Role,
backend: B,
}
impl<B> DateTimeFunction<B> {
pub const fn datetime(backend: B) -> Self {
DateTimeFunction {
role: Role::Function(Kind::DateTime),
backend,
}
}
pub const fn date(backend: B) -> Self {
DateTimeFunction {
role: Role::Function(Kind::Date),
backend,
}
}
pub const fn time(backend: B) -> Self {
DateTimeFunction {
role: Role::Function(Kind::Time),
backend,
}
}
pub const fn unannotated(backend: B) -> Self {
DateTimeFunction {
role: Role::Unannotated,
backend,
}
}
pub const fn backend(&self) -> &B {
&self.backend
}
const fn kind(&self) -> Kind {
match self.role {
Role::Function(k) => k,
Role::Unannotated => Kind::DateTime,
}
}
}
pub fn operand<'a>(v: &Value<'a>) -> Option<DateTime<'a>> {
match v {
Value::DateTime(d) => Some(*d),
Value::Str(s) => parse_literal(s),
Value::Custom(c) => c
.as_date_time()
.or_else(|| c.as_str().and_then(parse_literal)),
_ => None,
}
}
fn parts<'a>(kind: Kind, own: &Own<'a>) -> DateTimeOptions<'a> {
let date = DateStyle {
fields: own.fields.unwrap_or(DateFields::YearMonthDay),
length: own.length.unwrap_or(DateLength::Medium),
};
let time = own.precision.unwrap_or(TimePrecision::Minute);
let mut o = DateTimeOptions::default();
match kind {
Kind::DateTime => {
o.date = Some(date);
o.time = Some(time);
o.time_zone_style = own.zone_style;
}
Kind::Date => o.date = Some(date),
Kind::Time => {
o.time = Some(time);
o.time_zone_style = own.zone_style;
}
}
o
}
pub fn literal_options<'s>(
function: Option<&str>,
literal: &dyn Fn(&str) -> Option<&'s str>,
) -> Option<DateTimeOptions<'s>> {
let kind = match function {
None => Kind::DateTime,
Some(name) => options::kind(name)?,
};
let own = match function {
None => Own::default(),
Some(_) => options::read_literals(kind, literal),
};
let mut o = parts(kind, &own);
o.hour12 = own.hour12;
o.calendar = own.calendar;
Some(o)
}
fn resolve<'a>(
kind: Kind,
cx: &FnContext<'_>,
operand: Option<&Value<'a>>,
own: &Own<'a>,
errs: &mut dyn ErrorSink,
) -> Option<DateTime<'a>> {
let Some(mut d) = operand.and_then(self::operand) else {
errs.error(FormatError::BadOperand);
return None;
};
let inherited = d.options;
let mut o = parts(kind, own);
o.hour12 = own.hour12.or(inherited.hour12);
o.calendar = own.calendar.or(inherited.calendar);
match zone::place(cx, &mut d, own.time_zone.or(inherited.time_zone), errs) {
Ok(z) => o.time_zone = z,
Err(e) => {
errs.error(e);
return None;
}
}
d.options = o;
Some(d)
}
struct First(Option<FormatError>);
impl ErrorSink for First {
fn error(&mut self, e: FormatError) {
if self.0.is_none() {
self.0 = Some(e);
}
}
}
impl<B> DateTimeFunction<B> {
fn value<'v>(
&self,
cx: &FnContext<'_>,
value: &Value<'v>,
) -> Result<DateTime<'v>, FormatError> {
match (self.role, value) {
(Role::Unannotated, v) => {
let mut first = First(None);
match resolve(Kind::DateTime, cx, Some(v), &Own::default(), &mut first) {
Some(d) if first.0.is_none() => Ok(d),
_ => Err(first.0.unwrap_or(FormatError::BadOperand)),
}
}
(Role::Function(_), Value::DateTime(d)) => Ok(*d),
(Role::Function(_), _) => Err(FormatError::MessageFunctionError),
}
}
}
impl<B: Backend> Function for DateTimeFunction<B> {
fn resolve<'a>(
&self,
cx: &FnContext<'_>,
operand: Option<&Value<'a>>,
options: &Options<'_, 'a>,
errs: &mut dyn ErrorSink,
) -> Option<Value<'a>> {
let kind = self.kind();
let own = options::read(kind, *options, errs);
resolve(kind, cx, operand, &own, errs).map(Value::DateTime)
}
fn formattable(&self, cx: &FnContext<'_>, value: &Value<'_>) -> Result<(), FormatError> {
let d = self.value(cx, value)?;
self.backend.supports(cx, &Plan::new(cx, &d))
}
fn format(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn Sink) {
if let Ok(d) = self.value(cx, value) {
self.backend.format(cx, &Plan::new(cx, &d), out);
}
}
fn format_parts(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn SubPartSink) {
if let Ok(d) = self.value(cx, value) {
self.backend.format_parts(cx, &Plan::new(cx, &d), out);
}
}
fn part_kind(&self) -> &'static str {
"datetime"
}
fn dir(&self, cx: &FnContext<'_>, value: &Value<'_>) -> Dir {
match self.value(cx, value) {
Ok(d) => self.backend.dir(cx, &Plan::new(cx, &d)),
Err(_) => Dir::Auto,
}
}
}