Skip to main content

mf2_fn_datetime/
function.rs

1//! The handlers: `:datetime`, `:date`, `:time` (datetime.md), and the
2//! handler of unannotated date/time values (`Registry::with_dates`).
3
4use mf2_runtime::{
5    DateFields, DateLength, DateStyle, DateTime, DateTimeOptions, Dir, ErrorSink, FnContext,
6    FormatError, Function, Options, Sink, SubPartSink, TimePrecision, Value,
7};
8
9use crate::literal::parse_literal;
10use crate::options::{self, Kind, Own};
11use crate::plan::{Backend, Plan};
12use crate::zone;
13
14/// What a handler is.
15#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
16enum Role {
17    /// A function: `:datetime`, `:date` or `:time`.
18    Function(Kind),
19    /// Unannotated date/time values: formatted as `:datetime` with its
20    /// defaults.
21    Unannotated,
22}
23
24/// A date/time handler over the backend `B`: `:datetime`, `:date`,
25/// `:time`, or the unannotated one — the statics [`crate::DATETIME`],
26/// [`crate::DATE`], [`crate::TIME`], [`crate::DATES`] over the default
27/// backend, or one built with the constructors over another.
28#[derive(Clone, Copy, Debug)]
29pub struct DateTimeFunction<B = crate::DefaultBackend> {
30    role: Role,
31    backend: B,
32}
33
34impl<B> DateTimeFunction<B> {
35    /// `:datetime`.
36    pub const fn datetime(backend: B) -> Self {
37        DateTimeFunction {
38            role: Role::Function(Kind::DateTime),
39            backend,
40        }
41    }
42
43    /// `:date`.
44    pub const fn date(backend: B) -> Self {
45        DateTimeFunction {
46            role: Role::Function(Kind::Date),
47            backend,
48        }
49    }
50
51    /// `:time`.
52    pub const fn time(backend: B) -> Self {
53        DateTimeFunction {
54            role: Role::Function(Kind::Time),
55            backend,
56        }
57    }
58
59    /// The handler of unannotated date/time values, for
60    /// `Registry::with_dates`: a value is formatted as `:datetime` with no
61    /// options would format it — its own override options apply — and if
62    /// that resolution reports an error, it is a fallback value with that
63    /// error. Named in a registry, it is `:datetime`.
64    pub const fn unannotated(backend: B) -> Self {
65        DateTimeFunction {
66            role: Role::Unannotated,
67            backend,
68        }
69    }
70
71    /// The backend.
72    pub const fn backend(&self) -> &B {
73        &self.backend
74    }
75
76    const fn kind(&self) -> Kind {
77        match self.role {
78            Role::Function(k) => k,
79            Role::Unannotated => Kind::DateTime,
80        }
81    }
82}
83
84/// The value of a date/time operand (datetime.md, "Date and Time
85/// Operands"): a date/time — an argument, or a date/time function's value
86/// with its options —, an application value's `as_date_time`, or a string
87/// that is a *date/time literal value* (a literal, a string argument, an
88/// application value's `as_str`). Anything else — a number, a fallback
89/// value, an application value with neither conversion — is none.
90pub fn operand<'a>(v: &Value<'a>) -> Option<DateTime<'a>> {
91    match v {
92        Value::DateTime(d) => Some(*d),
93        Value::Str(s) => parse_literal(s),
94        Value::Custom(c) => c
95            .as_date_time()
96            .or_else(|| c.as_str().and_then(parse_literal)),
97        _ => None,
98    }
99}
100
101/// The parts `kind` shows under its own options `own`, with their defaults:
102/// the date (`year-month-day`, `medium`) for `:datetime` and `:date`, the
103/// time (`minute`) for `:datetime` and `:time`, and their zone style.
104fn parts<'a>(kind: Kind, own: &Own<'a>) -> DateTimeOptions<'a> {
105    let date = DateStyle {
106        fields: own.fields.unwrap_or(DateFields::YearMonthDay),
107        length: own.length.unwrap_or(DateLength::Medium),
108    };
109    let time = own.precision.unwrap_or(TimePrecision::Minute);
110    let mut o = DateTimeOptions::default();
111    match kind {
112        Kind::DateTime => {
113            o.date = Some(date);
114            o.time = Some(time);
115            o.time_zone_style = own.zone_style;
116        }
117        Kind::Date => o.date = Some(date),
118        Kind::Time => {
119            o.time = Some(time);
120            o.time_zone_style = own.zone_style;
121        }
122    }
123    o
124}
125
126/// Build side (`mf2-locale-data`'s `icu.blob` slicing, `plans/02-catalog-format.md`
127/// §4.4): what an expression of `function` — `datetime`, `date` or `time`,
128/// or `None` for an unannotated date/time value, formatted as `:datetime`
129/// with no options — shows, from its literal options: `literal(name)` is
130/// the literal value of the option `name`, `None` when the expression has
131/// none or sets it by a variable. The result has the date and time parts
132/// with their defaults, the zone style, and `hour12` and `calendar` when a
133/// literal sets them — as the handler resolves them, since a non-override
134/// option set by a variable, or an invalid value, is ignored (*Bad Option*)
135/// and takes its default. Not the zone, nor what a date/time operand passes
136/// on. Another function name: `None`.
137pub fn literal_options<'s>(
138    function: Option<&str>,
139    literal: &dyn Fn(&str) -> Option<&'s str>,
140) -> Option<DateTimeOptions<'s>> {
141    let kind = match function {
142        None => Kind::DateTime,
143        Some(name) => options::kind(name)?,
144    };
145    let own = match function {
146        None => Own::default(),
147        Some(_) => options::read_literals(kind, literal),
148    };
149    let mut o = parts(kind, &own);
150    o.hour12 = own.hour12;
151    o.calendar = own.calendar;
152    Some(o)
153}
154
155/// Function resolution for `kind` with the options `own`: the operand's
156/// value (else *Bad Operand*, fallback), the options with their defaults,
157/// the override options inherited from the operand where the expression
158/// sets none, and the value moved into its time zone ([`zone::place`]).
159fn resolve<'a>(
160    kind: Kind,
161    cx: &FnContext<'_>,
162    operand: Option<&Value<'a>>,
163    own: &Own<'a>,
164    errs: &mut dyn ErrorSink,
165) -> Option<DateTime<'a>> {
166    let Some(mut d) = operand.and_then(self::operand) else {
167        errs.error(FormatError::BadOperand);
168        return None;
169    };
170    // Only the override options travel from the operand; datetime.md drops
171    // every other option an operand carries.
172    let inherited = d.options;
173    let mut o = parts(kind, own);
174    o.hour12 = own.hour12.or(inherited.hour12);
175    o.calendar = own.calendar.or(inherited.calendar);
176    match zone::place(cx, &mut d, own.time_zone.or(inherited.time_zone), errs) {
177        Ok(z) => o.time_zone = z,
178        Err(e) => {
179            errs.error(e);
180            return None;
181        }
182    }
183    d.options = o;
184    Some(d)
185}
186
187/// Keeps the first error.
188struct First(Option<FormatError>);
189
190impl ErrorSink for First {
191    fn error(&mut self, e: FormatError) {
192        if self.0.is_none() {
193            self.0 = Some(e);
194        }
195    }
196}
197
198impl<B> DateTimeFunction<B> {
199    /// The resolved value to format: `value` itself, or — unannotated — the
200    /// argument resolved now.
201    fn value<'v>(
202        &self,
203        cx: &FnContext<'_>,
204        value: &Value<'v>,
205    ) -> Result<DateTime<'v>, FormatError> {
206        match (self.role, value) {
207            (Role::Unannotated, v) => {
208                let mut first = First(None);
209                match resolve(Kind::DateTime, cx, Some(v), &Own::default(), &mut first) {
210                    Some(d) if first.0.is_none() => Ok(d),
211                    _ => Err(first.0.unwrap_or(FormatError::BadOperand)),
212                }
213            }
214            (Role::Function(_), Value::DateTime(d)) => Ok(*d),
215            (Role::Function(_), _) => Err(FormatError::MessageFunctionError),
216        }
217    }
218}
219
220impl<B: Backend> Function for DateTimeFunction<B> {
221    fn resolve<'a>(
222        &self,
223        cx: &FnContext<'_>,
224        operand: Option<&Value<'a>>,
225        options: &Options<'_, 'a>,
226        errs: &mut dyn ErrorSink,
227    ) -> Option<Value<'a>> {
228        let kind = self.kind();
229        let own = options::read(kind, *options, errs);
230        resolve(kind, cx, operand, &own, errs).map(Value::DateTime)
231    }
232
233    fn formattable(&self, cx: &FnContext<'_>, value: &Value<'_>) -> Result<(), FormatError> {
234        let d = self.value(cx, value)?;
235        self.backend.supports(cx, &Plan::new(cx, &d))
236    }
237
238    fn format(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn Sink) {
239        if let Ok(d) = self.value(cx, value) {
240            self.backend.format(cx, &Plan::new(cx, &d), out);
241        }
242    }
243
244    fn format_parts(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn SubPartSink) {
245        if let Ok(d) = self.value(cx, value) {
246            self.backend.format_parts(cx, &Plan::new(cx, &d), out);
247        }
248    }
249
250    fn part_kind(&self) -> &'static str {
251        "datetime"
252    }
253
254    fn dir(&self, cx: &FnContext<'_>, value: &Value<'_>) -> Dir {
255        match self.value(cx, value) {
256            Ok(d) => self.backend.dir(cx, &Plan::new(cx, &d)),
257            Err(_) => Dir::Auto,
258        }
259    }
260}