1 | // This is a part of Chrono. |
2 | // See README.md and LICENSE.txt for details. |
3 | |
4 | //! Formatting (and parsing) utilities for date and time. |
5 | //! |
6 | //! This module provides the common types and routines to implement, |
7 | //! for example, [`DateTime::format`](../struct.DateTime.html#method.format) or |
8 | //! [`DateTime::parse_from_str`](../struct.DateTime.html#method.parse_from_str) methods. |
9 | //! For most cases you should use these high-level interfaces. |
10 | //! |
11 | //! Internally the formatting and parsing shares the same abstract **formatting items**, |
12 | //! which are just an [`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) of |
13 | //! the [`Item`](./enum.Item.html) type. |
14 | //! They are generated from more readable **format strings**; |
15 | //! currently Chrono supports a built-in syntax closely resembling |
16 | //! C's `strftime` format. The available options can be found [here](./strftime/index.html). |
17 | //! |
18 | //! # Example |
19 | #![cfg_attr (not(feature = "std" ), doc = "```ignore" )] |
20 | #![cfg_attr (feature = "std" , doc = "```rust" )] |
21 | //! use chrono::{TimeZone, Utc}; |
22 | //! |
23 | //! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); |
24 | //! |
25 | //! let formatted = format!("{}" , date_time.format("%Y-%m-%d %H:%M:%S" )); |
26 | //! assert_eq!(formatted, "2020-11-10 00:01:32" ); |
27 | //! |
28 | //! let parsed = Utc.datetime_from_str(&formatted, "%Y-%m-%d %H:%M:%S" )?; |
29 | //! assert_eq!(parsed, date_time); |
30 | //! # Ok::<(), chrono::ParseError>(()) |
31 | //! ``` |
32 | |
33 | #[cfg (feature = "alloc" )] |
34 | extern crate alloc; |
35 | |
36 | #[cfg (feature = "alloc" )] |
37 | use alloc::boxed::Box; |
38 | #[cfg (feature = "alloc" )] |
39 | use alloc::string::{String, ToString}; |
40 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
41 | use core::borrow::Borrow; |
42 | use core::fmt; |
43 | use core::fmt::Write; |
44 | use core::str::FromStr; |
45 | #[cfg (any(feature = "std" , test))] |
46 | use std::error::Error; |
47 | |
48 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
49 | use crate::naive::{NaiveDate, NaiveTime}; |
50 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
51 | use crate::offset::{FixedOffset, Offset}; |
52 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
53 | use crate::{Datelike, Timelike}; |
54 | use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; |
55 | |
56 | #[cfg (feature = "unstable-locales" )] |
57 | pub(crate) mod locales; |
58 | |
59 | pub use parse::{parse, parse_and_remainder}; |
60 | pub use parsed::Parsed; |
61 | /// L10n locales. |
62 | #[cfg (feature = "unstable-locales" )] |
63 | pub use pure_rust_locales::Locale; |
64 | pub use strftime::StrftimeItems; |
65 | |
66 | #[cfg (not(feature = "unstable-locales" ))] |
67 | #[allow (dead_code)] |
68 | #[derive (Debug)] |
69 | struct Locale; |
70 | |
71 | /// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. |
72 | #[derive (Clone, PartialEq, Eq, Hash)] |
73 | enum Void {} |
74 | |
75 | /// Padding characters for numeric items. |
76 | #[derive (Copy, Clone, PartialEq, Eq, Debug, Hash)] |
77 | pub enum Pad { |
78 | /// No padding. |
79 | None, |
80 | /// Zero (`0`) padding. |
81 | Zero, |
82 | /// Space padding. |
83 | Space, |
84 | } |
85 | |
86 | /// Numeric item types. |
87 | /// They have associated formatting width (FW) and parsing width (PW). |
88 | /// |
89 | /// The **formatting width** is the minimal width to be formatted. |
90 | /// If the number is too short, and the padding is not [`Pad::None`](./enum.Pad.html#variant.None), |
91 | /// then it is left-padded. |
92 | /// If the number is too long or (in some cases) negative, it is printed as is. |
93 | /// |
94 | /// The **parsing width** is the maximal width to be scanned. |
95 | /// The parser only tries to consume from one to given number of digits (greedily). |
96 | /// It also trims the preceding whitespace if any. |
97 | /// It cannot parse the negative number, so some date and time cannot be formatted then |
98 | /// parsed with the same formatting items. |
99 | #[derive (Clone, PartialEq, Eq, Debug, Hash)] |
100 | pub enum Numeric { |
101 | /// Full Gregorian year (FW=4, PW=∞). |
102 | /// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-). |
103 | Year, |
104 | /// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year. |
105 | YearDiv100, |
106 | /// Gregorian year modulo 100 (FW=PW=2). Cannot be negative. |
107 | YearMod100, |
108 | /// Year in the ISO week date (FW=4, PW=∞). |
109 | /// May accept years before 1 BCE or after 9999 CE, given an initial sign. |
110 | IsoYear, |
111 | /// Year in the ISO week date, divided by 100 (FW=PW=2). Implies the non-negative year. |
112 | IsoYearDiv100, |
113 | /// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative. |
114 | IsoYearMod100, |
115 | /// Month (FW=PW=2). |
116 | Month, |
117 | /// Day of the month (FW=PW=2). |
118 | Day, |
119 | /// Week number, where the week 1 starts at the first Sunday of January (FW=PW=2). |
120 | WeekFromSun, |
121 | /// Week number, where the week 1 starts at the first Monday of January (FW=PW=2). |
122 | WeekFromMon, |
123 | /// Week number in the ISO week date (FW=PW=2). |
124 | IsoWeek, |
125 | /// Day of the week, where Sunday = 0 and Saturday = 6 (FW=PW=1). |
126 | NumDaysFromSun, |
127 | /// Day of the week, where Monday = 1 and Sunday = 7 (FW=PW=1). |
128 | WeekdayFromMon, |
129 | /// Day of the year (FW=PW=3). |
130 | Ordinal, |
131 | /// Hour number in the 24-hour clocks (FW=PW=2). |
132 | Hour, |
133 | /// Hour number in the 12-hour clocks (FW=PW=2). |
134 | Hour12, |
135 | /// The number of minutes since the last whole hour (FW=PW=2). |
136 | Minute, |
137 | /// The number of seconds since the last whole minute (FW=PW=2). |
138 | Second, |
139 | /// The number of nanoseconds since the last whole second (FW=PW=9). |
140 | /// Note that this is *not* left-aligned; |
141 | /// see also [`Fixed::Nanosecond`](./enum.Fixed.html#variant.Nanosecond). |
142 | Nanosecond, |
143 | /// The number of non-leap seconds since the midnight UTC on January 1, 1970 (FW=1, PW=∞). |
144 | /// For formatting, it assumes UTC upon the absence of time zone offset. |
145 | Timestamp, |
146 | |
147 | /// Internal uses only. |
148 | /// |
149 | /// This item exists so that one can add additional internal-only formatting |
150 | /// without breaking major compatibility (as enum variants cannot be selectively private). |
151 | Internal(InternalNumeric), |
152 | } |
153 | |
154 | /// An opaque type representing numeric item types for internal uses only. |
155 | #[derive (Clone, Eq, Hash, PartialEq)] |
156 | pub struct InternalNumeric { |
157 | _dummy: Void, |
158 | } |
159 | |
160 | impl fmt::Debug for InternalNumeric { |
161 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
162 | write!(f, "<InternalNumeric>" ) |
163 | } |
164 | } |
165 | |
166 | /// Fixed-format item types. |
167 | /// |
168 | /// They have their own rules of formatting and parsing. |
169 | /// Otherwise noted, they print in the specified cases but parse case-insensitively. |
170 | #[derive (Clone, PartialEq, Eq, Debug, Hash)] |
171 | pub enum Fixed { |
172 | /// Abbreviated month names. |
173 | /// |
174 | /// Prints a three-letter-long name in the title case, reads the same name in any case. |
175 | ShortMonthName, |
176 | /// Full month names. |
177 | /// |
178 | /// Prints a full name in the title case, reads either a short or full name in any case. |
179 | LongMonthName, |
180 | /// Abbreviated day of the week names. |
181 | /// |
182 | /// Prints a three-letter-long name in the title case, reads the same name in any case. |
183 | ShortWeekdayName, |
184 | /// Full day of the week names. |
185 | /// |
186 | /// Prints a full name in the title case, reads either a short or full name in any case. |
187 | LongWeekdayName, |
188 | /// AM/PM. |
189 | /// |
190 | /// Prints in lower case, reads in any case. |
191 | LowerAmPm, |
192 | /// AM/PM. |
193 | /// |
194 | /// Prints in upper case, reads in any case. |
195 | UpperAmPm, |
196 | /// An optional dot plus one or more digits for left-aligned nanoseconds. |
197 | /// May print nothing, 3, 6 or 9 digits according to the available accuracy. |
198 | /// See also [`Numeric::Nanosecond`](./enum.Numeric.html#variant.Nanosecond). |
199 | Nanosecond, |
200 | /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3. |
201 | Nanosecond3, |
202 | /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6. |
203 | Nanosecond6, |
204 | /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9. |
205 | Nanosecond9, |
206 | /// Timezone name. |
207 | /// |
208 | /// It does not support parsing, its use in the parser is an immediate failure. |
209 | TimezoneName, |
210 | /// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`). |
211 | /// |
212 | /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. |
213 | /// The offset is limited from `-24:00` to `+24:00`, |
214 | /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. |
215 | TimezoneOffsetColon, |
216 | /// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`). |
217 | /// |
218 | /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. |
219 | /// The offset is limited from `-24:00:00` to `+24:00:00`, |
220 | /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. |
221 | TimezoneOffsetDoubleColon, |
222 | /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). |
223 | /// |
224 | /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. |
225 | /// The offset is limited from `-24` to `+24`, |
226 | /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. |
227 | TimezoneOffsetTripleColon, |
228 | /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). |
229 | /// |
230 | /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, |
231 | /// and `Z` can be either in upper case or in lower case. |
232 | /// The offset is limited from `-24:00` to `+24:00`, |
233 | /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. |
234 | TimezoneOffsetColonZ, |
235 | /// Same as [`TimezoneOffsetColon`](#variant.TimezoneOffsetColon) but prints no colon. |
236 | /// Parsing allows an optional colon. |
237 | TimezoneOffset, |
238 | /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ) but prints no colon. |
239 | /// Parsing allows an optional colon. |
240 | TimezoneOffsetZ, |
241 | /// RFC 2822 date and time syntax. Commonly used for email and MIME date and time. |
242 | RFC2822, |
243 | /// RFC 3339 & ISO 8601 date and time syntax. |
244 | RFC3339, |
245 | |
246 | /// Internal uses only. |
247 | /// |
248 | /// This item exists so that one can add additional internal-only formatting |
249 | /// without breaking major compatibility (as enum variants cannot be selectively private). |
250 | Internal(InternalFixed), |
251 | } |
252 | |
253 | /// An opaque type representing fixed-format item types for internal uses only. |
254 | #[derive (Debug, Clone, PartialEq, Eq, Hash)] |
255 | pub struct InternalFixed { |
256 | val: InternalInternal, |
257 | } |
258 | |
259 | #[derive (Debug, Clone, PartialEq, Eq, Hash)] |
260 | enum InternalInternal { |
261 | /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but |
262 | /// allows missing minutes (per [ISO 8601][iso8601]). |
263 | /// |
264 | /// # Panics |
265 | /// |
266 | /// If you try to use this for printing. |
267 | /// |
268 | /// [iso8601]: https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC |
269 | TimezoneOffsetPermissive, |
270 | /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3 and there is no leading dot. |
271 | Nanosecond3NoDot, |
272 | /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6 and there is no leading dot. |
273 | Nanosecond6NoDot, |
274 | /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9 and there is no leading dot. |
275 | Nanosecond9NoDot, |
276 | } |
277 | |
278 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
279 | #[derive (Debug, Clone, PartialEq, Eq, Hash)] |
280 | enum Colons { |
281 | None, |
282 | Single, |
283 | Double, |
284 | Triple, |
285 | } |
286 | |
287 | /// A single formatting item. This is used for both formatting and parsing. |
288 | #[derive (Clone, PartialEq, Eq, Debug, Hash)] |
289 | pub enum Item<'a> { |
290 | /// A literally printed and parsed text. |
291 | Literal(&'a str), |
292 | /// Same as `Literal` but with the string owned by the item. |
293 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
294 | #[cfg_attr (docsrs, doc(cfg(any(feature = "alloc" , feature = "std" ))))] |
295 | OwnedLiteral(Box<str>), |
296 | /// Whitespace. Prints literally but reads zero or more whitespace. |
297 | Space(&'a str), |
298 | /// Same as `Space` but with the string owned by the item. |
299 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
300 | #[cfg_attr (docsrs, doc(cfg(any(feature = "alloc" , feature = "std" ))))] |
301 | OwnedSpace(Box<str>), |
302 | /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; |
303 | /// the parser simply ignores any padded whitespace and zeroes. |
304 | Numeric(Numeric, Pad), |
305 | /// Fixed-format item. |
306 | Fixed(Fixed), |
307 | /// Issues a formatting error. Used to signal an invalid format string. |
308 | Error, |
309 | } |
310 | |
311 | macro_rules! lit { |
312 | ($x:expr) => { |
313 | Item::Literal($x) |
314 | }; |
315 | } |
316 | macro_rules! sp { |
317 | ($x:expr) => { |
318 | Item::Space($x) |
319 | }; |
320 | } |
321 | macro_rules! num { |
322 | ($x:ident) => { |
323 | Item::Numeric(Numeric::$x, Pad::None) |
324 | }; |
325 | } |
326 | macro_rules! num0 { |
327 | ($x:ident) => { |
328 | Item::Numeric(Numeric::$x, Pad::Zero) |
329 | }; |
330 | } |
331 | macro_rules! nums { |
332 | ($x:ident) => { |
333 | Item::Numeric(Numeric::$x, Pad::Space) |
334 | }; |
335 | } |
336 | macro_rules! fix { |
337 | ($x:ident) => { |
338 | Item::Fixed(Fixed::$x) |
339 | }; |
340 | } |
341 | macro_rules! internal_fix { |
342 | ($x:ident) => { |
343 | Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x })) |
344 | }; |
345 | } |
346 | |
347 | /// An error from the `parse` function. |
348 | #[derive (Debug, Clone, PartialEq, Eq, Copy, Hash)] |
349 | pub struct ParseError(ParseErrorKind); |
350 | |
351 | impl ParseError { |
352 | /// The category of parse error |
353 | pub const fn kind(&self) -> ParseErrorKind { |
354 | self.0 |
355 | } |
356 | } |
357 | |
358 | /// The category of parse error |
359 | #[allow (clippy::manual_non_exhaustive)] |
360 | #[derive (Debug, Clone, PartialEq, Eq, Copy, Hash)] |
361 | pub enum ParseErrorKind { |
362 | /// Given field is out of permitted range. |
363 | OutOfRange, |
364 | |
365 | /// There is no possible date and time value with given set of fields. |
366 | /// |
367 | /// This does not include the out-of-range conditions, which are trivially invalid. |
368 | /// It includes the case that there are one or more fields that are inconsistent to each other. |
369 | Impossible, |
370 | |
371 | /// Given set of fields is not enough to make a requested date and time value. |
372 | /// |
373 | /// Note that there *may* be a case that given fields constrain the possible values so much |
374 | /// that there is a unique possible value. Chrono only tries to be correct for |
375 | /// most useful sets of fields however, as such constraint solving can be expensive. |
376 | NotEnough, |
377 | |
378 | /// The input string has some invalid character sequence for given formatting items. |
379 | Invalid, |
380 | |
381 | /// The input string has been prematurely ended. |
382 | TooShort, |
383 | |
384 | /// All formatting items have been read but there is a remaining input. |
385 | TooLong, |
386 | |
387 | /// There was an error on the formatting string, or there were non-supported formating items. |
388 | BadFormat, |
389 | |
390 | // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. |
391 | #[doc (hidden)] |
392 | __Nonexhaustive, |
393 | } |
394 | |
395 | /// Same as `Result<T, ParseError>`. |
396 | pub type ParseResult<T> = Result<T, ParseError>; |
397 | |
398 | impl fmt::Display for ParseError { |
399 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
400 | match self.0 { |
401 | ParseErrorKind::OutOfRange => write!(f, "input is out of range" ), |
402 | ParseErrorKind::Impossible => write!(f, "no possible date and time matching input" ), |
403 | ParseErrorKind::NotEnough => write!(f, "input is not enough for unique date and time" ), |
404 | ParseErrorKind::Invalid => write!(f, "input contains invalid characters" ), |
405 | ParseErrorKind::TooShort => write!(f, "premature end of input" ), |
406 | ParseErrorKind::TooLong => write!(f, "trailing input" ), |
407 | ParseErrorKind::BadFormat => write!(f, "bad or unsupported format string" ), |
408 | _ => unreachable!(), |
409 | } |
410 | } |
411 | } |
412 | |
413 | #[cfg (any(feature = "std" , test))] |
414 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
415 | impl Error for ParseError { |
416 | #[allow (deprecated)] |
417 | fn description(&self) -> &str { |
418 | "parser error, see to_string() for details" |
419 | } |
420 | } |
421 | |
422 | // to be used in this module and submodules |
423 | const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); |
424 | const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible); |
425 | const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough); |
426 | const INVALID: ParseError = ParseError(ParseErrorKind::Invalid); |
427 | const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); |
428 | const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); |
429 | const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); |
430 | |
431 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
432 | struct Locales { |
433 | short_months: &'static [&'static str], |
434 | long_months: &'static [&'static str], |
435 | short_weekdays: &'static [&'static str], |
436 | long_weekdays: &'static [&'static str], |
437 | am_pm: &'static [&'static str], |
438 | } |
439 | |
440 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
441 | impl Locales { |
442 | fn new(_locale: Option<Locale>) -> Self { |
443 | #[cfg (feature = "unstable-locales" )] |
444 | { |
445 | let locale = _locale.unwrap_or(Locale::POSIX); |
446 | Self { |
447 | short_months: locales::short_months(locale), |
448 | long_months: locales::long_months(locale), |
449 | short_weekdays: locales::short_weekdays(locale), |
450 | long_weekdays: locales::long_weekdays(locale), |
451 | am_pm: locales::am_pm(locale), |
452 | } |
453 | } |
454 | #[cfg (not(feature = "unstable-locales" ))] |
455 | Self { |
456 | short_months: &[ |
457 | "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" , |
458 | ], |
459 | long_months: &[ |
460 | "January" , |
461 | "February" , |
462 | "March" , |
463 | "April" , |
464 | "May" , |
465 | "June" , |
466 | "July" , |
467 | "August" , |
468 | "September" , |
469 | "October" , |
470 | "November" , |
471 | "December" , |
472 | ], |
473 | short_weekdays: &["Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ], |
474 | long_weekdays: &[ |
475 | "Sunday" , |
476 | "Monday" , |
477 | "Tuesday" , |
478 | "Wednesday" , |
479 | "Thursday" , |
480 | "Friday" , |
481 | "Saturday" , |
482 | ], |
483 | am_pm: &["AM" , "PM" ], |
484 | } |
485 | } |
486 | } |
487 | |
488 | /// Formats single formatting item |
489 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
490 | #[cfg_attr (docsrs, doc(cfg(any(feature = "alloc" , feature = "std" ))))] |
491 | pub fn format_item( |
492 | w: &mut fmt::Formatter, |
493 | date: Option<&NaiveDate>, |
494 | time: Option<&NaiveTime>, |
495 | off: Option<&(String, FixedOffset)>, |
496 | item: &Item<'_>, |
497 | ) -> fmt::Result { |
498 | let mut result: String = String::new(); |
499 | format_inner(&mut result, date, time, off, item, locale:None)?; |
500 | w.pad(&result) |
501 | } |
502 | |
503 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
504 | fn format_inner( |
505 | result: &mut String, |
506 | date: Option<&NaiveDate>, |
507 | time: Option<&NaiveTime>, |
508 | off: Option<&(String, FixedOffset)>, |
509 | item: &Item<'_>, |
510 | locale: Option<Locale>, |
511 | ) -> fmt::Result { |
512 | let locale = Locales::new(locale); |
513 | |
514 | match *item { |
515 | Item::Literal(s) | Item::Space(s) => result.push_str(s), |
516 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
517 | Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), |
518 | |
519 | Item::Numeric(ref spec, ref pad) => { |
520 | use self::Numeric::*; |
521 | |
522 | let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); |
523 | let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); |
524 | |
525 | let (width, v) = match *spec { |
526 | Year => (4, date.map(|d| i64::from(d.year()))), |
527 | YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), |
528 | YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), |
529 | IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), |
530 | IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), |
531 | IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), |
532 | Month => (2, date.map(|d| i64::from(d.month()))), |
533 | Day => (2, date.map(|d| i64::from(d.day()))), |
534 | WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), |
535 | WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), |
536 | IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), |
537 | NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), |
538 | WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), |
539 | Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), |
540 | Hour => (2, time.map(|t| i64::from(t.hour()))), |
541 | Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), |
542 | Minute => (2, time.map(|t| i64::from(t.minute()))), |
543 | Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), |
544 | Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), |
545 | Timestamp => ( |
546 | 1, |
547 | match (date, time, off) { |
548 | (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), |
549 | (Some(d), Some(t), Some(&(_, off))) => { |
550 | Some((d.and_time(*t) - off).timestamp()) |
551 | } |
552 | (_, _, _) => None, |
553 | }, |
554 | ), |
555 | |
556 | // for the future expansion |
557 | Internal(ref int) => match int._dummy {}, |
558 | }; |
559 | |
560 | if let Some(v) = v { |
561 | if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { |
562 | // non-four-digit years require an explicit sign as per ISO 8601 |
563 | match *pad { |
564 | Pad::None => write!(result, " {:+}" , v), |
565 | Pad::Zero => write!(result, " {:+01$}" , v, width + 1), |
566 | Pad::Space => write!(result, " {:+1$}" , v, width + 1), |
567 | } |
568 | } else { |
569 | match *pad { |
570 | Pad::None => write!(result, " {}" , v), |
571 | Pad::Zero => write!(result, " {:01$}" , v, width), |
572 | Pad::Space => write!(result, " {:1$}" , v, width), |
573 | } |
574 | }? |
575 | } else { |
576 | return Err(fmt::Error); // insufficient arguments for given format |
577 | } |
578 | } |
579 | |
580 | Item::Fixed(ref spec) => { |
581 | use self::Fixed::*; |
582 | |
583 | let ret = |
584 | match *spec { |
585 | ShortMonthName => date.map(|d| { |
586 | result.push_str(locale.short_months[d.month0() as usize]); |
587 | Ok(()) |
588 | }), |
589 | LongMonthName => date.map(|d| { |
590 | result.push_str(locale.long_months[d.month0() as usize]); |
591 | Ok(()) |
592 | }), |
593 | ShortWeekdayName => date.map(|d| { |
594 | result.push_str( |
595 | locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], |
596 | ); |
597 | Ok(()) |
598 | }), |
599 | LongWeekdayName => date.map(|d| { |
600 | result.push_str( |
601 | locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], |
602 | ); |
603 | Ok(()) |
604 | }), |
605 | LowerAmPm => time.map(|t| { |
606 | let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; |
607 | for char in ampm.chars() { |
608 | result.extend(char.to_lowercase()) |
609 | } |
610 | Ok(()) |
611 | }), |
612 | UpperAmPm => time.map(|t| { |
613 | result.push_str(if t.hour12().0 { |
614 | locale.am_pm[1] |
615 | } else { |
616 | locale.am_pm[0] |
617 | }); |
618 | Ok(()) |
619 | }), |
620 | Nanosecond => time.map(|t| { |
621 | let nano = t.nanosecond() % 1_000_000_000; |
622 | if nano == 0 { |
623 | Ok(()) |
624 | } else if nano % 1_000_000 == 0 { |
625 | write!(result, ". {:03}" , nano / 1_000_000) |
626 | } else if nano % 1_000 == 0 { |
627 | write!(result, ". {:06}" , nano / 1_000) |
628 | } else { |
629 | write!(result, ". {:09}" , nano) |
630 | } |
631 | }), |
632 | Nanosecond3 => time.map(|t| { |
633 | let nano = t.nanosecond() % 1_000_000_000; |
634 | write!(result, ". {:03}" , nano / 1_000_000) |
635 | }), |
636 | Nanosecond6 => time.map(|t| { |
637 | let nano = t.nanosecond() % 1_000_000_000; |
638 | write!(result, ". {:06}" , nano / 1_000) |
639 | }), |
640 | Nanosecond9 => time.map(|t| { |
641 | let nano = t.nanosecond() % 1_000_000_000; |
642 | write!(result, ". {:09}" , nano) |
643 | }), |
644 | Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time |
645 | .map(|t| { |
646 | let nano = t.nanosecond() % 1_000_000_000; |
647 | write!(result, " {:03}" , nano / 1_000_000) |
648 | }), |
649 | Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time |
650 | .map(|t| { |
651 | let nano = t.nanosecond() % 1_000_000_000; |
652 | write!(result, " {:06}" , nano / 1_000) |
653 | }), |
654 | Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time |
655 | .map(|t| { |
656 | let nano = t.nanosecond() % 1_000_000_000; |
657 | write!(result, " {:09}" , nano) |
658 | }), |
659 | TimezoneName => off.map(|(name, _)| { |
660 | result.push_str(name); |
661 | Ok(()) |
662 | }), |
663 | TimezoneOffsetColon => off |
664 | .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), |
665 | TimezoneOffsetDoubleColon => off |
666 | .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), |
667 | TimezoneOffsetTripleColon => off |
668 | .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), |
669 | TimezoneOffsetColonZ => off |
670 | .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), |
671 | TimezoneOffset => { |
672 | off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) |
673 | } |
674 | TimezoneOffsetZ => { |
675 | off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) |
676 | } |
677 | Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { |
678 | panic!("Do not try to write %#z it is undefined" ) |
679 | } |
680 | RFC2822 => |
681 | // same as `%a, %d %b %Y %H:%M:%S %z` |
682 | { |
683 | if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { |
684 | Some(write_rfc2822_inner(result, d, t, off, locale)) |
685 | } else { |
686 | None |
687 | } |
688 | } |
689 | RFC3339 => |
690 | // same as `%Y-%m-%dT%H:%M:%S%.f%:z` |
691 | { |
692 | if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { |
693 | Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) |
694 | } else { |
695 | None |
696 | } |
697 | } |
698 | }; |
699 | |
700 | match ret { |
701 | Some(ret) => ret?, |
702 | None => return Err(fmt::Error), // insufficient arguments for given format |
703 | } |
704 | } |
705 | |
706 | Item::Error => return Err(fmt::Error), |
707 | } |
708 | Ok(()) |
709 | } |
710 | |
711 | /// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. |
712 | /// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. |
713 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
714 | fn write_local_minus_utc( |
715 | result: &mut String, |
716 | off: FixedOffset, |
717 | allow_zulu: bool, |
718 | colon_type: Colons, |
719 | ) -> fmt::Result { |
720 | let off = off.local_minus_utc(); |
721 | if allow_zulu && off == 0 { |
722 | result.push('Z' ); |
723 | return Ok(()); |
724 | } |
725 | let (sign, off) = if off < 0 { ('-' , -off) } else { ('+' , off) }; |
726 | result.push(sign); |
727 | |
728 | write_hundreds(result, (off / 3600) as u8)?; |
729 | |
730 | match colon_type { |
731 | Colons::None => write_hundreds(result, (off / 60 % 60) as u8), |
732 | Colons::Single => { |
733 | result.push(':' ); |
734 | write_hundreds(result, (off / 60 % 60) as u8) |
735 | } |
736 | Colons::Double => { |
737 | result.push(':' ); |
738 | write_hundreds(result, (off / 60 % 60) as u8)?; |
739 | result.push(':' ); |
740 | write_hundreds(result, (off % 60) as u8) |
741 | } |
742 | Colons::Triple => Ok(()), |
743 | } |
744 | } |
745 | |
746 | /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` |
747 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
748 | pub(crate) fn write_rfc3339( |
749 | result: &mut String, |
750 | dt: crate::NaiveDateTime, |
751 | off: FixedOffset, |
752 | ) -> fmt::Result { |
753 | // reuse `Debug` impls which already print ISO 8601 format. |
754 | // this is faster in this way. |
755 | write!(result, " {:?}" , dt)?; |
756 | write_local_minus_utc(result, off, allow_zulu:false, colon_type:Colons::Single) |
757 | } |
758 | |
759 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
760 | /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` |
761 | pub(crate) fn write_rfc2822( |
762 | result: &mut String, |
763 | dt: crate::NaiveDateTime, |
764 | off: FixedOffset, |
765 | ) -> fmt::Result { |
766 | write_rfc2822_inner(result, &dt.date(), &dt.time(), off, locale:Locales::new(_locale:None)) |
767 | } |
768 | |
769 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
770 | /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` |
771 | fn write_rfc2822_inner( |
772 | result: &mut String, |
773 | d: &NaiveDate, |
774 | t: &NaiveTime, |
775 | off: FixedOffset, |
776 | locale: Locales, |
777 | ) -> fmt::Result { |
778 | let year = d.year(); |
779 | // RFC2822 is only defined on years 0 through 9999 |
780 | if !(0..=9999).contains(&year) { |
781 | return Err(fmt::Error); |
782 | } |
783 | |
784 | result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]); |
785 | result.push_str(", " ); |
786 | write_hundreds(result, d.day() as u8)?; |
787 | result.push(' ' ); |
788 | result.push_str(locale.short_months[d.month0() as usize]); |
789 | result.push(' ' ); |
790 | write_hundreds(result, (year / 100) as u8)?; |
791 | write_hundreds(result, (year % 100) as u8)?; |
792 | result.push(' ' ); |
793 | write_hundreds(result, t.hour() as u8)?; |
794 | result.push(':' ); |
795 | write_hundreds(result, t.minute() as u8)?; |
796 | result.push(':' ); |
797 | let sec = t.second() + t.nanosecond() / 1_000_000_000; |
798 | write_hundreds(result, sec as u8)?; |
799 | result.push(' ' ); |
800 | write_local_minus_utc(result, off, false, Colons::None) |
801 | } |
802 | |
803 | /// Equivalent to `{:02}` formatting for n < 100. |
804 | pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { |
805 | if n >= 100 { |
806 | return Err(fmt::Error); |
807 | } |
808 | |
809 | let tens: u8 = b'0' + n / 10; |
810 | let ones: u8 = b'0' + n % 10; |
811 | w.write_char(tens as char)?; |
812 | w.write_char(ones as char) |
813 | } |
814 | |
815 | /// Tries to format given arguments with given formatting items. |
816 | /// Internally used by `DelayedFormat`. |
817 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
818 | #[cfg_attr (docsrs, doc(cfg(any(feature = "alloc" , feature = "std" ))))] |
819 | pub fn format<'a, I, B>( |
820 | w: &mut fmt::Formatter, |
821 | date: Option<&NaiveDate>, |
822 | time: Option<&NaiveTime>, |
823 | off: Option<&(String, FixedOffset)>, |
824 | items: I, |
825 | ) -> fmt::Result |
826 | where |
827 | I: Iterator<Item = B> + Clone, |
828 | B: Borrow<Item<'a>>, |
829 | { |
830 | let mut result: String = String::new(); |
831 | for item: B in items { |
832 | format_inner(&mut result, date, time, off, item:item.borrow(), locale:None)?; |
833 | } |
834 | w.pad(&result) |
835 | } |
836 | |
837 | mod parsed; |
838 | |
839 | // due to the size of parsing routines, they are in separate modules. |
840 | mod parse; |
841 | mod scan; |
842 | |
843 | pub mod strftime; |
844 | |
845 | /// A *temporary* object which can be used as an argument to `format!` or others. |
846 | /// This is normally constructed via `format` methods of each date and time type. |
847 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
848 | #[cfg_attr (docsrs, doc(cfg(any(feature = "alloc" , feature = "std" ))))] |
849 | #[derive (Debug)] |
850 | pub struct DelayedFormat<I> { |
851 | /// The date view, if any. |
852 | date: Option<NaiveDate>, |
853 | /// The time view, if any. |
854 | time: Option<NaiveTime>, |
855 | /// The name and local-to-UTC difference for the offset (timezone), if any. |
856 | off: Option<(String, FixedOffset)>, |
857 | /// An iterator returning formatting items. |
858 | items: I, |
859 | /// Locale used for text. |
860 | // TODO: Only used with the locale feature. We should make this property |
861 | // only present when the feature is enabled. |
862 | #[cfg (feature = "unstable-locales" )] |
863 | locale: Option<Locale>, |
864 | } |
865 | |
866 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
867 | impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> { |
868 | /// Makes a new `DelayedFormat` value out of local date and time. |
869 | #[must_use ] |
870 | pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> { |
871 | DelayedFormat { |
872 | date, |
873 | time, |
874 | off: None, |
875 | items, |
876 | #[cfg (feature = "unstable-locales" )] |
877 | locale: None, |
878 | } |
879 | } |
880 | |
881 | /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. |
882 | #[must_use ] |
883 | pub fn new_with_offset<Off>( |
884 | date: Option<NaiveDate>, |
885 | time: Option<NaiveTime>, |
886 | offset: &Off, |
887 | items: I, |
888 | ) -> DelayedFormat<I> |
889 | where |
890 | Off: Offset + fmt::Display, |
891 | { |
892 | let name_and_diff = (offset.to_string(), offset.fix()); |
893 | DelayedFormat { |
894 | date, |
895 | time, |
896 | off: Some(name_and_diff), |
897 | items, |
898 | #[cfg (feature = "unstable-locales" )] |
899 | locale: None, |
900 | } |
901 | } |
902 | |
903 | /// Makes a new `DelayedFormat` value out of local date and time and locale. |
904 | #[cfg (feature = "unstable-locales" )] |
905 | #[cfg_attr (docsrs, doc(cfg(feature = "unstable-locales" )))] |
906 | #[must_use ] |
907 | pub fn new_with_locale( |
908 | date: Option<NaiveDate>, |
909 | time: Option<NaiveTime>, |
910 | items: I, |
911 | locale: Locale, |
912 | ) -> DelayedFormat<I> { |
913 | DelayedFormat { date, time, off: None, items, locale: Some(locale) } |
914 | } |
915 | |
916 | /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. |
917 | #[cfg (feature = "unstable-locales" )] |
918 | #[cfg_attr (docsrs, doc(cfg(feature = "unstable-locales" )))] |
919 | #[must_use ] |
920 | pub fn new_with_offset_and_locale<Off>( |
921 | date: Option<NaiveDate>, |
922 | time: Option<NaiveTime>, |
923 | offset: &Off, |
924 | items: I, |
925 | locale: Locale, |
926 | ) -> DelayedFormat<I> |
927 | where |
928 | Off: Offset + fmt::Display, |
929 | { |
930 | let name_and_diff = (offset.to_string(), offset.fix()); |
931 | DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } |
932 | } |
933 | } |
934 | |
935 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
936 | impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> { |
937 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
938 | #[cfg (feature = "unstable-locales" )] |
939 | { |
940 | if let Some(locale) = self.locale { |
941 | return format_localized( |
942 | f, |
943 | self.date.as_ref(), |
944 | self.time.as_ref(), |
945 | self.off.as_ref(), |
946 | self.items.clone(), |
947 | locale, |
948 | ); |
949 | } |
950 | } |
951 | |
952 | format(w:f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) |
953 | } |
954 | } |
955 | |
956 | // this implementation is here only because we need some private code from `scan` |
957 | |
958 | /// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html). |
959 | /// |
960 | /// # Example |
961 | /// |
962 | /// ``` |
963 | /// use chrono::Weekday; |
964 | /// |
965 | /// assert_eq!("Sunday" .parse::<Weekday>(), Ok(Weekday::Sun)); |
966 | /// assert!("any day" .parse::<Weekday>().is_err()); |
967 | /// ``` |
968 | /// |
969 | /// The parsing is case-insensitive. |
970 | /// |
971 | /// ``` |
972 | /// # use chrono::Weekday; |
973 | /// assert_eq!("mON" .parse::<Weekday>(), Ok(Weekday::Mon)); |
974 | /// ``` |
975 | /// |
976 | /// Only the shortest form (e.g. `sun`) and the longest form (e.g. `sunday`) is accepted. |
977 | /// |
978 | /// ``` |
979 | /// # use chrono::Weekday; |
980 | /// assert!("thurs" .parse::<Weekday>().is_err()); |
981 | /// ``` |
982 | impl FromStr for Weekday { |
983 | type Err = ParseWeekdayError; |
984 | |
985 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
986 | if let Ok(("" , w: Weekday)) = scan::short_or_long_weekday(s) { |
987 | Ok(w) |
988 | } else { |
989 | Err(ParseWeekdayError { _dummy: () }) |
990 | } |
991 | } |
992 | } |
993 | |
994 | /// Formats single formatting item |
995 | #[cfg (feature = "unstable-locales" )] |
996 | #[cfg_attr (docsrs, doc(cfg(feature = "unstable-locales" )))] |
997 | pub fn format_item_localized<'a>( |
998 | w: &mut fmt::Formatter, |
999 | date: Option<&NaiveDate>, |
1000 | time: Option<&NaiveTime>, |
1001 | off: Option<&(String, FixedOffset)>, |
1002 | item: &Item<'a>, |
1003 | locale: Locale, |
1004 | ) -> fmt::Result { |
1005 | let mut result = String::new(); |
1006 | format_inner(&mut result, date, time, off, item, Some(locale))?; |
1007 | w.pad(&result) |
1008 | } |
1009 | |
1010 | /// Tries to format given arguments with given formatting items. |
1011 | /// Internally used by `DelayedFormat`. |
1012 | #[cfg (feature = "unstable-locales" )] |
1013 | #[cfg_attr (docsrs, doc(cfg(feature = "unstable-locales" )))] |
1014 | pub fn format_localized<'a, I, B>( |
1015 | w: &mut fmt::Formatter, |
1016 | date: Option<&NaiveDate>, |
1017 | time: Option<&NaiveTime>, |
1018 | off: Option<&(String, FixedOffset)>, |
1019 | items: I, |
1020 | locale: Locale, |
1021 | ) -> fmt::Result |
1022 | where |
1023 | I: Iterator<Item = B> + Clone, |
1024 | B: Borrow<Item<'a>>, |
1025 | { |
1026 | let mut result = String::new(); |
1027 | for item in items { |
1028 | format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; |
1029 | } |
1030 | w.pad(&result) |
1031 | } |
1032 | |
1033 | /// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html). |
1034 | /// |
1035 | /// # Example |
1036 | /// |
1037 | /// ``` |
1038 | /// use chrono::Month; |
1039 | /// |
1040 | /// assert_eq!("January" .parse::<Month>(), Ok(Month::January)); |
1041 | /// assert!("any day" .parse::<Month>().is_err()); |
1042 | /// ``` |
1043 | /// |
1044 | /// The parsing is case-insensitive. |
1045 | /// |
1046 | /// ``` |
1047 | /// # use chrono::Month; |
1048 | /// assert_eq!("fEbruARy" .parse::<Month>(), Ok(Month::February)); |
1049 | /// ``` |
1050 | /// |
1051 | /// Only the shortest form (e.g. `jan`) and the longest form (e.g. `january`) is accepted. |
1052 | /// |
1053 | /// ``` |
1054 | /// # use chrono::Month; |
1055 | /// assert!("septem" .parse::<Month>().is_err()); |
1056 | /// assert!("Augustin" .parse::<Month>().is_err()); |
1057 | /// ``` |
1058 | impl FromStr for Month { |
1059 | type Err = ParseMonthError; |
1060 | |
1061 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
1062 | if let Ok(("" , w)) = scan::short_or_long_month0(s) { |
1063 | match w { |
1064 | 0 => Ok(Month::January), |
1065 | 1 => Ok(Month::February), |
1066 | 2 => Ok(Month::March), |
1067 | 3 => Ok(Month::April), |
1068 | 4 => Ok(Month::May), |
1069 | 5 => Ok(Month::June), |
1070 | 6 => Ok(Month::July), |
1071 | 7 => Ok(Month::August), |
1072 | 8 => Ok(Month::September), |
1073 | 9 => Ok(Month::October), |
1074 | 10 => Ok(Month::November), |
1075 | 11 => Ok(Month::December), |
1076 | _ => Err(ParseMonthError { _dummy: () }), |
1077 | } |
1078 | } else { |
1079 | Err(ParseMonthError { _dummy: () }) |
1080 | } |
1081 | } |
1082 | } |
1083 | |