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