#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![no_std]
#![forbid(unsafe_code)]
extern crate alloc;
use alloc::string::String;
use mf2_runtime::Host;
use unicode_normalization::UnicodeNormalization;
#[derive(Clone, Copy, Default, Debug)]
pub struct StdHost;
pub static HOST: StdHost = StdHost;
impl Host for StdHost {
fn nfc<'a>(&self, s: &'a str, buf: &'a mut String) -> &'a str {
buf.clear();
buf.extend(s.nfc());
buf
}
fn f64_to_text<'b>(&self, x: f64, buf: &'b mut [u8; 32]) -> Option<&'b str> {
if !x.is_finite() {
return None;
}
let mut b = ryu::Buffer::new();
let text = b.format_finite(x).as_bytes();
let out = buf.get_mut(..text.len())?;
out.copy_from_slice(text);
core::str::from_utf8(out).ok()
}
fn zone_offset(&self, zone: &str, epoch_ms: i64) -> Option<i32> {
let tz = jiff::tz::TimeZoneDatabase::bundled().get(zone).ok()?;
let t = jiff::Timestamp::from_millisecond(epoch_ms).ok()?;
Some(tz.to_offset(t).seconds())
}
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use mf2_runtime::Host;
use super::HOST;
#[test]
fn nfc() {
let mut buf = String::new();
assert_eq!(HOST.nfc("\u{1E0A}\u{0323}", &mut buf), "\u{1E0C}\u{0307}");
let mut buf = String::new();
assert_eq!(HOST.nfc("e\u{301}", &mut buf), "\u{e9}");
}
#[test]
fn floats() {
let mut buf = [0u8; 32];
for (x, want) in [
(4.2, "4.2"),
(1.0, "1.0"),
(-0.5, "-0.5"),
(1e21, "1e21"),
(1.5e-7, "1.5e-7"),
(f64::MAX, "1.7976931348623157e308"),
(-f64::MIN_POSITIVE, "-2.2250738585072014e-308"),
] {
assert_eq!(HOST.f64_to_text(x, &mut buf), Some(want));
}
assert_eq!(HOST.f64_to_text(f64::NAN, &mut buf), None);
}
fn at(year: i32, month: u8, day: u8, hour: i64, minute: i64, second: i64) -> i64 {
let days =
mf2_runtime::Date::new(year, month, day).map_or(0, mf2_runtime::Date::days_since_epoch);
((days * 24 + hour) * 60 + minute) * 60_000 + second * 1000
}
#[test]
fn zone_offsets() {
const H: i32 = 3600;
for (zone, t, want) in [
("Europe/Paris", at(2006, 3, 26, 0, 59, 59), H),
("Europe/Paris", at(2006, 3, 26, 1, 0, 0), 2 * H),
("Europe/Paris", at(2006, 10, 29, 0, 59, 59), 2 * H),
("Europe/Paris", at(2006, 10, 29, 1, 0, 0), H),
("America/New_York", at(2021, 3, 14, 6, 59, 59), -5 * H),
("America/New_York", at(2021, 3, 14, 7, 0, 0), -4 * H),
("America/New_York", at(2021, 11, 7, 5, 59, 59), -4 * H),
("America/New_York", at(2021, 11, 7, 6, 0, 0), -5 * H),
("America/New_York", at(2006, 3, 20, 12, 0, 0), -5 * H),
("America/New_York", at(2006, 4, 2, 7, 0, 0), -4 * H),
("Australia/Sydney", at(2006, 1, 2, 0, 0, 0), 11 * H),
("Australia/Sydney", at(2006, 7, 2, 0, 0, 0), 10 * H),
("Asia/Kolkata", at(2006, 1, 2, 0, 0, 0), 5 * H + 1800),
("Asia/Kathmandu", at(2006, 1, 2, 0, 0, 0), 5 * H + 2700),
("UTC", at(2006, 1, 2, 0, 0, 0), 0),
("Etc/GMT+5", at(2006, 1, 2, 0, 0, 0), -5 * H),
("europe/paris", at(2006, 1, 2, 0, 0, 0), H),
("Europe/Paris", at(1900, 1, 1, 0, 0, 0), 561),
] {
assert_eq!(HOST.zone_offset(zone, t), Some(want), "{zone} at {t}");
}
assert_eq!(HOST.zone_offset("Mars/Olympus_Mons", 0), None);
assert_eq!(HOST.zone_offset("", 0), None);
assert_eq!(HOST.zone_offset("Europe/Paris", i64::MAX), None);
}
}