chrono/format/
strftime.rs

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
9The 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 to `%b`.                                                              |
22|       |          |                                                                            |
23| `%d`  | `08`     | Day number (01--31), zero-padded to 2 digits.                              |
24| `%e`  | ` 8`     | Same to `%d` but space-padded. Same to `%_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 to `%U`, but week 1 starts with the first Monday in that year instead.|
33|       |          |                                                                            |
34| `%G`  | `2001`   | Same to `%Y` but uses the year number in ISO 8601 week date. [^4]          |
35| `%g`  | `01`     | Same to `%y` but uses the year number in ISO 8601 week date. [^4]          |
36| `%V`  | `27`     | Same to `%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 to `%m/%d/%y`.                            |
41| `%x`  | `07/08/01`    | Same to `%D`.                                                         |
42| `%F`  | `2001-07-08`  | Year-month-day format (ISO 8601). Same to `%Y-%m-%d`.                 |
43| `%v`  | ` 8-Jul-2001` | Day-month-year format. Same to `%e-%b-%Y`.                            |
44|       |          |                                                                            |
45|       |          | **TIME SPECIFIERS:**                                                       |
46| `%H`  | `00`     | Hour number (00--23), zero-padded to 2 digits.                             |
47| `%k`  | ` 0`     | Same to `%H` but space-padded. Same to `%_H`.                              |
48| `%I`  | `12`     | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.           |
49| `%l`  | `12`     | Same to `%I` but space-padded. Same to `%_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 to `%H:%M`.                                  |
66| `%T`  | `00:34:60`    | Hour-minute-second format. Same to `%H:%M:%S`.                        |
67| `%X`  | `00:34:60`    | Same to `%T`.                                                         |
68| `%r`  | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same to `%I:%M:%S %p`.   |
69|       |          |                                                                            |
70|       |          | **TIME ZONE SPECIFIERS:**                                                  |
71| `%Z`  | `ACST`   | *Formatting only:* Local time zone name.                                   |
72| `%z`  | `+0930`  | Offset from the local time to UTC (with UTC being `+0000`).                |
73| `%:z` | `+09:30` | Same to `%z` but with a colon.                                             |
74| `%#z` | `+09`    | *Parsing only:* Same to `%z` but allows minutes to be missing or present.  |
75|       |          |                                                                            |
76|       |          | **DATE & TIME SPECIFIERS:**                                                |
77|`%c`|`Sun Jul  8 00:34:60 2001`|`ctime` date & time format. Same to `%a %b %e %T %Y` sans `\n`.|
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
87It is possible to override the default padding behavior of numeric specifiers `%?`.
88This is not allowed for other specifiers and will result in the `BAD_FORMAT` error.
89
90Modifier | 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
96Notes:
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*/
161
162use super::{Item, Numeric, Fixed, InternalFixed, InternalInternal, Pad};
163
164/// Parsing iterator for `strftime`-like format strings.
165#[derive(Clone, Debug)]
166pub struct StrftimeItems<'a> {
167    /// Remaining portion of the string.
168    remainder: &'a str,
169    /// If the current specifier is composed of multiple formatting items (e.g. `%+`),
170    /// parser refers to the statically reconstructed slice of them.
171    /// If `recons` is not empty they have to be returned earlier than the `remainder`.
172    recons: &'static [Item<'static>],
173}
174
175impl<'a> StrftimeItems<'a> {
176    /// Creates a new parsing iterator from the `strftime`-like format string.
177    pub fn new(s: &'a str) -> StrftimeItems<'a> {
178        static FMT_NONE: [Item<'static>; 0] = [];
179        StrftimeItems { remainder: s, recons: &FMT_NONE }
180    }
181}
182
183const HAVE_ALTERNATES: &'static str = "z";
184
185impl<'a> Iterator for StrftimeItems<'a> {
186    type Item = Item<'a>;
187
188    fn next(&mut self) -> Option<Item<'a>> {
189        // we have some reconstructed items to return
190        if !self.recons.is_empty() {
191            let item = self.recons[0].clone();
192            self.recons = &self.recons[1..];
193            return Some(item);
194        }
195
196        match self.remainder.chars().next() {
197            // we are done
198            None => None,
199
200            // the next item is a specifier
201            Some('%') => {
202                self.remainder = &self.remainder[1..];
203
204                macro_rules! next {
205                    () => (
206                        match self.remainder.chars().next() {
207                            Some(x) => {
208                                self.remainder = &self.remainder[x.len_utf8()..];
209                                x
210                            },
211                            None => return Some(Item::Error), // premature end of string
212                        }
213                    )
214                }
215
216                let spec = next!();
217                let pad_override = match spec {
218                    '-' => Some(Pad::None),
219                    '0' => Some(Pad::Zero),
220                    '_' => Some(Pad::Space),
221                    _ => None,
222                };
223                let is_alternate = spec == '#';
224                let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
225                if is_alternate && !HAVE_ALTERNATES.contains(spec) {
226                    return Some(Item::Error);
227                }
228
229                macro_rules! recons {
230                    [$head:expr, $($tail:expr),+] => ({
231                        const RECONS: &'static [Item<'static>] = &[$($tail),+];
232                        self.recons = RECONS;
233                        $head
234                    })
235                }
236
237                let item = match spec {
238                    'A' => fix!(LongWeekdayName),
239                    'B' => fix!(LongMonthName),
240                    'C' => num0!(YearDiv100),
241                    'D' => recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"),
242                                   num0!(YearMod100)],
243                    'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)],
244                    'G' => num0!(IsoYear),
245                    'H' => num0!(Hour),
246                    'I' => num0!(Hour12),
247                    'M' => num0!(Minute),
248                    'P' => fix!(LowerAmPm),
249                    'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)],
250                    'S' => num0!(Second),
251                    'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)],
252                    'U' => num0!(WeekFromSun),
253                    'V' => num0!(IsoWeek),
254                    'W' => num0!(WeekFromMon),
255                    'X' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)],
256                    'Y' => num0!(Year),
257                    'Z' => fix!(TimezoneName),
258                    'a' => fix!(ShortWeekdayName),
259                    'b' | 'h' => fix!(ShortMonthName),
260                    'c' => recons![fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName),
261                                   sp!(" "), nums!(Day), sp!(" "), num0!(Hour), lit!(":"),
262                                   num0!(Minute), lit!(":"), num0!(Second), sp!(" "), num0!(Year)],
263                    'd' => num0!(Day),
264                    'e' => nums!(Day),
265                    'f' => num0!(Nanosecond),
266                    'g' => num0!(IsoYearMod100),
267                    'j' => num0!(Ordinal),
268                    'k' => nums!(Hour),
269                    'l' => nums!(Hour12),
270                    'm' => num0!(Month),
271                    'n' => sp!("\n"),
272                    'p' => fix!(UpperAmPm),
273                    'r' => recons![num0!(Hour12), lit!(":"), num0!(Minute), lit!(":"),
274                                   num0!(Second), sp!(" "), fix!(UpperAmPm)],
275                    's' => num!(Timestamp),
276                    't' => sp!("\t"),
277                    'u' => num!(WeekdayFromMon),
278                    'v' => recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"),
279                                   num0!(Year)],
280                    'w' => num!(NumDaysFromSun),
281                    'x' => recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"),
282                                   num0!(YearMod100)],
283                    'y' => num0!(YearMod100),
284                    'z' => if is_alternate {
285                        internal_fix!(TimezoneOffsetPermissive)
286                    } else {
287                        fix!(TimezoneOffset)
288                    },
289                    '+' => fix!(RFC3339),
290                    ':' => match next!() {
291                        'z' => fix!(TimezoneOffsetColon),
292                        _ => Item::Error,
293                    },
294                    '.' => match next!() {
295                        '3' => match next!() {
296                            'f' => fix!(Nanosecond3),
297                            _ => Item::Error,
298                        },
299                        '6' => match next!() {
300                            'f' => fix!(Nanosecond6),
301                            _ => Item::Error,
302                        },
303                        '9' => match next!() {
304                            'f' => fix!(Nanosecond9),
305                            _ => Item::Error,
306                        },
307                        'f' => fix!(Nanosecond),
308                        _ => Item::Error,
309                    },
310                    '3' => match next!() {
311                        'f' => internal_fix!(Nanosecond3NoDot),
312                        _ => Item::Error,
313                    },
314                    '6' => match next!() {
315                        'f' => internal_fix!(Nanosecond6NoDot),
316                        _ => Item::Error,
317                    },
318                    '9' => match next!() {
319                        'f' => internal_fix!(Nanosecond9NoDot),
320                        _ => Item::Error,
321                    },
322                    '%' => lit!("%"),
323                    _ => Item::Error, // no such specifier
324                };
325
326                // adjust `item` if we have any padding modifier
327                if let Some(new_pad) = pad_override {
328                    match item {
329                        Item::Numeric(ref kind, _pad) if self.recons.is_empty() =>
330                            Some(Item::Numeric(kind.clone(), new_pad)),
331                        _ => Some(Item::Error), // no reconstructed or non-numeric item allowed
332                    }
333                } else {
334                    Some(item)
335                }
336            },
337
338            // the next item is space
339            Some(c) if c.is_whitespace() => {
340                // `%` is not a whitespace, so `c != '%'` is redundant
341                let nextspec = self.remainder.find(|c: char| !c.is_whitespace())
342                                             .unwrap_or_else(|| self.remainder.len());
343                assert!(nextspec > 0);
344                let item = sp!(&self.remainder[..nextspec]);
345                self.remainder = &self.remainder[nextspec..];
346                Some(item)
347            },
348
349            // the next item is literal
350            _ => {
351                let nextspec = self.remainder.find(|c: char| c.is_whitespace() || c == '%')
352                                             .unwrap_or_else(|| self.remainder.len());
353                assert!(nextspec > 0);
354                let item = lit!(&self.remainder[..nextspec]);
355                self.remainder = &self.remainder[nextspec..];
356                Some(item)
357            },
358        }
359    }
360}
361
362#[cfg(test)]
363#[test]
364fn test_strftime_items() {
365    fn parse_and_collect<'a>(s: &'a str) -> Vec<Item<'a>> {
366        // map any error into `[Item::Error]`. useful for easy testing.
367        let items = StrftimeItems::new(s);
368        let items = items.map(|spec| if spec == Item::Error {None} else {Some(spec)});
369        items.collect::<Option<Vec<_>>>().unwrap_or(vec![Item::Error])
370    }
371
372    assert_eq!(parse_and_collect(""), []);
373    assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
374    assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
375    assert_eq!(parse_and_collect("a  b\t\nc"), [lit!("a"), sp!("  "), lit!("b"), sp!("\t\n"),
376                                                lit!("c")]);
377    assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
378    assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
379    assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
380    assert_eq!(parse_and_collect("%Y-%m-%d"), [num0!(Year), lit!("-"), num0!(Month), lit!("-"),
381                                               num0!(Day)]);
382    assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
383    assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
384    assert_eq!(parse_and_collect("%"), [Item::Error]);
385    assert_eq!(parse_and_collect("%%"), [lit!("%")]);
386    assert_eq!(parse_and_collect("%%%"), [Item::Error]);
387    assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
388    assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
389    assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
390    assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
391    assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
392    assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
393    assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
394    assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
395    assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
396    assert_eq!(parse_and_collect("%.j"), [Item::Error]);
397    assert_eq!(parse_and_collect("%:j"), [Item::Error]);
398    assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]);
399    assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]);
400    assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]);
401    assert_eq!(parse_and_collect("%.e"), [Item::Error]);
402    assert_eq!(parse_and_collect("%:e"), [Item::Error]);
403    assert_eq!(parse_and_collect("%-e"), [num!(Day)]);
404    assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
405    assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
406    assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
407    assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
408    assert_eq!(parse_and_collect("%#m"), [Item::Error]);
409}
410
411#[cfg(test)]
412#[test]
413fn test_strftime_docs() {
414    use {FixedOffset, TimeZone, Timelike};
415
416    let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708);
417
418    // date specifiers
419    assert_eq!(dt.format("%Y").to_string(), "2001");
420    assert_eq!(dt.format("%C").to_string(), "20");
421    assert_eq!(dt.format("%y").to_string(), "01");
422    assert_eq!(dt.format("%m").to_string(), "07");
423    assert_eq!(dt.format("%b").to_string(), "Jul");
424    assert_eq!(dt.format("%B").to_string(), "July");
425    assert_eq!(dt.format("%h").to_string(), "Jul");
426    assert_eq!(dt.format("%d").to_string(), "08");
427    assert_eq!(dt.format("%e").to_string(), " 8");
428    assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
429    assert_eq!(dt.format("%a").to_string(), "Sun");
430    assert_eq!(dt.format("%A").to_string(), "Sunday");
431    assert_eq!(dt.format("%w").to_string(), "0");
432    assert_eq!(dt.format("%u").to_string(), "7");
433    assert_eq!(dt.format("%U").to_string(), "28");
434    assert_eq!(dt.format("%W").to_string(), "27");
435    assert_eq!(dt.format("%G").to_string(), "2001");
436    assert_eq!(dt.format("%g").to_string(), "01");
437    assert_eq!(dt.format("%V").to_string(), "27");
438    assert_eq!(dt.format("%j").to_string(), "189");
439    assert_eq!(dt.format("%D").to_string(), "07/08/01");
440    assert_eq!(dt.format("%x").to_string(), "07/08/01");
441    assert_eq!(dt.format("%F").to_string(), "2001-07-08");
442    assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
443
444    // time specifiers
445    assert_eq!(dt.format("%H").to_string(), "00");
446    assert_eq!(dt.format("%k").to_string(), " 0");
447    assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
448    assert_eq!(dt.format("%I").to_string(), "12");
449    assert_eq!(dt.format("%l").to_string(), "12");
450    assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
451    assert_eq!(dt.format("%P").to_string(), "am");
452    assert_eq!(dt.format("%p").to_string(), "AM");
453    assert_eq!(dt.format("%M").to_string(), "34");
454    assert_eq!(dt.format("%S").to_string(), "60");
455    assert_eq!(dt.format("%f").to_string(), "026490708");
456    assert_eq!(dt.format("%.f").to_string(), ".026490708");
457    assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(),
458               ".026490");
459    assert_eq!(dt.format("%.3f").to_string(), ".026");
460    assert_eq!(dt.format("%.6f").to_string(), ".026490");
461    assert_eq!(dt.format("%.9f").to_string(), ".026490708");
462    assert_eq!(dt.format("%3f").to_string(), "026");
463    assert_eq!(dt.format("%6f").to_string(), "026490");
464    assert_eq!(dt.format("%9f").to_string(), "026490708");
465    assert_eq!(dt.format("%R").to_string(), "00:34");
466    assert_eq!(dt.format("%T").to_string(), "00:34:60");
467    assert_eq!(dt.format("%X").to_string(), "00:34:60");
468    assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
469
470    // time zone specifiers
471    //assert_eq!(dt.format("%Z").to_string(), "ACST");
472    assert_eq!(dt.format("%z").to_string(), "+0930");
473    assert_eq!(dt.format("%:z").to_string(), "+09:30");
474
475    // date & time specifiers
476    assert_eq!(dt.format("%c").to_string(), "Sun Jul  8 00:34:60 2001");
477    assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
478    assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
479               "2001-07-08T00:34:60.026490+09:30");
480    assert_eq!(dt.format("%s").to_string(), "994518299");
481
482    // special specifiers
483    assert_eq!(dt.format("%t").to_string(), "\t");
484    assert_eq!(dt.format("%n").to_string(), "\n");
485    assert_eq!(dt.format("%%").to_string(), "%");
486}