1use mf2_runtime::{DateFields, FnContext, Sink, TimePrecision, ZoneOption, ZoneStyle};
6
7use crate::plan::{Backend, Plan};
8
9#[derive(Clone, Copy, Default, Debug)]
28pub struct Neutral;
29
30const DIGITS: [&str; 10] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
31
32const WEEKDAYS: [&str; 7] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
33
34fn digits(out: &mut dyn Sink, n: u32, width: usize) {
37 const N: usize = 6;
38 let mut buf = [0u8; N];
39 let mut len = 0;
40 let mut rest = n;
41 while len < N {
42 if let Some(b) = buf.get_mut(N - 1 - len) {
43 #[allow(clippy::cast_possible_truncation)]
45 let d = (rest % 10) as u8;
46 *b = d;
47 }
48 rest /= 10;
49 len += 1;
50 if rest == 0 && len >= width {
51 break;
52 }
53 }
54 for &d in buf.get(N - len..).unwrap_or(&[]) {
55 out.push_str(DIGITS.get(usize::from(d)).copied().unwrap_or("0"));
56 }
57}
58
59fn offset(out: &mut dyn Sink, seconds: i32) {
61 out.push_str(if seconds < 0 { "-" } else { "+" });
62 let s = seconds.unsigned_abs();
63 digits(out, s / 3600, 2);
64 out.push_str(":");
65 digits(out, s / 60 % 60, 2);
66 if !s.is_multiple_of(60) {
67 out.push_str(":");
68 digits(out, s % 60, 2);
69 }
70}
71
72impl Backend for Neutral {
73 fn format(&self, _cx: &FnContext<'_>, plan: &Plan<'_>, out: &mut dyn Sink) {
74 let o = plan.options;
75 let mut first = true;
76 let mut space = |out: &mut dyn Sink| {
77 if !first {
78 out.push_str(" ");
79 }
80 first = false;
81 };
82 if let Some(style) = o.date {
83 let d = plan.date;
84 let (year, month, weekday) = match style.fields {
85 DateFields::YearMonthDayWeekday => (true, true, true),
86 DateFields::MonthDay => (false, true, false),
87 DateFields::MonthDayWeekday => (false, true, true),
88 DateFields::DayWeekday | DateFields::Weekday => (false, false, true),
89 _ => (true, true, false),
92 };
93 if style.fields != DateFields::Weekday {
94 space(out);
95 if year {
96 let y = d.year();
97 if (0..=9999).contains(&y) {
98 digits(out, y.unsigned_abs(), 4);
99 } else {
100 out.push_str(if y < 0 { "-" } else { "+" });
101 digits(out, y.unsigned_abs(), 6);
102 }
103 } else {
104 out.push_str("-");
105 }
106 out.push_str("-");
107 if month {
108 digits(out, u32::from(d.month()), 2);
109 }
110 out.push_str("-");
111 digits(out, u32::from(d.day()), 2);
112 }
113 if weekday {
114 space(out);
115 let w = usize::from(d.weekday().saturating_sub(1));
116 out.push_str(WEEKDAYS.get(w).copied().unwrap_or(""));
117 }
118 }
119 if let Some(precision) = o.time {
120 space(out);
121 let h = plan.time.hour();
122 let twelve = o.hour12 == Some(true);
123 let shown = match (twelve, h % 12) {
124 (true, 0) => 12,
125 (true, h12) => h12,
126 (false, _) => h,
127 };
128 digits(out, u32::from(shown), 2);
129 if precision != TimePrecision::Hour {
130 out.push_str(":");
131 digits(out, u32::from(plan.time.minute()), 2);
132 }
133 if precision == TimePrecision::Second {
134 out.push_str(":");
135 digits(out, u32::from(plan.time.second()), 2);
136 }
137 if twelve {
138 out.push_str(if h < 12 { " AM" } else { " PM" });
139 }
140 }
141 if let Some(style) = o.time_zone_style {
142 space(out);
143 match (style, plan.zone, plan.offset) {
144 (ZoneStyle::Short, _, Some(0)) => out.push_str("Z"),
145 (ZoneStyle::Short, _, Some(s)) | (ZoneStyle::Long, ZoneOption::Offset(s), _) => {
146 offset(out, s);
147 }
148 (_, ZoneOption::Named(n), _) => out.push_str(n),
149 (ZoneStyle::Short, ZoneOption::Offset(s), None) => offset(out, s),
150 _ => out.push_str("UTC"),
152 }
153 }
154 }
155}