1 //! Formatting helpers for a `Date`.
2 
3 #![allow(non_snake_case)]
4 
5 use crate::{
6     error,
7     format::{
8         parse::{
9             consume_padding, try_consume_digits, try_consume_exact_digits, try_consume_first_match,
10         },
11         Padding, ParseResult, ParsedItems,
12     },
13     Date, Weekday,
14 };
15 #[cfg(not(feature = "std"))]
16 use alloc::string::ToString;
17 use core::{
18     fmt::{self, Formatter},
19     num::{NonZeroU16, NonZeroU8},
20 };
21 #[allow(unused_imports)]
22 use standback::prelude::*;
23 
24 /// Array of weekdays that corresponds to the localized values. This can be
25 /// zipped via an iterator to perform parsing easily.
26 const WEEKDAYS: [Weekday; 7] = [
27     Weekday::Monday,
28     Weekday::Tuesday,
29     Weekday::Wednesday,
30     Weekday::Thursday,
31     Weekday::Friday,
32     Weekday::Saturday,
33     Weekday::Sunday,
34 ];
35 
36 /// Full weekday names
37 const WEEKDAYS_FULL: [&str; 7] = [
38     "Monday",
39     "Tuesday",
40     "Wednesday",
41     "Thursday",
42     "Friday",
43     "Saturday",
44     "Sunday",
45 ];
46 
47 /// Abbreviated weekday names
48 const WEEKDAYS_ABBR: [&str; 7] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
49 
50 /// Full month names
51 const MONTHS_FULL: [&str; 12] = [
52     "January",
53     "February",
54     "March",
55     "April",
56     "May",
57     "June",
58     "July",
59     "August",
60     "September",
61     "October",
62     "November",
63     "December",
64 ];
65 
66 /// Abbreviated month names
67 const MONTHS_ABBR: [&str; 12] = [
68     "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
69 ];
70 
71 /// Short day of the week
fmt_a(f: &mut Formatter<'_>, date: Date) -> fmt::Result72 pub(crate) fn fmt_a(f: &mut Formatter<'_>, date: Date) -> fmt::Result {
73     f.write_str(WEEKDAYS_ABBR[date.weekday().number_days_from_monday() as usize])
74 }
75 
76 /// Short day of the week
parse_a(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()>77 pub(crate) fn parse_a(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()> {
78     items.weekday = Some(
79         try_consume_first_match(s, WEEKDAYS_ABBR.iter().zip(WEEKDAYS.iter().cloned()))
80             .ok_or(error::Parse::InvalidDayOfWeek)?,
81     );
82 
83     Ok(())
84 }
85 
86 /// Day of the week
fmt_A(f: &mut Formatter<'_>, date: Date) -> fmt::Result87 pub(crate) fn fmt_A(f: &mut Formatter<'_>, date: Date) -> fmt::Result {
88     f.write_str(WEEKDAYS_FULL[date.weekday().number_days_from_monday() as usize])
89 }
90 
91 /// Day of the week
parse_A(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()>92 pub(crate) fn parse_A(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()> {
93     items.weekday = Some(
94         try_consume_first_match(s, WEEKDAYS_FULL.iter().zip(WEEKDAYS.iter().cloned()))
95             .ok_or(error::Parse::InvalidDayOfWeek)?,
96     );
97 
98     Ok(())
99 }
100 
101 /// Short month name
fmt_b(f: &mut Formatter<'_>, date: Date) -> fmt::Result102 pub(crate) fn fmt_b(f: &mut Formatter<'_>, date: Date) -> fmt::Result {
103     f.write_str(MONTHS_ABBR[date.month() as usize - 1])
104 }
105 
106 /// Short month name
parse_b(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()>107 pub(crate) fn parse_b(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()> {
108     items.month = Some(
109         try_consume_first_match(s, MONTHS_ABBR.iter().cloned().zip(1..))
110             .and_then(NonZeroU8::new)
111             .ok_or(error::Parse::InvalidMonth)?,
112     );
113 
114     Ok(())
115 }
116 
117 /// Month name
fmt_B(f: &mut Formatter<'_>, date: Date) -> fmt::Result118 pub(crate) fn fmt_B(f: &mut Formatter<'_>, date: Date) -> fmt::Result {
119     f.write_str(MONTHS_FULL[date.month() as usize - 1])
120 }
121 
122 /// Month name
parse_B(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()>123 pub(crate) fn parse_B(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()> {
124     items.month = Some(
125         try_consume_first_match(s, MONTHS_FULL.iter().cloned().zip(1..))
126             .and_then(NonZeroU8::new)
127             .ok_or(error::Parse::InvalidMonth)?,
128     );
129 
130     Ok(())
131 }
132 
133 /// Year divided by 100 and truncated to integer (`00`-`999`)
fmt_C(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result134 pub(crate) fn fmt_C(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
135     pad!(f, padding, 2, date.year() / 100)
136 }
137 
138 /// Year divided by 100 and truncated to integer (`00`-`999`)
parse_C(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>139 pub(crate) fn parse_C(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
140     let padding_length = consume_padding(s, padding, 1);
141     items.year = Some(
142         try_consume_digits::<i32>(s, 2 - padding_length, 3 - padding_length)
143             .ok_or(error::Parse::InvalidYear)?
144             * 100
145             + items.year.unwrap_or(0).rem_euclid(100),
146     );
147 
148     Ok(())
149 }
150 
151 /// Day of the month, zero-padded (`01`-`31`)
fmt_d(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result152 pub(crate) fn fmt_d(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
153     pad!(f, padding, 2, date.day())
154 }
155 
156 /// Day of the month, zero-padded (`01`-`31`)
parse_d(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>157 pub(crate) fn parse_d(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
158     items.day = Some(
159         try_consume_exact_digits(s, 2, padding)
160             .and_then(NonZeroU8::new)
161             .ok_or(error::Parse::InvalidDayOfMonth)?,
162     );
163 
164     Ok(())
165 }
166 
167 /// Week-based year, last two digits (`00`-`99`)
fmt_g(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result168 pub(crate) fn fmt_g(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
169     pad!(f, padding, 2, date.iso_year_week().0.rem_euclid(100))
170 }
171 
172 /// Week-based year, last two digits (`00`-`99`)
parse_g(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>173 pub(crate) fn parse_g(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
174     items.week_based_year = Some(
175         items.week_based_year.unwrap_or(0) / 100 * 100
176             + try_consume_exact_digits::<i32>(s, 2, padding).ok_or(error::Parse::InvalidYear)?,
177     );
178 
179     Ok(())
180 }
181 
182 /// Week-based year
fmt_G(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result183 pub(crate) fn fmt_G(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
184     let year = date.iso_year_week().0;
185 
186     if year >= 10_000 {
187         f.write_str("+")?;
188     }
189 
190     pad!(f, padding, 4, year)
191 }
192 
193 /// Week-based year
parse_G(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>194 pub(crate) fn parse_G(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
195     let sign = try_consume_first_match(s, [("+", 1), ("-", -1)].iter().cloned()).unwrap_or(1);
196 
197     consume_padding(s, padding, 4);
198 
199     items.week_based_year = Some(
200         try_consume_digits(s, 1, 6)
201             .map(|v: i32| sign * v)
202             .ok_or(error::Parse::InvalidYear)?,
203     );
204 
205     Ok(())
206 }
207 
208 /// Day of the year, zero-padded to width 3 (`001`-`366`)
fmt_j(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result209 pub(crate) fn fmt_j(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
210     pad!(f, padding, 3, date.ordinal())
211 }
212 
213 /// Day of the year, zero-padded to width 3 (`001`-`366`)
parse_j(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>214 pub(crate) fn parse_j(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
215     items.ordinal_day = Some(
216         try_consume_exact_digits(s, 3, padding)
217             .and_then(NonZeroU16::new)
218             .ok_or(error::Parse::InvalidDayOfYear)?,
219     );
220 
221     Ok(())
222 }
223 
224 /// Month of the year, zero-padded (`01`-`12`)
fmt_m(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result225 pub(crate) fn fmt_m(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
226     pad!(f, padding, 2, date.month())
227 }
228 
229 /// Month of the year, zero-padded (`01`-`12`)
parse_m(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>230 pub(crate) fn parse_m(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
231     items.month = Some(
232         try_consume_exact_digits(s, 2, padding)
233             .and_then(NonZeroU8::new)
234             .ok_or(error::Parse::InvalidMonth)?,
235     );
236 
237     Ok(())
238 }
239 
240 /// ISO weekday (Monday = `1`, Sunday = `7`)
fmt_u(f: &mut Formatter<'_>, date: Date) -> fmt::Result241 pub(crate) fn fmt_u(f: &mut Formatter<'_>, date: Date) -> fmt::Result {
242     write!(f, "{}", date.weekday().iso_weekday_number())
243 }
244 
245 /// ISO weekday (Monday = `1`, Sunday = `7`)
parse_u(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()>246 pub(crate) fn parse_u(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()> {
247     items.weekday = Some(
248         try_consume_first_match(
249             s,
250             (1..).map(|d| d.to_string()).zip(WEEKDAYS.iter().cloned()),
251         )
252         .ok_or(error::Parse::InvalidDayOfWeek)?,
253     );
254 
255     Ok(())
256 }
257 
258 /// Sunday-based week number (`00`-`53`)
fmt_U(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result259 pub(crate) fn fmt_U(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
260     pad!(f, padding, 2, date.sunday_based_week())
261 }
262 
263 /// Sunday-based week number (`00`-`53`)
parse_U(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>264 pub(crate) fn parse_U(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
265     items.sunday_week =
266         Some(try_consume_exact_digits(s, 2, padding).ok_or(error::Parse::InvalidWeek)?);
267 
268     Ok(())
269 }
270 
271 /// ISO week number, zero-padded (`01`-`53`)
fmt_V(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result272 pub(crate) fn fmt_V(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
273     pad!(f, padding, 2, date.week())
274 }
275 
276 /// ISO week number, zero-padded (`01`-`53`)
parse_V(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>277 pub(crate) fn parse_V(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
278     items.iso_week = Some(
279         try_consume_exact_digits(s, 2, padding)
280             .and_then(NonZeroU8::new)
281             .ok_or(error::Parse::InvalidWeek)?,
282     );
283 
284     Ok(())
285 }
286 
287 /// Weekday number (Sunday = `0`, Saturday = `6`)
fmt_w(f: &mut Formatter<'_>, date: Date) -> fmt::Result288 pub(crate) fn fmt_w(f: &mut Formatter<'_>, date: Date) -> fmt::Result {
289     write!(f, "{}", date.weekday().number_days_from_sunday())
290 }
291 
292 /// Weekday number (Sunday = `0`, Saturday = `6`)
parse_w(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()>293 pub(crate) fn parse_w(items: &mut ParsedItems, s: &mut &str) -> ParseResult<()> {
294     let mut weekdays = WEEKDAYS;
295     weekdays.rotate_left(1);
296 
297     items.weekday = Some(
298         try_consume_first_match(
299             s,
300             (0..)
301                 .map(|d: u8| d.to_string())
302                 .zip(weekdays.iter().cloned()),
303         )
304         .ok_or(error::Parse::InvalidDayOfWeek)?,
305     );
306 
307     Ok(())
308 }
309 
310 /// Monday-based week number (`00`-`53`)
fmt_W(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result311 pub(crate) fn fmt_W(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
312     pad!(f, padding, 2, date.monday_based_week())
313 }
314 
315 /// Monday-based week number (`00`-`53`)
parse_W(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>316 pub(crate) fn parse_W(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
317     items.monday_week =
318         Some(try_consume_exact_digits(s, 2, padding).ok_or(error::Parse::InvalidWeek)?);
319 
320     Ok(())
321 }
322 
323 /// Last two digits of year (`00`-`99`)
fmt_y(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result324 pub(crate) fn fmt_y(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
325     pad!(f, padding, 2, date.year().rem_euclid(100))
326 }
327 
328 /// Last two digits of year (`00`-`99`)
parse_y(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>329 pub(crate) fn parse_y(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
330     items.year = Some(
331         items.year.unwrap_or(0) / 100 * 100
332             + try_consume_exact_digits::<i32>(s, 2, padding).ok_or(error::Parse::InvalidYear)?,
333     );
334 
335     Ok(())
336 }
337 
338 /// Full year
fmt_Y(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result339 pub(crate) fn fmt_Y(f: &mut Formatter<'_>, date: Date, padding: Padding) -> fmt::Result {
340     let year = date.year();
341 
342     if year >= 10_000 {
343         f.write_str("+")?;
344     }
345 
346     pad!(f, padding, 4, year)
347 }
348 
349 /// Full year
parse_Y(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()>350 pub(crate) fn parse_Y(items: &mut ParsedItems, s: &mut &str, padding: Padding) -> ParseResult<()> {
351     let (sign, max_digits) =
352         try_consume_first_match(s, [("+", (1, 6)), ("-", (-1, 6))].iter().cloned())
353             .unwrap_or((1, 4));
354 
355     consume_padding(s, padding, 3);
356 
357     items.year = Some(
358         try_consume_digits(s, 1, max_digits)
359             .map(|v: i32| sign * v)
360             .ok_or(error::Parse::InvalidYear)?,
361     );
362 
363     Ok(())
364 }
365