1 // This is a part of Chrono.
2 // See README.md and LICENSE.txt for details.
3
4 /*!
5 `strftime`/`strptime`-inspired date and time formatting syntax.
6
7 ## Specifiers
8
9 The following specifiers are available both to formatting and parsing.
10
11 | Spec. | Example | Description |
12 |-------|----------|----------------------------------------------------------------------------|
13 | | | **DATE SPECIFIERS:** |
14 | `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. [^1] |
15 | `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^2] |
16 | `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^2] |
17 | | | |
18 | `%m` | `07` | Month number (01--12), zero-padded to 2 digits. |
19 | `%b` | `Jul` | Abbreviated month name. Always 3 letters. |
20 | `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. |
21 | `%h` | `Jul` | Same as `%b`. |
22 | | | |
23 | `%d` | `08` | Day number (01--31), zero-padded to 2 digits. |
24 | `%e` | ` 8` | Same as `%d` but space-padded. Same as `%_d`. |
25 | | | |
26 | `%a` | `Sun` | Abbreviated weekday name. Always 3 letters. |
27 | `%A` | `Sunday` | Full weekday name. Also accepts corresponding abbreviation in parsing. |
28 | `%w` | `0` | Sunday = 0, Monday = 1, ..., Saturday = 6. |
29 | `%u` | `7` | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) |
30 | | | |
31 | `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^3] |
32 | `%W` | `27` | Same as `%U`, but week 1 starts with the first Monday in that year instead.|
33 | | | |
34 | `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^4] |
35 | `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^4] |
36 | `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^4] |
37 | | | |
38 | `%j` | `189` | Day of the year (001--366), zero-padded to 3 digits. |
39 | | | |
40 | `%D` | `07/08/01` | Month-day-year format. Same as `%m/%d/%y`. |
41 | `%x` | `07/08/01` | Locale's date representation (e.g., 12/31/99). |
42 | `%F` | `2001-07-08` | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`. |
43 | `%v` | ` 8-Jul-2001` | Day-month-year format. Same as `%e-%b-%Y`. |
44 | | | |
45 | | | **TIME SPECIFIERS:** |
46 | `%H` | `00` | Hour number (00--23), zero-padded to 2 digits. |
47 | `%k` | ` 0` | Same as `%H` but space-padded. Same as `%_H`. |
48 | `%I` | `12` | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits. |
49 | `%l` | `12` | Same as `%I` but space-padded. Same as `%_I`. |
50 | | | |
51 | `%P` | `am` | `am` or `pm` in 12-hour clocks. |
52 | `%p` | `AM` | `AM` or `PM` in 12-hour clocks. |
53 | | | |
54 | `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. |
55 | `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^5] |
56 | `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^8] |
57 | `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^8] |
58 | `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^8] |
59 | `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^8] |
60 | `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^8] |
61 | `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^8] |
62 | `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^8] |
63 | `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^8] |
64 | | | |
65 | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. |
66 | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. |
67 | `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). |
68 | `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. |
69 | | | |
70 | | | **TIME ZONE SPECIFIERS:** |
71 | `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^9] |
72 | `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). |
73 | `%:z` | `+09:30` | Same as `%z` but with a colon. |
74 | `%#z` | `+09` | *Parsing only:* Same as `%z` but allows minutes to be missing or present. |
75 | | | |
76 | | | **DATE & TIME SPECIFIERS:** |
77 |`%c`|`Sun Jul 8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar 3 23:05:25 2005). |
78 | `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^6] |
79 | | | |
80 | `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^7]|
81 | | | |
82 | | | **SPECIAL SPECIFIERS:** |
83 | `%t` | | Literal tab (`\t`). |
84 | `%n` | | Literal newline (`\n`). |
85 | `%%` | | Literal percent sign. |
86
87 It is possible to override the default padding behavior of numeric specifiers `%?`.
88 This is not allowed for other specifiers and will result in the `BAD_FORMAT` error.
89
90 Modifier | Description
91 -------- | -----------
92 `%-?` | Suppresses any padding including spaces and zeroes. (e.g. `%j` = `012`, `%-j` = `12`)
93 `%_?` | Uses spaces as a padding. (e.g. `%j` = `012`, `%_j` = ` 12`)
94 `%0?` | Uses zeroes as a padding. (e.g. `%e` = ` 9`, `%0e` = `09`)
95
96 Notes:
97
98 [^1]: `%Y`:
99 Negative years are allowed in formatting but not in parsing.
100
101 [^2]: `%C`, `%y`:
102 This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively.
103
104 [^3]: `%U`:
105 Week 1 starts with the first Sunday in that year.
106 It is possible to have week 0 for days before the first Sunday.
107
108 [^4]: `%G`, `%g`, `%V`:
109 Week 1 is the first week with at least 4 days in that year.
110 Week 0 does not exist, so this should be used with `%G` or `%g`.
111
112 [^5]: `%S`:
113 It accounts for leap seconds, so `60` is possible.
114
115 [^6]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional
116 digits for seconds and colons in the time zone offset.
117 <br>
118 <br>
119 The typical `strftime` implementations have different (and locale-dependent)
120 formats for this specifier. While Chrono's format for `%+` is far more
121 stable, it is best to avoid this specifier if you want to control the exact
122 output.
123
124 [^7]: `%s`:
125 This is not padded and can be negative.
126 For the purpose of Chrono, it only accounts for non-leap seconds
127 so it slightly differs from ISO C `strftime` behavior.
128
129 [^8]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`:
130 <br>
131 The default `%f` is right-aligned and always zero-padded to 9 digits
132 for the compatibility with glibc and others,
133 so it always counts the number of nanoseconds since the last whole second.
134 E.g. 7ms after the last second will print `007000000`,
135 and parsing `7000000` will yield the same.
136 <br>
137 <br>
138 The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits
139 according to the precision.
140 E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`),
141 and parsing `.07`, `.070000` etc. will yield the same.
142 Note that they can print or read nothing if the fractional part is zero or
143 the next character is not `.`.
144 <br>
145 <br>
146 The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits
147 according to the number preceding `f`.
148 E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`),
149 and parsing `.07`, `.070000` etc. will yield the same.
150 Note that they can read nothing if the fractional part is zero or
151 the next character is not `.` however will print with the specified length.
152 <br>
153 <br>
154 The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits
155 according to the number preceding `f`, but without the leading dot.
156 E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`),
157 and parsing `07`, `070000` etc. will yield the same.
158 Note that they can read nothing if the fractional part is zero.
159
160 [^9]: `%Z`:
161 Offset will not be populated from the parsed data, nor will it be validated.
162 Timezone is completely ignored. Similar to the glibc `strptime` treatment of
163 this format code.
164 <br>
165 <br>
166 It is not possible to reliably convert from an abbreviation to an offset,
167 for example CDT can mean either Central Daylight Time (North America) or
168 China Daylight Time.
169 */
170
171 #[cfg(feature = "unstable-locales")]
172 use super::{locales, Locale};
173 use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad};
174
175 #[cfg(feature = "unstable-locales")]
176 type Fmt<'a> = Vec<Item<'a>>;
177 #[cfg(not(feature = "unstable-locales"))]
178 type Fmt<'a> = &'static [Item<'static>];
179
180 static D_FMT: &'static [Item<'static>] =
181 &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)];
182 static D_T_FMT: &'static [Item<'static>] = &[
183 fix!(ShortWeekdayName),
184 sp!(" "),
185 fix!(ShortMonthName),
186 sp!(" "),
187 nums!(Day),
188 sp!(" "),
189 num0!(Hour),
190 lit!(":"),
191 num0!(Minute),
192 lit!(":"),
193 num0!(Second),
194 sp!(" "),
195 num0!(Year),
196 ];
197 static T_FMT: &'static [Item<'static>] =
198 &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)];
199
200 /// Parsing iterator for `strftime`-like format strings.
201 #[derive(Clone, Debug)]
202 pub struct StrftimeItems<'a> {
203 /// Remaining portion of the string.
204 remainder: &'a str,
205 /// If the current specifier is composed of multiple formatting items (e.g. `%+`),
206 /// parser refers to the statically reconstructed slice of them.
207 /// If `recons` is not empty they have to be returned earlier than the `remainder`.
208 recons: Fmt<'a>,
209 /// Date format
210 d_fmt: Fmt<'a>,
211 /// Date and time format
212 d_t_fmt: Fmt<'a>,
213 /// Time format
214 t_fmt: Fmt<'a>,
215 }
216
217 impl<'a> StrftimeItems<'a> {
218 /// Creates a new parsing iterator from the `strftime`-like format string.
new(s: &'a str) -> StrftimeItems<'a>219 pub fn new(s: &'a str) -> StrftimeItems<'a> {
220 Self::with_remainer(s)
221 }
222
223 /// Creates a new parsing iterator from the `strftime`-like format string.
224 #[cfg(feature = "unstable-locales")]
new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a>225 pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
226 let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect();
227 let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect();
228 let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect();
229
230 StrftimeItems {
231 remainder: s,
232 recons: Vec::new(),
233 d_fmt: d_fmt,
234 d_t_fmt: d_t_fmt,
235 t_fmt: t_fmt,
236 }
237 }
238
239 #[cfg(not(feature = "unstable-locales"))]
with_remainer(s: &'a str) -> StrftimeItems<'a>240 fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
241 static FMT_NONE: &'static [Item<'static>; 0] = &[];
242
243 StrftimeItems {
244 remainder: s,
245 recons: FMT_NONE,
246 d_fmt: D_FMT,
247 d_t_fmt: D_T_FMT,
248 t_fmt: T_FMT,
249 }
250 }
251
252 #[cfg(feature = "unstable-locales")]
with_remainer(s: &'a str) -> StrftimeItems<'a>253 fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
254 StrftimeItems {
255 remainder: s,
256 recons: Vec::new(),
257 d_fmt: D_FMT.to_vec(),
258 d_t_fmt: D_T_FMT.to_vec(),
259 t_fmt: T_FMT.to_vec(),
260 }
261 }
262 }
263
264 const HAVE_ALTERNATES: &'static str = "z";
265
266 impl<'a> Iterator for StrftimeItems<'a> {
267 type Item = Item<'a>;
268
next(&mut self) -> Option<Item<'a>>269 fn next(&mut self) -> Option<Item<'a>> {
270 // we have some reconstructed items to return
271 if !self.recons.is_empty() {
272 let item;
273 #[cfg(feature = "unstable-locales")]
274 {
275 item = self.recons.remove(0);
276 }
277 #[cfg(not(feature = "unstable-locales"))]
278 {
279 item = self.recons[0].clone();
280 self.recons = &self.recons[1..];
281 }
282 return Some(item);
283 }
284
285 match self.remainder.chars().next() {
286 // we are done
287 None => None,
288
289 // the next item is a specifier
290 Some('%') => {
291 self.remainder = &self.remainder[1..];
292
293 macro_rules! next {
294 () => {
295 match self.remainder.chars().next() {
296 Some(x) => {
297 self.remainder = &self.remainder[x.len_utf8()..];
298 x
299 }
300 None => return Some(Item::Error), // premature end of string
301 }
302 };
303 }
304
305 let spec = next!();
306 let pad_override = match spec {
307 '-' => Some(Pad::None),
308 '0' => Some(Pad::Zero),
309 '_' => Some(Pad::Space),
310 _ => None,
311 };
312 let is_alternate = spec == '#';
313 let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
314 if is_alternate && !HAVE_ALTERNATES.contains(spec) {
315 return Some(Item::Error);
316 }
317
318 macro_rules! recons {
319 [$head:expr, $($tail:expr),+ $(,)*] => ({
320 #[cfg(feature = "unstable-locales")]
321 {
322 self.recons.clear();
323 $(self.recons.push($tail);)+
324 }
325 #[cfg(not(feature = "unstable-locales"))]
326 {
327 const RECONS: &'static [Item<'static>] = &[$($tail),+];
328 self.recons = RECONS;
329 }
330 $head
331 })
332 }
333
334 macro_rules! recons_from_slice {
335 ($slice:expr) => {{
336 #[cfg(feature = "unstable-locales")]
337 {
338 self.recons.clear();
339 self.recons.extend_from_slice(&$slice[1..]);
340 }
341 #[cfg(not(feature = "unstable-locales"))]
342 {
343 self.recons = &$slice[1..];
344 }
345 $slice[0].clone()
346 }};
347 }
348
349 let item = match spec {
350 'A' => fix!(LongWeekdayName),
351 'B' => fix!(LongMonthName),
352 'C' => num0!(YearDiv100),
353 'D' => {
354 recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]
355 }
356 'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)],
357 'G' => num0!(IsoYear),
358 'H' => num0!(Hour),
359 'I' => num0!(Hour12),
360 'M' => num0!(Minute),
361 'P' => fix!(LowerAmPm),
362 'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)],
363 'S' => num0!(Second),
364 'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)],
365 'U' => num0!(WeekFromSun),
366 'V' => num0!(IsoWeek),
367 'W' => num0!(WeekFromMon),
368 'X' => recons_from_slice!(self.t_fmt),
369 'Y' => num0!(Year),
370 'Z' => fix!(TimezoneName),
371 'a' => fix!(ShortWeekdayName),
372 'b' | 'h' => fix!(ShortMonthName),
373 'c' => recons_from_slice!(self.d_t_fmt),
374 'd' => num0!(Day),
375 'e' => nums!(Day),
376 'f' => num0!(Nanosecond),
377 'g' => num0!(IsoYearMod100),
378 'j' => num0!(Ordinal),
379 'k' => nums!(Hour),
380 'l' => nums!(Hour12),
381 'm' => num0!(Month),
382 'n' => sp!("\n"),
383 'p' => fix!(UpperAmPm),
384 'r' => recons![
385 num0!(Hour12),
386 lit!(":"),
387 num0!(Minute),
388 lit!(":"),
389 num0!(Second),
390 sp!(" "),
391 fix!(UpperAmPm)
392 ],
393 's' => num!(Timestamp),
394 't' => sp!("\t"),
395 'u' => num!(WeekdayFromMon),
396 'v' => {
397 recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)]
398 }
399 'w' => num!(NumDaysFromSun),
400 'x' => recons_from_slice!(self.d_fmt),
401 'y' => num0!(YearMod100),
402 'z' => {
403 if is_alternate {
404 internal_fix!(TimezoneOffsetPermissive)
405 } else {
406 fix!(TimezoneOffset)
407 }
408 }
409 '+' => fix!(RFC3339),
410 ':' => match next!() {
411 'z' => fix!(TimezoneOffsetColon),
412 _ => Item::Error,
413 },
414 '.' => match next!() {
415 '3' => match next!() {
416 'f' => fix!(Nanosecond3),
417 _ => Item::Error,
418 },
419 '6' => match next!() {
420 'f' => fix!(Nanosecond6),
421 _ => Item::Error,
422 },
423 '9' => match next!() {
424 'f' => fix!(Nanosecond9),
425 _ => Item::Error,
426 },
427 'f' => fix!(Nanosecond),
428 _ => Item::Error,
429 },
430 '3' => match next!() {
431 'f' => internal_fix!(Nanosecond3NoDot),
432 _ => Item::Error,
433 },
434 '6' => match next!() {
435 'f' => internal_fix!(Nanosecond6NoDot),
436 _ => Item::Error,
437 },
438 '9' => match next!() {
439 'f' => internal_fix!(Nanosecond9NoDot),
440 _ => Item::Error,
441 },
442 '%' => lit!("%"),
443 _ => Item::Error, // no such specifier
444 };
445
446 // adjust `item` if we have any padding modifier
447 if let Some(new_pad) = pad_override {
448 match item {
449 Item::Numeric(ref kind, _pad) if self.recons.is_empty() => {
450 Some(Item::Numeric(kind.clone(), new_pad))
451 }
452 _ => Some(Item::Error), // no reconstructed or non-numeric item allowed
453 }
454 } else {
455 Some(item)
456 }
457 }
458
459 // the next item is space
460 Some(c) if c.is_whitespace() => {
461 // `%` is not a whitespace, so `c != '%'` is redundant
462 let nextspec = self
463 .remainder
464 .find(|c: char| !c.is_whitespace())
465 .unwrap_or_else(|| self.remainder.len());
466 assert!(nextspec > 0);
467 let item = sp!(&self.remainder[..nextspec]);
468 self.remainder = &self.remainder[nextspec..];
469 Some(item)
470 }
471
472 // the next item is literal
473 _ => {
474 let nextspec = self
475 .remainder
476 .find(|c: char| c.is_whitespace() || c == '%')
477 .unwrap_or_else(|| self.remainder.len());
478 assert!(nextspec > 0);
479 let item = lit!(&self.remainder[..nextspec]);
480 self.remainder = &self.remainder[nextspec..];
481 Some(item)
482 }
483 }
484 }
485 }
486
487 #[cfg(test)]
488 #[test]
test_strftime_items()489 fn test_strftime_items() {
490 fn parse_and_collect<'a>(s: &'a str) -> Vec<Item<'a>> {
491 // map any error into `[Item::Error]`. useful for easy testing.
492 let items = StrftimeItems::new(s);
493 let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
494 items.collect::<Option<Vec<_>>>().unwrap_or(vec![Item::Error])
495 }
496
497 assert_eq!(parse_and_collect(""), []);
498 assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
499 assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
500 assert_eq!(
501 parse_and_collect("a b\t\nc"),
502 [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")]
503 );
504 assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
505 assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
506 assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
507 assert_eq!(
508 parse_and_collect("%Y-%m-%d"),
509 [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)]
510 );
511 assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
512 assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
513 assert_eq!(parse_and_collect("%"), [Item::Error]);
514 assert_eq!(parse_and_collect("%%"), [lit!("%")]);
515 assert_eq!(parse_and_collect("%%%"), [Item::Error]);
516 assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
517 assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
518 assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
519 assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
520 assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
521 assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
522 assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
523 assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
524 assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
525 assert_eq!(parse_and_collect("%.j"), [Item::Error]);
526 assert_eq!(parse_and_collect("%:j"), [Item::Error]);
527 assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]);
528 assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]);
529 assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]);
530 assert_eq!(parse_and_collect("%.e"), [Item::Error]);
531 assert_eq!(parse_and_collect("%:e"), [Item::Error]);
532 assert_eq!(parse_and_collect("%-e"), [num!(Day)]);
533 assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
534 assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
535 assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
536 assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
537 assert_eq!(parse_and_collect("%#m"), [Item::Error]);
538 }
539
540 #[cfg(test)]
541 #[test]
test_strftime_docs()542 fn test_strftime_docs() {
543 use {FixedOffset, TimeZone, Timelike};
544
545 let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708);
546
547 // date specifiers
548 assert_eq!(dt.format("%Y").to_string(), "2001");
549 assert_eq!(dt.format("%C").to_string(), "20");
550 assert_eq!(dt.format("%y").to_string(), "01");
551 assert_eq!(dt.format("%m").to_string(), "07");
552 assert_eq!(dt.format("%b").to_string(), "Jul");
553 assert_eq!(dt.format("%B").to_string(), "July");
554 assert_eq!(dt.format("%h").to_string(), "Jul");
555 assert_eq!(dt.format("%d").to_string(), "08");
556 assert_eq!(dt.format("%e").to_string(), " 8");
557 assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
558 assert_eq!(dt.format("%a").to_string(), "Sun");
559 assert_eq!(dt.format("%A").to_string(), "Sunday");
560 assert_eq!(dt.format("%w").to_string(), "0");
561 assert_eq!(dt.format("%u").to_string(), "7");
562 assert_eq!(dt.format("%U").to_string(), "28");
563 assert_eq!(dt.format("%W").to_string(), "27");
564 assert_eq!(dt.format("%G").to_string(), "2001");
565 assert_eq!(dt.format("%g").to_string(), "01");
566 assert_eq!(dt.format("%V").to_string(), "27");
567 assert_eq!(dt.format("%j").to_string(), "189");
568 assert_eq!(dt.format("%D").to_string(), "07/08/01");
569 assert_eq!(dt.format("%x").to_string(), "07/08/01");
570 assert_eq!(dt.format("%F").to_string(), "2001-07-08");
571 assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
572
573 // time specifiers
574 assert_eq!(dt.format("%H").to_string(), "00");
575 assert_eq!(dt.format("%k").to_string(), " 0");
576 assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
577 assert_eq!(dt.format("%I").to_string(), "12");
578 assert_eq!(dt.format("%l").to_string(), "12");
579 assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
580 assert_eq!(dt.format("%P").to_string(), "am");
581 assert_eq!(dt.format("%p").to_string(), "AM");
582 assert_eq!(dt.format("%M").to_string(), "34");
583 assert_eq!(dt.format("%S").to_string(), "60");
584 assert_eq!(dt.format("%f").to_string(), "026490708");
585 assert_eq!(dt.format("%.f").to_string(), ".026490708");
586 assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
587 assert_eq!(dt.format("%.3f").to_string(), ".026");
588 assert_eq!(dt.format("%.6f").to_string(), ".026490");
589 assert_eq!(dt.format("%.9f").to_string(), ".026490708");
590 assert_eq!(dt.format("%3f").to_string(), "026");
591 assert_eq!(dt.format("%6f").to_string(), "026490");
592 assert_eq!(dt.format("%9f").to_string(), "026490708");
593 assert_eq!(dt.format("%R").to_string(), "00:34");
594 assert_eq!(dt.format("%T").to_string(), "00:34:60");
595 assert_eq!(dt.format("%X").to_string(), "00:34:60");
596 assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
597
598 // time zone specifiers
599 //assert_eq!(dt.format("%Z").to_string(), "ACST");
600 assert_eq!(dt.format("%z").to_string(), "+0930");
601 assert_eq!(dt.format("%:z").to_string(), "+09:30");
602
603 // date & time specifiers
604 assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001");
605 assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
606 assert_eq!(
607 dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
608 "2001-07-08T00:34:60.026490+09:30"
609 );
610 assert_eq!(dt.format("%s").to_string(), "994518299");
611
612 // special specifiers
613 assert_eq!(dt.format("%t").to_string(), "\t");
614 assert_eq!(dt.format("%n").to_string(), "\n");
615 assert_eq!(dt.format("%%").to_string(), "%");
616 }
617
618 #[cfg(feature = "unstable-locales")]
619 #[test]
test_strftime_docs_localized()620 fn test_strftime_docs_localized() {
621 use {FixedOffset, TimeZone};
622
623 let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708);
624
625 // date specifiers
626 assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
627 assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
628 assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
629 assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
630 assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
631 assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
632 assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
633 assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
634 assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
635
636 // time specifiers
637 assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
638 assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
639 assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
640 assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
641 assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
642 assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 ");
643
644 // date & time specifiers
645 assert_eq!(
646 dt.format_localized("%c", Locale::fr_BE).to_string(),
647 "dim 08 jui 2001 00:34:60 +09:30"
648 );
649 }
650