| // This is a part of Chrono. |
| // See README.md and LICENSE.txt for details. |
| |
| /*! |
| `strftime`/`strptime`-inspired date and time formatting syntax. |
| |
| ## Specifiers |
| |
| The following specifiers are available both to formatting and parsing. |
| |
| | Spec. | Example | Description | |
| |-------|----------|----------------------------------------------------------------------------| |
| | | | **DATE SPECIFIERS:** | |
| | `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. [^1] | |
| | `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^2] | |
| | `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^2] | |
| | | | | |
| | `%m` | `07` | Month number (01--12), zero-padded to 2 digits. | |
| | `%b` | `Jul` | Abbreviated month name. Always 3 letters. | |
| | `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. | |
| | `%h` | `Jul` | Same as `%b`. | |
| | | | | |
| | `%d` | `08` | Day number (01--31), zero-padded to 2 digits. | |
| | `%e` | ` 8` | Same as `%d` but space-padded. Same as `%_d`. | |
| | | | | |
| | `%a` | `Sun` | Abbreviated weekday name. Always 3 letters. | |
| | `%A` | `Sunday` | Full weekday name. Also accepts corresponding abbreviation in parsing. | |
| | `%w` | `0` | Sunday = 0, Monday = 1, ..., Saturday = 6. | |
| | `%u` | `7` | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) | |
| | | | | |
| | `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^3] | |
| | `%W` | `27` | Same as `%U`, but week 1 starts with the first Monday in that year instead.| |
| | | | | |
| | `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^4] | |
| | `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^4] | |
| | `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^4] | |
| | | | | |
| | `%j` | `189` | Day of the year (001--366), zero-padded to 3 digits. | |
| | | | | |
| | `%D` | `07/08/01` | Month-day-year format. Same as `%m/%d/%y`. | |
| | `%x` | `07/08/01` | Locale's date representation (e.g., 12/31/99). | |
| | `%F` | `2001-07-08` | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`. | |
| | `%v` | ` 8-Jul-2001` | Day-month-year format. Same as `%e-%b-%Y`. | |
| | | | | |
| | | | **TIME SPECIFIERS:** | |
| | `%H` | `00` | Hour number (00--23), zero-padded to 2 digits. | |
| | `%k` | ` 0` | Same as `%H` but space-padded. Same as `%_H`. | |
| | `%I` | `12` | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits. | |
| | `%l` | `12` | Same as `%I` but space-padded. Same as `%_I`. | |
| | | | | |
| | `%P` | `am` | `am` or `pm` in 12-hour clocks. | |
| | `%p` | `AM` | `AM` or `PM` in 12-hour clocks. | |
| | | | | |
| | `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. | |
| | `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^5] | |
| | `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^8] | |
| | `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^8] | |
| | `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^8] | |
| | `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^8] | |
| | `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^8] | |
| | `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^8] | |
| | `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^8] | |
| | `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^8] | |
| | | | | |
| | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | |
| | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | |
| | `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). | |
| | `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. | |
| | | | | |
| | | | **TIME ZONE SPECIFIERS:** | |
| | `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^9] | |
| | `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). | |
| | `%:z` | `+09:30` | Same as `%z` but with a colon. | |
| | `%#z` | `+09` | *Parsing only:* Same as `%z` but allows minutes to be missing or present. | |
| | | | | |
| | | | **DATE & TIME SPECIFIERS:** | |
| |`%c`|`Sun Jul 8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar 3 23:05:25 2005). | |
| | `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^6] | |
| | | | | |
| | `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^7]| |
| | | | | |
| | | | **SPECIAL SPECIFIERS:** | |
| | `%t` | | Literal tab (`\t`). | |
| | `%n` | | Literal newline (`\n`). | |
| | `%%` | | Literal percent sign. | |
| |
| It is possible to override the default padding behavior of numeric specifiers `%?`. |
| This is not allowed for other specifiers and will result in the `BAD_FORMAT` error. |
| |
| Modifier | Description |
| -------- | ----------- |
| `%-?` | Suppresses any padding including spaces and zeroes. (e.g. `%j` = `012`, `%-j` = `12`) |
| `%_?` | Uses spaces as a padding. (e.g. `%j` = `012`, `%_j` = ` 12`) |
| `%0?` | Uses zeroes as a padding. (e.g. `%e` = ` 9`, `%0e` = `09`) |
| |
| Notes: |
| |
| [^1]: `%Y`: |
| Negative years are allowed in formatting but not in parsing. |
| |
| [^2]: `%C`, `%y`: |
| This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively. |
| |
| [^3]: `%U`: |
| Week 1 starts with the first Sunday in that year. |
| It is possible to have week 0 for days before the first Sunday. |
| |
| [^4]: `%G`, `%g`, `%V`: |
| Week 1 is the first week with at least 4 days in that year. |
| Week 0 does not exist, so this should be used with `%G` or `%g`. |
| |
| [^5]: `%S`: |
| It accounts for leap seconds, so `60` is possible. |
| |
| [^6]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional |
| digits for seconds and colons in the time zone offset. |
| <br> |
| <br> |
| The typical `strftime` implementations have different (and locale-dependent) |
| formats for this specifier. While Chrono's format for `%+` is far more |
| stable, it is best to avoid this specifier if you want to control the exact |
| output. |
| |
| [^7]: `%s`: |
| This is not padded and can be negative. |
| For the purpose of Chrono, it only accounts for non-leap seconds |
| so it slightly differs from ISO C `strftime` behavior. |
| |
| [^8]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`: |
| <br> |
| The default `%f` is right-aligned and always zero-padded to 9 digits |
| for the compatibility with glibc and others, |
| so it always counts the number of nanoseconds since the last whole second. |
| E.g. 7ms after the last second will print `007000000`, |
| and parsing `7000000` will yield the same. |
| <br> |
| <br> |
| The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits |
| according to the precision. |
| E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`), |
| and parsing `.07`, `.070000` etc. will yield the same. |
| Note that they can print or read nothing if the fractional part is zero or |
| the next character is not `.`. |
| <br> |
| <br> |
| The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits |
| according to the number preceding `f`. |
| E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`), |
| and parsing `.07`, `.070000` etc. will yield the same. |
| Note that they can read nothing if the fractional part is zero or |
| the next character is not `.` however will print with the specified length. |
| <br> |
| <br> |
| The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits |
| according to the number preceding `f`, but without the leading dot. |
| E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`), |
| and parsing `07`, `070000` etc. will yield the same. |
| Note that they can read nothing if the fractional part is zero. |
| |
| [^9]: `%Z`: |
| Offset will not be populated from the parsed data, nor will it be validated. |
| Timezone is completely ignored. Similar to the glibc `strptime` treatment of |
| this format code. |
| <br> |
| <br> |
| It is not possible to reliably convert from an abbreviation to an offset, |
| for example CDT can mean either Central Daylight Time (North America) or |
| China Daylight Time. |
| */ |
| |
| #[cfg(feature = "unstable-locales")] |
| use super::{locales, Locale}; |
| use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; |
| |
| #[cfg(feature = "unstable-locales")] |
| type Fmt<'a> = Vec<Item<'a>>; |
| #[cfg(not(feature = "unstable-locales"))] |
| type Fmt<'a> = &'static [Item<'static>]; |
| |
| static D_FMT: &'static [Item<'static>] = |
| &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; |
| static D_T_FMT: &'static [Item<'static>] = &[ |
| fix!(ShortWeekdayName), |
| sp!(" "), |
| fix!(ShortMonthName), |
| sp!(" "), |
| nums!(Day), |
| sp!(" "), |
| num0!(Hour), |
| lit!(":"), |
| num0!(Minute), |
| lit!(":"), |
| num0!(Second), |
| sp!(" "), |
| num0!(Year), |
| ]; |
| static T_FMT: &'static [Item<'static>] = |
| &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; |
| |
| /// Parsing iterator for `strftime`-like format strings. |
| #[derive(Clone, Debug)] |
| pub struct StrftimeItems<'a> { |
| /// Remaining portion of the string. |
| remainder: &'a str, |
| /// If the current specifier is composed of multiple formatting items (e.g. `%+`), |
| /// parser refers to the statically reconstructed slice of them. |
| /// If `recons` is not empty they have to be returned earlier than the `remainder`. |
| recons: Fmt<'a>, |
| /// Date format |
| d_fmt: Fmt<'a>, |
| /// Date and time format |
| d_t_fmt: Fmt<'a>, |
| /// Time format |
| t_fmt: Fmt<'a>, |
| } |
| |
| impl<'a> StrftimeItems<'a> { |
| /// Creates a new parsing iterator from the `strftime`-like format string. |
| pub fn new(s: &'a str) -> StrftimeItems<'a> { |
| Self::with_remainer(s) |
| } |
| |
| /// Creates a new parsing iterator from the `strftime`-like format string. |
| #[cfg(feature = "unstable-locales")] |
| pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { |
| let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect(); |
| let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); |
| let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect(); |
| |
| StrftimeItems { |
| remainder: s, |
| recons: Vec::new(), |
| d_fmt: d_fmt, |
| d_t_fmt: d_t_fmt, |
| t_fmt: t_fmt, |
| } |
| } |
| |
| #[cfg(not(feature = "unstable-locales"))] |
| fn with_remainer(s: &'a str) -> StrftimeItems<'a> { |
| static FMT_NONE: &'static [Item<'static>; 0] = &[]; |
| |
| StrftimeItems { |
| remainder: s, |
| recons: FMT_NONE, |
| d_fmt: D_FMT, |
| d_t_fmt: D_T_FMT, |
| t_fmt: T_FMT, |
| } |
| } |
| |
| #[cfg(feature = "unstable-locales")] |
| fn with_remainer(s: &'a str) -> StrftimeItems<'a> { |
| StrftimeItems { |
| remainder: s, |
| recons: Vec::new(), |
| d_fmt: D_FMT.to_vec(), |
| d_t_fmt: D_T_FMT.to_vec(), |
| t_fmt: T_FMT.to_vec(), |
| } |
| } |
| } |
| |
| const HAVE_ALTERNATES: &'static str = "z"; |
| |
| impl<'a> Iterator for StrftimeItems<'a> { |
| type Item = Item<'a>; |
| |
| fn next(&mut self) -> Option<Item<'a>> { |
| // we have some reconstructed items to return |
| if !self.recons.is_empty() { |
| let item; |
| #[cfg(feature = "unstable-locales")] |
| { |
| item = self.recons.remove(0); |
| } |
| #[cfg(not(feature = "unstable-locales"))] |
| { |
| item = self.recons[0].clone(); |
| self.recons = &self.recons[1..]; |
| } |
| return Some(item); |
| } |
| |
| match self.remainder.chars().next() { |
| // we are done |
| None => None, |
| |
| // the next item is a specifier |
| Some('%') => { |
| self.remainder = &self.remainder[1..]; |
| |
| macro_rules! next { |
| () => { |
| match self.remainder.chars().next() { |
| Some(x) => { |
| self.remainder = &self.remainder[x.len_utf8()..]; |
| x |
| } |
| None => return Some(Item::Error), // premature end of string |
| } |
| }; |
| } |
| |
| let spec = next!(); |
| let pad_override = match spec { |
| '-' => Some(Pad::None), |
| '0' => Some(Pad::Zero), |
| '_' => Some(Pad::Space), |
| _ => None, |
| }; |
| let is_alternate = spec == '#'; |
| let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; |
| if is_alternate && !HAVE_ALTERNATES.contains(spec) { |
| return Some(Item::Error); |
| } |
| |
| macro_rules! recons { |
| [$head:expr, $($tail:expr),+ $(,)*] => ({ |
| #[cfg(feature = "unstable-locales")] |
| { |
| self.recons.clear(); |
| $(self.recons.push($tail);)+ |
| } |
| #[cfg(not(feature = "unstable-locales"))] |
| { |
| const RECONS: &'static [Item<'static>] = &[$($tail),+]; |
| self.recons = RECONS; |
| } |
| $head |
| }) |
| } |
| |
| macro_rules! recons_from_slice { |
| ($slice:expr) => {{ |
| #[cfg(feature = "unstable-locales")] |
| { |
| self.recons.clear(); |
| self.recons.extend_from_slice(&$slice[1..]); |
| } |
| #[cfg(not(feature = "unstable-locales"))] |
| { |
| self.recons = &$slice[1..]; |
| } |
| $slice[0].clone() |
| }}; |
| } |
| |
| let item = match spec { |
| 'A' => fix!(LongWeekdayName), |
| 'B' => fix!(LongMonthName), |
| 'C' => num0!(YearDiv100), |
| 'D' => { |
| recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] |
| } |
| 'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], |
| 'G' => num0!(IsoYear), |
| 'H' => num0!(Hour), |
| 'I' => num0!(Hour12), |
| 'M' => num0!(Minute), |
| 'P' => fix!(LowerAmPm), |
| 'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)], |
| 'S' => num0!(Second), |
| 'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], |
| 'U' => num0!(WeekFromSun), |
| 'V' => num0!(IsoWeek), |
| 'W' => num0!(WeekFromMon), |
| 'X' => recons_from_slice!(self.t_fmt), |
| 'Y' => num0!(Year), |
| 'Z' => fix!(TimezoneName), |
| 'a' => fix!(ShortWeekdayName), |
| 'b' | 'h' => fix!(ShortMonthName), |
| 'c' => recons_from_slice!(self.d_t_fmt), |
| 'd' => num0!(Day), |
| 'e' => nums!(Day), |
| 'f' => num0!(Nanosecond), |
| 'g' => num0!(IsoYearMod100), |
| 'j' => num0!(Ordinal), |
| 'k' => nums!(Hour), |
| 'l' => nums!(Hour12), |
| 'm' => num0!(Month), |
| 'n' => sp!("\n"), |
| 'p' => fix!(UpperAmPm), |
| 'r' => recons![ |
| num0!(Hour12), |
| lit!(":"), |
| num0!(Minute), |
| lit!(":"), |
| num0!(Second), |
| sp!(" "), |
| fix!(UpperAmPm) |
| ], |
| 's' => num!(Timestamp), |
| 't' => sp!("\t"), |
| 'u' => num!(WeekdayFromMon), |
| 'v' => { |
| recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] |
| } |
| 'w' => num!(NumDaysFromSun), |
| 'x' => recons_from_slice!(self.d_fmt), |
| 'y' => num0!(YearMod100), |
| 'z' => { |
| if is_alternate { |
| internal_fix!(TimezoneOffsetPermissive) |
| } else { |
| fix!(TimezoneOffset) |
| } |
| } |
| '+' => fix!(RFC3339), |
| ':' => match next!() { |
| 'z' => fix!(TimezoneOffsetColon), |
| _ => Item::Error, |
| }, |
| '.' => match next!() { |
| '3' => match next!() { |
| 'f' => fix!(Nanosecond3), |
| _ => Item::Error, |
| }, |
| '6' => match next!() { |
| 'f' => fix!(Nanosecond6), |
| _ => Item::Error, |
| }, |
| '9' => match next!() { |
| 'f' => fix!(Nanosecond9), |
| _ => Item::Error, |
| }, |
| 'f' => fix!(Nanosecond), |
| _ => Item::Error, |
| }, |
| '3' => match next!() { |
| 'f' => internal_fix!(Nanosecond3NoDot), |
| _ => Item::Error, |
| }, |
| '6' => match next!() { |
| 'f' => internal_fix!(Nanosecond6NoDot), |
| _ => Item::Error, |
| }, |
| '9' => match next!() { |
| 'f' => internal_fix!(Nanosecond9NoDot), |
| _ => Item::Error, |
| }, |
| '%' => lit!("%"), |
| _ => Item::Error, // no such specifier |
| }; |
| |
| // adjust `item` if we have any padding modifier |
| if let Some(new_pad) = pad_override { |
| match item { |
| Item::Numeric(ref kind, _pad) if self.recons.is_empty() => { |
| Some(Item::Numeric(kind.clone(), new_pad)) |
| } |
| _ => Some(Item::Error), // no reconstructed or non-numeric item allowed |
| } |
| } else { |
| Some(item) |
| } |
| } |
| |
| // the next item is space |
| Some(c) if c.is_whitespace() => { |
| // `%` is not a whitespace, so `c != '%'` is redundant |
| let nextspec = self |
| .remainder |
| .find(|c: char| !c.is_whitespace()) |
| .unwrap_or_else(|| self.remainder.len()); |
| assert!(nextspec > 0); |
| let item = sp!(&self.remainder[..nextspec]); |
| self.remainder = &self.remainder[nextspec..]; |
| Some(item) |
| } |
| |
| // the next item is literal |
| _ => { |
| let nextspec = self |
| .remainder |
| .find(|c: char| c.is_whitespace() || c == '%') |
| .unwrap_or_else(|| self.remainder.len()); |
| assert!(nextspec > 0); |
| let item = lit!(&self.remainder[..nextspec]); |
| self.remainder = &self.remainder[nextspec..]; |
| Some(item) |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| #[test] |
| fn test_strftime_items() { |
| fn parse_and_collect<'a>(s: &'a str) -> Vec<Item<'a>> { |
| // map any error into `[Item::Error]`. useful for easy testing. |
| let items = StrftimeItems::new(s); |
| let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); |
| items.collect::<Option<Vec<_>>>().unwrap_or(vec![Item::Error]) |
| } |
| |
| assert_eq!(parse_and_collect(""), []); |
| assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]); |
| assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]); |
| assert_eq!( |
| parse_and_collect("a b\t\nc"), |
| [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")] |
| ); |
| assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]); |
| assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]); |
| assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]); |
| assert_eq!( |
| parse_and_collect("%Y-%m-%d"), |
| [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] |
| ); |
| assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); |
| assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]); |
| assert_eq!(parse_and_collect("%"), [Item::Error]); |
| assert_eq!(parse_and_collect("%%"), [lit!("%")]); |
| assert_eq!(parse_and_collect("%%%"), [Item::Error]); |
| assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]); |
| assert_eq!(parse_and_collect("foo%?"), [Item::Error]); |
| assert_eq!(parse_and_collect("bar%42"), [Item::Error]); |
| assert_eq!(parse_and_collect("quux% +"), [Item::Error]); |
| assert_eq!(parse_and_collect("%.Z"), [Item::Error]); |
| assert_eq!(parse_and_collect("%:Z"), [Item::Error]); |
| assert_eq!(parse_and_collect("%-Z"), [Item::Error]); |
| assert_eq!(parse_and_collect("%0Z"), [Item::Error]); |
| assert_eq!(parse_and_collect("%_Z"), [Item::Error]); |
| assert_eq!(parse_and_collect("%.j"), [Item::Error]); |
| assert_eq!(parse_and_collect("%:j"), [Item::Error]); |
| assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]); |
| assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]); |
| assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]); |
| assert_eq!(parse_and_collect("%.e"), [Item::Error]); |
| assert_eq!(parse_and_collect("%:e"), [Item::Error]); |
| assert_eq!(parse_and_collect("%-e"), [num!(Day)]); |
| assert_eq!(parse_and_collect("%0e"), [num0!(Day)]); |
| assert_eq!(parse_and_collect("%_e"), [nums!(Day)]); |
| assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]); |
| assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]); |
| assert_eq!(parse_and_collect("%#m"), [Item::Error]); |
| } |
| |
| #[cfg(test)] |
| #[test] |
| fn test_strftime_docs() { |
| use {FixedOffset, TimeZone, Timelike}; |
| |
| let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); |
| |
| // date specifiers |
| assert_eq!(dt.format("%Y").to_string(), "2001"); |
| assert_eq!(dt.format("%C").to_string(), "20"); |
| assert_eq!(dt.format("%y").to_string(), "01"); |
| assert_eq!(dt.format("%m").to_string(), "07"); |
| assert_eq!(dt.format("%b").to_string(), "Jul"); |
| assert_eq!(dt.format("%B").to_string(), "July"); |
| assert_eq!(dt.format("%h").to_string(), "Jul"); |
| assert_eq!(dt.format("%d").to_string(), "08"); |
| assert_eq!(dt.format("%e").to_string(), " 8"); |
| assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string()); |
| assert_eq!(dt.format("%a").to_string(), "Sun"); |
| assert_eq!(dt.format("%A").to_string(), "Sunday"); |
| assert_eq!(dt.format("%w").to_string(), "0"); |
| assert_eq!(dt.format("%u").to_string(), "7"); |
| assert_eq!(dt.format("%U").to_string(), "28"); |
| assert_eq!(dt.format("%W").to_string(), "27"); |
| assert_eq!(dt.format("%G").to_string(), "2001"); |
| assert_eq!(dt.format("%g").to_string(), "01"); |
| assert_eq!(dt.format("%V").to_string(), "27"); |
| assert_eq!(dt.format("%j").to_string(), "189"); |
| assert_eq!(dt.format("%D").to_string(), "07/08/01"); |
| assert_eq!(dt.format("%x").to_string(), "07/08/01"); |
| assert_eq!(dt.format("%F").to_string(), "2001-07-08"); |
| assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001"); |
| |
| // time specifiers |
| assert_eq!(dt.format("%H").to_string(), "00"); |
| assert_eq!(dt.format("%k").to_string(), " 0"); |
| assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string()); |
| assert_eq!(dt.format("%I").to_string(), "12"); |
| assert_eq!(dt.format("%l").to_string(), "12"); |
| assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string()); |
| assert_eq!(dt.format("%P").to_string(), "am"); |
| assert_eq!(dt.format("%p").to_string(), "AM"); |
| assert_eq!(dt.format("%M").to_string(), "34"); |
| assert_eq!(dt.format("%S").to_string(), "60"); |
| assert_eq!(dt.format("%f").to_string(), "026490708"); |
| assert_eq!(dt.format("%.f").to_string(), ".026490708"); |
| assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490"); |
| assert_eq!(dt.format("%.3f").to_string(), ".026"); |
| assert_eq!(dt.format("%.6f").to_string(), ".026490"); |
| assert_eq!(dt.format("%.9f").to_string(), ".026490708"); |
| assert_eq!(dt.format("%3f").to_string(), "026"); |
| assert_eq!(dt.format("%6f").to_string(), "026490"); |
| assert_eq!(dt.format("%9f").to_string(), "026490708"); |
| assert_eq!(dt.format("%R").to_string(), "00:34"); |
| assert_eq!(dt.format("%T").to_string(), "00:34:60"); |
| assert_eq!(dt.format("%X").to_string(), "00:34:60"); |
| assert_eq!(dt.format("%r").to_string(), "12:34:60 AM"); |
| |
| // time zone specifiers |
| //assert_eq!(dt.format("%Z").to_string(), "ACST"); |
| assert_eq!(dt.format("%z").to_string(), "+0930"); |
| assert_eq!(dt.format("%:z").to_string(), "+09:30"); |
| |
| // date & time specifiers |
| assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); |
| assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); |
| assert_eq!( |
| dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), |
| "2001-07-08T00:34:60.026490+09:30" |
| ); |
| assert_eq!(dt.format("%s").to_string(), "994518299"); |
| |
| // special specifiers |
| assert_eq!(dt.format("%t").to_string(), "\t"); |
| assert_eq!(dt.format("%n").to_string(), "\n"); |
| assert_eq!(dt.format("%%").to_string(), "%"); |
| } |
| |
| #[cfg(feature = "unstable-locales")] |
| #[test] |
| fn test_strftime_docs_localized() { |
| use {FixedOffset, TimeZone}; |
| |
| let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); |
| |
| // date specifiers |
| assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); |
| assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); |
| assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); |
| assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); |
| assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); |
| assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); |
| assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); |
| assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); |
| assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); |
| |
| // time specifiers |
| assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); |
| assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); |
| assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); |
| assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); |
| assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); |
| assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 "); |
| |
| // date & time specifiers |
| assert_eq!( |
| dt.format_localized("%c", Locale::fr_BE).to_string(), |
| "dim 08 jui 2001 00:34:60 +09:30" |
| ); |
| } |