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