new formatter design!
- Format string is internally represented as a series of formatting items. Items can be directly given to now-public `format::format` function as well. - Format string parser is separated to `format::strftime` module. This is to allow for potentional alternative formatting syntaxes. - `DelayedFormat` now receives an iterator for formatting items.
This commit is contained in:
parent
98d1ce01c9
commit
43ee68b522
|
@ -17,7 +17,7 @@ use naive;
|
||||||
use naive::date::NaiveDate;
|
use naive::date::NaiveDate;
|
||||||
use naive::time::NaiveTime;
|
use naive::time::NaiveTime;
|
||||||
use datetime::DateTime;
|
use datetime::DateTime;
|
||||||
use format::DelayedFormat;
|
use format::{DelayedFormat, StrftimeItems};
|
||||||
|
|
||||||
/// ISO 8601 calendar date with timezone.
|
/// ISO 8601 calendar date with timezone.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -183,10 +183,11 @@ impl<Off:Offset> Date<Off> {
|
||||||
|
|
||||||
impl<Off: Offset + fmt::Display> Date<Off> {
|
impl<Off: Offset + fmt::Display> Date<Off> {
|
||||||
/// Formats the date in the specified format string.
|
/// Formats the date in the specified format string.
|
||||||
/// See the `format` module on the supported escape sequences.
|
/// See the `format::strftime` module on the supported escape sequences.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> {
|
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a, StrftimeItems<'a>> {
|
||||||
DelayedFormat::new_with_offset(Some(self.local()), None, &self.offset, fmt)
|
DelayedFormat::new_with_offset(Some(self.local()), None, &self.offset,
|
||||||
|
StrftimeItems::new(fmt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ use duration::Duration;
|
||||||
use naive::datetime::NaiveDateTime;
|
use naive::datetime::NaiveDateTime;
|
||||||
use time::Time;
|
use time::Time;
|
||||||
use date::Date;
|
use date::Date;
|
||||||
use format::DelayedFormat;
|
use format::{DelayedFormat, StrftimeItems};
|
||||||
|
|
||||||
/// ISO 8601 combined date and time with timezone.
|
/// ISO 8601 combined date and time with timezone.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -72,11 +72,12 @@ impl<Off:Offset> DateTime<Off> {
|
||||||
|
|
||||||
impl<Off: Offset + fmt::Display> DateTime<Off> {
|
impl<Off: Offset + fmt::Display> DateTime<Off> {
|
||||||
/// Formats the combined date and time in the specified format string.
|
/// Formats the combined date and time in the specified format string.
|
||||||
/// See the `format` module on the supported escape sequences.
|
/// See the `format::strftime` module on the supported escape sequences.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> {
|
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a, StrftimeItems<'a>> {
|
||||||
let local = self.local();
|
let local = self.local();
|
||||||
DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, fmt)
|
DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset,
|
||||||
|
StrftimeItems::new(fmt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
src/div.rs
14
src/div.rs
|
@ -16,14 +16,16 @@ pub fn div_rem<T: Int>(a: T, b: T) -> (T, T) {
|
||||||
(a / b, a % b)
|
(a / b, a % b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `let (q, r) = div_mod_floor(a, b); r`.
|
/// Calculates a floored integer quotient.
|
||||||
|
#[inline]
|
||||||
|
pub fn div_floor<T: Int>(a: T, b: T) -> T {
|
||||||
|
div_mod_floor(a, b).0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates a floored modulo.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mod_floor<T: Int>(a: T, b: T) -> T {
|
pub fn mod_floor<T: Int>(a: T, b: T) -> T {
|
||||||
let zero = Int::zero();
|
div_mod_floor(a, b).1
|
||||||
match a % b {
|
|
||||||
r if (r > zero && b < zero) || (r < zero && b > zero) => r + b,
|
|
||||||
r => r,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates a floored integer quotient and modulo.
|
/// Calculates a floored integer quotient and modulo.
|
||||||
|
|
216
src/format.rs
216
src/format.rs
|
@ -1,216 +0,0 @@
|
||||||
// This is a part of rust-chrono.
|
|
||||||
// Copyright (c) 2014-2015, Kang Seonghoon.
|
|
||||||
// See README.md and LICENSE.txt for details.
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Formatting utilities for date and time.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use {Datelike, Timelike};
|
|
||||||
use duration::Duration;
|
|
||||||
use offset::Offset;
|
|
||||||
use naive::date::NaiveDate;
|
|
||||||
use naive::time::NaiveTime;
|
|
||||||
|
|
||||||
/// The internal workhouse for `DelayedFormat`.
|
|
||||||
fn format(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>,
|
|
||||||
off: Option<&(String, Duration)>, fmt: &str) -> fmt::Result {
|
|
||||||
static SHORT_MONTHS: [&'static str; 12] =
|
|
||||||
["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
||||||
static LONG_MONTHS: [&'static str; 12] =
|
|
||||||
["January", "February", "March", "April", "May", "June",
|
|
||||||
"July", "August", "September", "October", "November", "December"];
|
|
||||||
static SHORT_WEEKDAYS: [&'static str; 7] =
|
|
||||||
["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
||||||
static LONG_WEEKDAYS: [&'static str; 7] =
|
|
||||||
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
|
||||||
|
|
||||||
let mut parts = fmt.split('%');
|
|
||||||
match parts.next() {
|
|
||||||
Some(first) => try!(write!(w, "{}", first)),
|
|
||||||
None => return Ok(()),
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut last_was_percent = false;
|
|
||||||
for part in parts {
|
|
||||||
if last_was_percent { // `%%<part>`
|
|
||||||
last_was_percent = false;
|
|
||||||
try!(write!(w, "%{}", part));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (head, tail) = match part.slice_shift_char() {
|
|
||||||
Some((head, tail)) => (Some(head), tail),
|
|
||||||
None => (None, ""),
|
|
||||||
};
|
|
||||||
match (head, date, time, off) {
|
|
||||||
// year
|
|
||||||
(Some('Y'), Some(d), _, _) => try!(write!(w, "{}", d.year())),
|
|
||||||
(Some('C'), Some(d), _, _) => try!(write!(w, "{:02}", d.year() / 100)),
|
|
||||||
(Some('y'), Some(d), _, _) => try!(write!(w, "{:02}", d.year() % 100)),
|
|
||||||
(Some('G'), Some(d), _, _) => try!(write!(w, "{:04}", d.isoweekdate().0)),
|
|
||||||
(Some('g'), Some(d), _, _) => try!(write!(w, "{:02}", d.isoweekdate().0 % 100)),
|
|
||||||
|
|
||||||
// month
|
|
||||||
(Some('m'), Some(d), _, _) => try!(write!(w, "{:02}", d.month())),
|
|
||||||
(Some('b'), Some(d), _, _) | (Some('h'), Some(d), _, _) =>
|
|
||||||
try!(write!(w, "{}", SHORT_MONTHS[d.month0() as usize])),
|
|
||||||
(Some('B'), Some(d), _, _) =>
|
|
||||||
try!(write!(w, "{}", LONG_MONTHS[d.month0() as usize])),
|
|
||||||
|
|
||||||
// day of month
|
|
||||||
(Some('d'), Some(d), _, _) => try!(write!(w, "{:02}", d.day())),
|
|
||||||
(Some('e'), Some(d), _, _) => try!(write!(w, "{:2}", d.day())),
|
|
||||||
|
|
||||||
// week
|
|
||||||
(Some('U'), Some(d), _, _) =>
|
|
||||||
try!(write!(w, "{:02}", (d.ordinal() - d.weekday().num_days_from_sunday()
|
|
||||||
+ 7) / 7)),
|
|
||||||
(Some('W'), Some(d), _, _) =>
|
|
||||||
try!(write!(w, "{:02}", (d.ordinal() - d.weekday().num_days_from_monday()
|
|
||||||
+ 7) / 7)),
|
|
||||||
(Some('V'), Some(d), _, _) => try!(write!(w, "{:02}", d.isoweekdate().1)),
|
|
||||||
|
|
||||||
// day of week
|
|
||||||
(Some('a'), Some(d), _, _) =>
|
|
||||||
try!(write!(w, "{}", SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as usize])),
|
|
||||||
(Some('A'), Some(d), _, _) =>
|
|
||||||
try!(write!(w, "{}", LONG_WEEKDAYS[d.weekday().num_days_from_monday() as usize])),
|
|
||||||
(Some('w'), Some(d), _, _) => try!(write!(w, "{}", d.weekday().num_days_from_sunday())),
|
|
||||||
(Some('u'), Some(d), _, _) => try!(write!(w, "{}", d.weekday().number_from_monday())),
|
|
||||||
|
|
||||||
// day of year
|
|
||||||
(Some('j'), Some(d), _, _) => try!(write!(w, "{:03}", d.ordinal())),
|
|
||||||
|
|
||||||
// combined date
|
|
||||||
(Some('D'), Some(d), _, _) | (Some('x'), Some(d), _, _) => // `%m/%d/%y`
|
|
||||||
try!(write!(w, "{:02}/{:02}/{:02}", d.month(), d.day(), d.year() % 100)),
|
|
||||||
(Some('F'), Some(d), _, _) => // `%Y-%m-%d'
|
|
||||||
try!(write!(w, "{:04}-{:02}-{:02}", d.year(), d.month(), d.day())),
|
|
||||||
(Some('v'), Some(d), _, _) => // `%e-%b-%Y'
|
|
||||||
try!(write!(w, "{:2}-{}-{:04}", d.day(), SHORT_MONTHS[d.month0() as usize],
|
|
||||||
d.year())),
|
|
||||||
|
|
||||||
// hour
|
|
||||||
(Some('H'), _, Some(t), _) => try!(write!(w, "{:02}", t.hour())),
|
|
||||||
(Some('k'), _, Some(t), _) => try!(write!(w, "{:2}", t.hour())),
|
|
||||||
(Some('I'), _, Some(t), _) => try!(write!(w, "{:02}", t.hour12().1)),
|
|
||||||
(Some('l'), _, Some(t), _) => try!(write!(w, "{:2}", t.hour12().1)),
|
|
||||||
(Some('P'), _, Some(t), _) =>
|
|
||||||
try!(write!(w, "{}", if t.hour12().0 {"pm"} else {"am"})),
|
|
||||||
(Some('p'), _, Some(t), _) =>
|
|
||||||
try!(write!(w, "{}", if t.hour12().0 {"PM"} else {"AM"})),
|
|
||||||
|
|
||||||
// minute
|
|
||||||
(Some('M'), _, Some(t), _) => try!(write!(w, "{:02}", t.minute())),
|
|
||||||
|
|
||||||
// second and below
|
|
||||||
(Some('S'), _, Some(t), _) => try!(write!(w, "{:02}", t.second())),
|
|
||||||
(Some('f'), _, Some(t), _) => try!(write!(w, "{:09}", t.nanosecond())),
|
|
||||||
|
|
||||||
// combined time
|
|
||||||
(Some('R'), _, Some(t), _) => // `%H:%M`
|
|
||||||
try!(write!(w, "{:02}:{:02}", t.hour(), t.minute())),
|
|
||||||
(Some('T'), _, Some(t), _) | (Some('X'), _, Some(t), _) => // `%H:%M:%S`
|
|
||||||
try!(write!(w, "{:02}:{:02}:{:02}", t.hour(), t.minute(), t.second())),
|
|
||||||
(Some('r'), _, Some(t), _) => { // `%I:%M:%S %p`
|
|
||||||
let (is_pm, hour12) = t.hour12();
|
|
||||||
try!(write!(w, "{:02}:{:02}:{:02} {}", hour12, t.minute(), t.second(),
|
|
||||||
if is_pm {"PM"} else {"AM"}))
|
|
||||||
},
|
|
||||||
|
|
||||||
// timezone
|
|
||||||
(Some('Z'), _, _, Some(&(ref name, _))) => try!(write!(w, "{}", *name)),
|
|
||||||
(Some('z'), _, _, Some(&(_, ref local_minus_utc))) => {
|
|
||||||
let off = local_minus_utc.num_minutes();
|
|
||||||
let (sign, off) = if off < 0 {('-', -off)} else {('+', off)};
|
|
||||||
try!(write!(w, "{}{:02}{:02}", sign, off / 60, off % 60))
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
// timestamp
|
|
||||||
(Some('s'), Some(d), Some(t), Some(o)) => { // XXX
|
|
||||||
let datetime = o.from_local_datetime(&d.and_time(t.clone())).unwrap();
|
|
||||||
try!(write!(w, "{}", datetime.num_seconds_from_unix_epoch()))
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
// combined date and time
|
|
||||||
(Some('c'), Some(d), Some(t), _) => // `%a %b %e %T %Y`
|
|
||||||
try!(write!(w, "{} {} {:2} {:02}:{:02}:{:02} {:04}",
|
|
||||||
SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as usize],
|
|
||||||
SHORT_MONTHS[d.month0() as usize], d.day(),
|
|
||||||
t.hour(), t.minute(), t.second(), d.year())),
|
|
||||||
(Some('+'), Some(d), Some(t),
|
|
||||||
Some(&(_, ref local_minus_utc))) => { // `%Y-%m-%dT%H:%M:%S` plus tz
|
|
||||||
let off = local_minus_utc.num_minutes();
|
|
||||||
let (sign, off) = if off < 0 {('-', -off)} else {('+', off)};
|
|
||||||
try!(write!(w, "{}-{:02}-{:02}T{:02}:{:02}:{:02}{}{:02}:{:02}",
|
|
||||||
d.year(), d.month(), d.day(), t.hour(), t.minute(), t.second(),
|
|
||||||
sign, off / 60, off % 60))
|
|
||||||
},
|
|
||||||
|
|
||||||
// special characters
|
|
||||||
(Some('t'), _, _, _) => try!(write!(w, "\t")),
|
|
||||||
(Some('n'), _, _, _) => try!(write!(w, "\n")),
|
|
||||||
|
|
||||||
// TODO issue a detailed error if possible
|
|
||||||
(Some(_), _, _, _) => return Err(fmt::Error),
|
|
||||||
|
|
||||||
(None, _, _, _) => {
|
|
||||||
// if there is the next part, a single `%` and that part should be printed
|
|
||||||
// in verbatim. otherwise it is an error (the stray `%`).
|
|
||||||
last_was_percent = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try!(write!(w, "{}", tail));
|
|
||||||
}
|
|
||||||
|
|
||||||
if last_was_percent { // a stray `%`
|
|
||||||
// TODO issue a detailed error if possible
|
|
||||||
Err(fmt::Error)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A *temporary* object which can be used as an argument to `format!` or others.
|
|
||||||
/// This is normally constructed via `format` methods of each date and time type.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DelayedFormat<'a> {
|
|
||||||
/// The date view, if any.
|
|
||||||
date: Option<NaiveDate>,
|
|
||||||
/// The time view, if any.
|
|
||||||
time: Option<NaiveTime>,
|
|
||||||
/// The name and local-to-UTC difference for the offset (timezone), if any.
|
|
||||||
off: Option<(String, Duration)>,
|
|
||||||
/// The format string.
|
|
||||||
fmt: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DelayedFormat<'a> {
|
|
||||||
/// Makes a new `DelayedFormat` value out of local date and time.
|
|
||||||
pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>,
|
|
||||||
fmt: &'a str) -> DelayedFormat<'a> {
|
|
||||||
DelayedFormat { date: date, time: time, off: None, fmt: fmt }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
|
|
||||||
pub fn new_with_offset<Off>(date: Option<NaiveDate>, time: Option<NaiveTime>,
|
|
||||||
offset: &Off, fmt: &'a str) -> DelayedFormat<'a>
|
|
||||||
where Off: Offset + fmt::Display {
|
|
||||||
let name_and_diff = (offset.to_string(), offset.local_minus_utc());
|
|
||||||
DelayedFormat { date: date, time: time, off: Some(name_and_diff), fmt: fmt }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> fmt::Display for DelayedFormat<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let ret = format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.fmt);
|
|
||||||
ret.map_err(|_| fmt::Error) // we don't have any good means to pass detailed errors...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,276 @@
|
||||||
|
// This is a part of rust-chrono.
|
||||||
|
// Copyright (c) 2014-2015, Kang Seonghoon.
|
||||||
|
// See README.md and LICENSE.txt for details.
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Formatting utilities for date and time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use {Datelike, Timelike};
|
||||||
|
use div::{div_floor, mod_floor};
|
||||||
|
use duration::Duration;
|
||||||
|
use offset::Offset;
|
||||||
|
use naive::date::NaiveDate;
|
||||||
|
use naive::time::NaiveTime;
|
||||||
|
|
||||||
|
pub use self::strftime::StrftimeItems;
|
||||||
|
|
||||||
|
/// Padding characters for numeric items.
|
||||||
|
#[derive(Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Pad {
|
||||||
|
/// No padding.
|
||||||
|
None,
|
||||||
|
/// Zero (`0`) padding.
|
||||||
|
Zero,
|
||||||
|
/// Space padding.
|
||||||
|
Space,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Numeric item types.
|
||||||
|
#[derive(Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Numeric {
|
||||||
|
/// Full Gregorian year.
|
||||||
|
Year,
|
||||||
|
/// Gregorian year divided by 100 (century number). Always rounds towards minus infinity.
|
||||||
|
YearDiv100,
|
||||||
|
/// Gregorian year modulo 100. Cannot be negative.
|
||||||
|
YearMod100,
|
||||||
|
/// Year in the ISO week date.
|
||||||
|
IsoYear,
|
||||||
|
/// Year in the ISO week date, divided by 100. Always rounds towards minus infinity.
|
||||||
|
IsoYearDiv100,
|
||||||
|
/// Year in the ISO week date, modulo 100. Cannot be negative.
|
||||||
|
IsoYearMod100,
|
||||||
|
/// Month.
|
||||||
|
Month,
|
||||||
|
/// Day of the month.
|
||||||
|
Day,
|
||||||
|
/// Week number, where the week 1 starts at the first Sunday of January.
|
||||||
|
WeekFromSun,
|
||||||
|
/// Week number, where the week 1 starts at the first Monday of January.
|
||||||
|
WeekFromMon,
|
||||||
|
/// Week number in the ISO week date.
|
||||||
|
IsoWeek,
|
||||||
|
/// Day of the week, where Sunday = 0 and Saturday = 6.
|
||||||
|
NumDaysFromSun,
|
||||||
|
/// Day of the week, where Monday = 1 and Sunday = 7.
|
||||||
|
WeekdayFromMon,
|
||||||
|
/// Day of the year.
|
||||||
|
Ordinal,
|
||||||
|
/// Hour number in the 24-hour clocks.
|
||||||
|
Hour,
|
||||||
|
/// Hour number in the 12-hour clocks.
|
||||||
|
Hour12,
|
||||||
|
/// The number of minutes since the last whole hour.
|
||||||
|
Minute,
|
||||||
|
/// The number of seconds since the last whole minute.
|
||||||
|
Second,
|
||||||
|
/// The number of nanoseconds since the last whole second.
|
||||||
|
Nanosecond,
|
||||||
|
/// The number of non-leap seconds since January 1, 1970 0:00:00 UTC.
|
||||||
|
Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fixed-format item types.
|
||||||
|
#[derive(Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Fixed {
|
||||||
|
/// Abbreviated month names.
|
||||||
|
ShortMonthName,
|
||||||
|
/// Full month names.
|
||||||
|
LongMonthName,
|
||||||
|
/// Abbreviated day of the week names.
|
||||||
|
ShortWeekdayName,
|
||||||
|
/// FUll day of the week names.
|
||||||
|
LongWeekdayName,
|
||||||
|
/// AM/PM in upper cases.
|
||||||
|
LowerAmPm,
|
||||||
|
/// AM/PM in lower cases.
|
||||||
|
UpperAmPm,
|
||||||
|
/// Timezone name.
|
||||||
|
TimezoneName,
|
||||||
|
/// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`).
|
||||||
|
TimezoneOffset,
|
||||||
|
/// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`).
|
||||||
|
TimezoneOffsetZ,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single formatting item. This is used for both formatting and parsing.
|
||||||
|
#[derive(Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Item<'a> {
|
||||||
|
/// A literally printed and parsed text.
|
||||||
|
Literal(&'a str),
|
||||||
|
/// Whitespace. Prints literally but parses zero or more whitespace.
|
||||||
|
Space(&'a str),
|
||||||
|
/// Numeric item. Can be optionally padded to the maximal length (if any) when formatting;
|
||||||
|
/// the parser simply ignores any padded whitespace and zeroes.
|
||||||
|
Numeric(Numeric, Pad),
|
||||||
|
/// Fixed-format item.
|
||||||
|
Fixed(Fixed),
|
||||||
|
/// Issues a formatting error. Used to signal an invalid format string.
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! lit { ($x:expr) => (Item::Literal($x)) }
|
||||||
|
macro_rules! sp { ($x:expr) => (Item::Space($x)) }
|
||||||
|
macro_rules! num { ($x:ident) => (Item::Numeric(Numeric::$x, Pad::None)) }
|
||||||
|
macro_rules! num0 { ($x:ident) => (Item::Numeric(Numeric::$x, Pad::Zero)) }
|
||||||
|
macro_rules! nums { ($x:ident) => (Item::Numeric(Numeric::$x, Pad::Space)) }
|
||||||
|
macro_rules! fix { ($x:ident) => (Item::Fixed(Fixed::$x)) }
|
||||||
|
|
||||||
|
/// Abbreviated month names.
|
||||||
|
static SHORT_MONTHS: [&'static str; 12] =
|
||||||
|
["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
|
|
||||||
|
/// Full month names.
|
||||||
|
static LONG_MONTHS: [&'static str; 12] =
|
||||||
|
["January", "February", "March", "April", "May", "June",
|
||||||
|
"July", "August", "September", "October", "November", "December"];
|
||||||
|
|
||||||
|
/// Abbreviated weekday names.
|
||||||
|
static SHORT_WEEKDAYS: [&'static str; 7] =
|
||||||
|
["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||||
|
|
||||||
|
/// FUll weekday names.
|
||||||
|
static LONG_WEEKDAYS: [&'static str; 7] =
|
||||||
|
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
||||||
|
|
||||||
|
/// Tries to format given arguments with given formatting items.
|
||||||
|
/// Internally used by `DelayedFormat`.
|
||||||
|
pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>,
|
||||||
|
off: Option<&(String, Duration)>, items: I) -> fmt::Result
|
||||||
|
where I: Iterator<Item=Item<'a>> {
|
||||||
|
for item in items {
|
||||||
|
match item {
|
||||||
|
Item::Literal(s) | Item::Space(s) => try!(write!(w, "{}", s)),
|
||||||
|
|
||||||
|
Item::Numeric(spec, pad) => {
|
||||||
|
use self::Numeric::*;
|
||||||
|
let week_from_sun =
|
||||||
|
|&: d: &NaiveDate| (d.ordinal() - d.weekday().num_days_from_sunday() + 7) / 7;
|
||||||
|
let week_from_mon =
|
||||||
|
|&: d: &NaiveDate| (d.ordinal() - d.weekday().num_days_from_monday() + 7) / 7;
|
||||||
|
let (width, v) = match spec {
|
||||||
|
Year => (4, date.map(|d| d.year() as i64)),
|
||||||
|
YearDiv100 => (2, date.map(|d| div_floor(d.year() as i64, 100))),
|
||||||
|
YearMod100 => (2, date.map(|d| mod_floor(d.year() as i64, 100))),
|
||||||
|
IsoYear => (4, date.map(|d| d.isoweekdate().0 as i64)),
|
||||||
|
IsoYearDiv100 => (2, date.map(|d| div_floor(d.isoweekdate().0 as i64, 100))),
|
||||||
|
IsoYearMod100 => (2, date.map(|d| mod_floor(d.isoweekdate().0 as i64, 100))),
|
||||||
|
Month => (2, date.map(|d| d.month() as i64)),
|
||||||
|
Day => (2, date.map(|d| d.day() as i64)),
|
||||||
|
WeekFromSun => (2, date.map(|d| week_from_sun(d) as i64)),
|
||||||
|
WeekFromMon => (2, date.map(|d| week_from_mon(d) as i64)),
|
||||||
|
IsoWeek => (2, date.map(|d| d.isoweekdate().1 as i64)),
|
||||||
|
NumDaysFromSun => (1, date.map(|d| d.weekday().num_days_from_sunday() as i64)),
|
||||||
|
WeekdayFromMon => (1, date.map(|d| d.weekday().number_from_monday() as i64)),
|
||||||
|
Ordinal => (3, date.map(|d| d.ordinal() as i64)),
|
||||||
|
Hour => (2, time.map(|t| t.hour() as i64)),
|
||||||
|
Hour12 => (2, time.map(|t| t.hour12().1 as i64)),
|
||||||
|
Minute => (2, time.map(|t| t.minute() as i64)),
|
||||||
|
Second => (2, time.map(|t| (t.second() +
|
||||||
|
t.nanosecond() / 1_000_000_000) as i64)),
|
||||||
|
Nanosecond => (9, time.map(|t| (t.nanosecond() % 1_000_000_000) as i64)),
|
||||||
|
Timestamp => (1, match (date, time) {
|
||||||
|
(Some(d), Some(t)) => Some(d.and_time(*t).num_seconds_from_unix_epoch()),
|
||||||
|
(_, _) => None
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
if let Some(v) = v {
|
||||||
|
match pad {
|
||||||
|
Pad::None => try!(write!(w, "{}", v)),
|
||||||
|
Pad::Zero => try!(write!(w, "{:01$}", v, width)),
|
||||||
|
Pad::Space => try!(write!(w, "{:1$}", v, width)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(fmt::Error); // insufficient arguments for given format
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Item::Fixed(spec) => {
|
||||||
|
use self::Fixed::*;
|
||||||
|
let ret = match spec {
|
||||||
|
ShortMonthName =>
|
||||||
|
date.map(|d| write!(w, "{}", SHORT_MONTHS[d.month0() as usize])),
|
||||||
|
LongMonthName =>
|
||||||
|
date.map(|d| write!(w, "{}", LONG_MONTHS[d.month0() as usize])),
|
||||||
|
ShortWeekdayName =>
|
||||||
|
date.map(|d| write!(w, "{}",
|
||||||
|
SHORT_WEEKDAYS[d.weekday() .num_days_from_monday() as usize])),
|
||||||
|
LongWeekdayName =>
|
||||||
|
date.map(|d| write!(w, "{}",
|
||||||
|
LONG_WEEKDAYS[d.weekday().num_days_from_monday() as usize])),
|
||||||
|
LowerAmPm =>
|
||||||
|
time.map(|t| write!(w, "{}", if t.hour12().0 {"pm"} else {"am"})),
|
||||||
|
UpperAmPm =>
|
||||||
|
time.map(|t| write!(w, "{}", if t.hour12().0 {"PM"} else {"AM"})),
|
||||||
|
TimezoneName =>
|
||||||
|
off.map(|&(ref name, _)| write!(w, "{}", *name)),
|
||||||
|
TimezoneOffset =>
|
||||||
|
off.map(|&(_, ref local_minus_utc)| {
|
||||||
|
let off = local_minus_utc.num_minutes();
|
||||||
|
let (sign, off) = if off < 0 {('-', -off)} else {('+', off)};
|
||||||
|
write!(w, "{}{:02}{:02}", sign, off / 60, off % 60)
|
||||||
|
}),
|
||||||
|
TimezoneOffsetZ =>
|
||||||
|
off.map(|&(_, ref local_minus_utc)| {
|
||||||
|
let off = local_minus_utc.num_minutes();
|
||||||
|
if off != 0 {
|
||||||
|
let (sign, off) = if off < 0 {('-', -off)} else {('+', off)};
|
||||||
|
write!(w, "{}{:02}{:02}", sign, off / 60, off % 60)
|
||||||
|
} else {
|
||||||
|
write!(w, "Z")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match ret {
|
||||||
|
Some(ret) => try!(ret),
|
||||||
|
None => return Err(fmt::Error), // insufficient arguments for given format
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Item::Error => return Err(fmt::Error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A *temporary* object which can be used as an argument to `format!` or others.
|
||||||
|
/// This is normally constructed via `format` methods of each date and time type.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DelayedFormat<'a, I: Iterator<Item=Item<'a>> + Clone> {
|
||||||
|
/// The date view, if any.
|
||||||
|
date: Option<NaiveDate>,
|
||||||
|
/// The time view, if any.
|
||||||
|
time: Option<NaiveTime>,
|
||||||
|
/// The name and local-to-UTC difference for the offset (timezone), if any.
|
||||||
|
off: Option<(String, Duration)>,
|
||||||
|
/// An iterator returning formatting items.
|
||||||
|
items: I,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: Iterator<Item=Item<'a>> + Clone> DelayedFormat<'a, I> {
|
||||||
|
/// Makes a new `DelayedFormat` value out of local date and time.
|
||||||
|
pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<'a, I> {
|
||||||
|
DelayedFormat { date: date, time: time, off: None, items: items }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
|
||||||
|
pub fn new_with_offset<Off>(date: Option<NaiveDate>, time: Option<NaiveTime>,
|
||||||
|
offset: &Off, items: I) -> DelayedFormat<'a, I>
|
||||||
|
where Off: Offset + fmt::Display {
|
||||||
|
let name_and_diff = (offset.to_string(), offset.local_minus_utc());
|
||||||
|
DelayedFormat { date: date, time: time, off: Some(name_and_diff), items: items }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: Iterator<Item=Item<'a>> + Clone> fmt::Display for DelayedFormat<'a, I> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod strftime;
|
||||||
|
|
|
@ -0,0 +1,272 @@
|
||||||
|
// This is a part of rust-chrono.
|
||||||
|
// Copyright (c) 2015, Kang Seonghoon.
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
```plain
|
||||||
|
Spec. Example Description
|
||||||
|
----- ------- -----------
|
||||||
|
|
||||||
|
DATE SPECIFIERS:
|
||||||
|
|
||||||
|
%Y 2001 The full proleptic Gregorian year, zero-padded to 4 digits.
|
||||||
|
Negative years are allowed in formatting but not in parsing.
|
||||||
|
%C 20 The proleptic Gregorian year divided by 100, zero-padded to 2 digits.
|
||||||
|
This is floor division, so 100 BCE (year number -99) will print `-1`.
|
||||||
|
%y 01 The proleptic Gregorian year modulo 100, zero-padded to 2 digits.
|
||||||
|
This is floor division, so 100 BCE (year number -99) will print `99`.
|
||||||
|
|
||||||
|
%m 07 Month number (01--12), zero-padded to 2 digits.
|
||||||
|
%b Jul Abbreviated month name. Always 3 letters.
|
||||||
|
%B July Full month name.
|
||||||
|
%h Jul Same to `%b`.
|
||||||
|
|
||||||
|
%d 08 Day number (01--31), zero-padded to 2 digits.
|
||||||
|
%e 8 Same to `%d` but space-padded.
|
||||||
|
|
||||||
|
%a Sun Abbreviated weekday name. Always 3 letters.
|
||||||
|
%A Sunday Full weekday name.
|
||||||
|
%w 0 Sunday = 0, Monday = 1, ..., Saturday = 6.
|
||||||
|
%u 7 Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601)
|
||||||
|
|
||||||
|
%U 28 Week number (00--53), zero-padded to 2 digits.
|
||||||
|
Week 1 starts with the first Sunday in that year.
|
||||||
|
It is possible to have week 0 for days before the first Sunday.
|
||||||
|
%W 27 Same to `%U`, but week 1 starts with the first Monday in that year.
|
||||||
|
|
||||||
|
%G 2001 Same to `%Y` but uses the year number in ISO 8601 week date.
|
||||||
|
%g 01 Same to `%y` but uses the year number in ISO 8601 week date.
|
||||||
|
%V 27 Same to `%U` but uses the week number in ISO 8601 week date (01--53).
|
||||||
|
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`.
|
||||||
|
|
||||||
|
%j 189 Day of the year (001--366), zero-padded to 3 digits.
|
||||||
|
|
||||||
|
%D 08/07/2001 Month-day-year format. Same to `%m/%d/%Y`.
|
||||||
|
%x 08/07/2001 Same to `%D`.
|
||||||
|
%F 2001-07-08 Year-month-day format (ISO 8601). Same to `%Y-%m-%d`.
|
||||||
|
%v 7-Jul-2001 Day-month-year format. Same to `%e-%b-%Y`.
|
||||||
|
|
||||||
|
TIME SPECIFIERS:
|
||||||
|
|
||||||
|
%H 00 Hour number (00--23), zero-padded to 2 digits.
|
||||||
|
%k 0 Same to `%H` but space-padded.
|
||||||
|
%I 12 Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.
|
||||||
|
%l 12 Same to `%I` but space-padded.
|
||||||
|
|
||||||
|
%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.
|
||||||
|
It accounts for leap seconds, so `60` is possible.
|
||||||
|
%f 026413966 The number of nanoseconds since the last whole second,
|
||||||
|
zero-padded to 9 digits.
|
||||||
|
|
||||||
|
%R 00:34 Hour-minute format. Same to `%H:%M`.
|
||||||
|
%T 00:34:60 Hour-minute-second format. Same to `%H:%M:%S`.
|
||||||
|
%x 00:34:60 Same to `%T`.
|
||||||
|
%r 12:34:60 AM Hour-minute-second format in 12-hour clocks. Same to `%I:%M:%S %p`.
|
||||||
|
|
||||||
|
TIME ZONE SPECIFIERS:
|
||||||
|
|
||||||
|
%Z ACST Local time zone name.
|
||||||
|
%z +09:30 Offset from the local time to UTC (with UTC being `+00:00`).
|
||||||
|
|
||||||
|
DATE & TIME SPECIFIERS:
|
||||||
|
|
||||||
|
%c Sun Jul 8 00:34:60 2001
|
||||||
|
`ctime` date & time format. Same to `%a %b %e %T %Y`. (No newline!)
|
||||||
|
%+ 2001-07-08T00:34:60+09:30
|
||||||
|
ISO 8601 date & time format. Same to `%Y-%m-%dT%H:%M:%S%z`.
|
||||||
|
|
||||||
|
%s 994485899 UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC.
|
||||||
|
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.
|
||||||
|
|
||||||
|
SPECIAL SPECIFIERS:
|
||||||
|
|
||||||
|
%t Literal tab (`\t`).
|
||||||
|
%n Literal newline (`\n`).
|
||||||
|
%% Literal percent sign.
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::{Item, Numeric, Fixed, Pad};
|
||||||
|
|
||||||
|
/// Parsing iterator for `strftime`-like format strings.
|
||||||
|
#[derive(Clone)]
|
||||||
|
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: &'static [Item<'static>],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StrftimeItems<'a> {
|
||||||
|
/// Creates a new parsing iterator from the `strftime`-like format string.
|
||||||
|
pub fn new(s: &'a str) -> StrftimeItems<'a> {
|
||||||
|
static FMT_NONE: [Item<'static>; 0] = [];
|
||||||
|
StrftimeItems { remainder: s, recons: &FMT_NONE }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = self.recons[0];
|
||||||
|
self.recons = &self.recons[1..];
|
||||||
|
return Some(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.remainder.slice_shift_char() {
|
||||||
|
// we are done
|
||||||
|
None => return None,
|
||||||
|
|
||||||
|
// the next item is a specifier
|
||||||
|
Some(('%', remainder)) => {
|
||||||
|
self.remainder = remainder;
|
||||||
|
|
||||||
|
let (spec, remainder) = match self.remainder.slice_shift_char() {
|
||||||
|
Some(x) => x,
|
||||||
|
None => return Some(Item::Error), // premature end of string
|
||||||
|
};
|
||||||
|
self.remainder = remainder;
|
||||||
|
|
||||||
|
macro_rules! recons {
|
||||||
|
[$head:expr, $($tail:expr),+] => ({
|
||||||
|
const RECONS: &'static [Item<'static>] = &[$($tail),+];
|
||||||
|
self.recons = RECONS;
|
||||||
|
$head
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
match spec {
|
||||||
|
'A' => Some(fix!(LongWeekdayName)),
|
||||||
|
'B' => Some(fix!(LongMonthName)),
|
||||||
|
'C' => Some(num0!(YearDiv100)),
|
||||||
|
'D' => Some(recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"),
|
||||||
|
num0!(YearMod100)]),
|
||||||
|
'F' => Some(recons![num!(Year), lit!("-"), num0!(Month), lit!("-"),
|
||||||
|
num0!(Day)]),
|
||||||
|
'G' => Some(num!(IsoYear)),
|
||||||
|
'H' => Some(num0!(Hour)),
|
||||||
|
'I' => Some(num0!(Hour12)),
|
||||||
|
'M' => Some(num0!(Minute)),
|
||||||
|
'P' => Some(fix!(LowerAmPm)),
|
||||||
|
'R' => Some(recons![num0!(Hour), lit!(":"), num0!(Minute)]),
|
||||||
|
'S' => Some(num0!(Second)),
|
||||||
|
'T' => Some(recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"),
|
||||||
|
num0!(Second)]),
|
||||||
|
'U' => Some(num0!(WeekFromSun)),
|
||||||
|
'V' => Some(num0!(IsoWeek)),
|
||||||
|
'W' => Some(num0!(WeekFromMon)),
|
||||||
|
'X' => Some(recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"),
|
||||||
|
num0!(Second)]),
|
||||||
|
'Y' => Some(num!(Year)),
|
||||||
|
'Z' => Some(fix!(TimezoneName)),
|
||||||
|
'a' => Some(fix!(ShortWeekdayName)),
|
||||||
|
'b' => Some(fix!(ShortMonthName)),
|
||||||
|
'c' => Some(recons![fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName),
|
||||||
|
sp!(" "), nums!(Day), sp!(" "), num0!(Hour), lit!(":"),
|
||||||
|
num0!(Minute), lit!(":"), num0!(Second), sp!(" "),
|
||||||
|
num!(Year)]),
|
||||||
|
'd' => Some(num0!(Day)),
|
||||||
|
'e' => Some(nums!(Day)),
|
||||||
|
'f' => Some(num0!(Nanosecond)),
|
||||||
|
'g' => Some(num0!(IsoYearMod100)),
|
||||||
|
'h' => Some(fix!(ShortMonthName)),
|
||||||
|
'j' => Some(num0!(Ordinal)),
|
||||||
|
'k' => Some(nums!(Hour)),
|
||||||
|
'l' => Some(nums!(Hour12)),
|
||||||
|
'm' => Some(num0!(Month)),
|
||||||
|
'n' => Some(sp!("\n")),
|
||||||
|
'p' => Some(fix!(UpperAmPm)),
|
||||||
|
'r' => Some(recons![num0!(Hour12), lit!(":"), num0!(Minute), lit!(":"),
|
||||||
|
num0!(Second), sp!(" "), fix!(UpperAmPm)]),
|
||||||
|
's' => Some(num!(Timestamp)),
|
||||||
|
't' => Some(sp!("\t")),
|
||||||
|
'u' => Some(num!(WeekdayFromMon)),
|
||||||
|
'v' => Some(recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"),
|
||||||
|
num!(Year)]),
|
||||||
|
'w' => Some(num!(NumDaysFromSun)),
|
||||||
|
'x' => Some(recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"),
|
||||||
|
num0!(YearMod100)]),
|
||||||
|
'y' => Some(num0!(YearMod100)),
|
||||||
|
'z' => Some(fix!(TimezoneOffset)),
|
||||||
|
'+' => Some(recons![num!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day),
|
||||||
|
lit!("T"), num0!(Hour), lit!(":"), num0!(Minute), lit!(":"),
|
||||||
|
num0!(Second), fix!(TimezoneOffset)]),
|
||||||
|
'%' => Some(lit!("%")),
|
||||||
|
_ => Some(Item::Error), // no such specifier
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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(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(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"), [num!(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]);
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use div::div_mod_floor;
|
||||||
use duration::Duration;
|
use duration::Duration;
|
||||||
use naive::time::NaiveTime;
|
use naive::time::NaiveTime;
|
||||||
use naive::datetime::NaiveDateTime;
|
use naive::datetime::NaiveDateTime;
|
||||||
use format::DelayedFormat;
|
use format::{DelayedFormat, StrftimeItems};
|
||||||
|
|
||||||
use self::internals::{DateImpl, Of, Mdf, YearFlags};
|
use self::internals::{DateImpl, Of, Mdf, YearFlags};
|
||||||
|
|
||||||
|
@ -303,10 +303,10 @@ impl NaiveDate {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Formats the date in the specified format string.
|
/// Formats the date in the specified format string.
|
||||||
/// See the `format` module on the supported escape sequences.
|
/// See the `format::strftime` module on the supported escape sequences.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> {
|
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a, StrftimeItems<'a>> {
|
||||||
DelayedFormat::new(Some(self.clone()), None, fmt)
|
DelayedFormat::new(Some(self.clone()), None, StrftimeItems::new(fmt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ use div::div_mod_floor;
|
||||||
use duration::Duration;
|
use duration::Duration;
|
||||||
use naive::time::NaiveTime;
|
use naive::time::NaiveTime;
|
||||||
use naive::date::NaiveDate;
|
use naive::date::NaiveDate;
|
||||||
use format::DelayedFormat;
|
use format::{DelayedFormat, StrftimeItems};
|
||||||
|
|
||||||
/// ISO 8601 combined date and time without timezone.
|
/// ISO 8601 combined date and time without timezone.
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||||
|
@ -82,10 +82,11 @@ impl NaiveDateTime {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Formats the combined date and time in the specified format string.
|
/// Formats the combined date and time in the specified format string.
|
||||||
/// See the `format` module on the supported escape sequences.
|
/// See the `format::strftime` module on the supported escape sequences.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> {
|
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a, StrftimeItems<'a>> {
|
||||||
DelayedFormat::new(Some(self.date.clone()), Some(self.time.clone()), fmt)
|
DelayedFormat::new(Some(self.date.clone()), Some(self.time.clone()),
|
||||||
|
StrftimeItems::new(fmt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +282,13 @@ mod tests {
|
||||||
fn test_datetime_format() {
|
fn test_datetime_format() {
|
||||||
let dt = NaiveDate::from_ymd(2010, 9, 8).and_hms_milli(7, 6, 54, 321);
|
let dt = NaiveDate::from_ymd(2010, 9, 8).and_hms_milli(7, 6, 54, 321);
|
||||||
assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010");
|
assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010");
|
||||||
|
assert_eq!(dt.format("%s").to_string(), "1283929614");
|
||||||
assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
|
assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
|
||||||
|
|
||||||
|
// a horror of leap second: coming near to you.
|
||||||
|
let dt = NaiveDate::from_ymd(2012, 6, 30).and_hms_milli(23, 59, 59, 1_000);
|
||||||
|
assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012");
|
||||||
|
assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ use Timelike;
|
||||||
use div::div_mod_floor;
|
use div::div_mod_floor;
|
||||||
use offset::Offset;
|
use offset::Offset;
|
||||||
use duration::Duration;
|
use duration::Duration;
|
||||||
use format::DelayedFormat;
|
use format::{DelayedFormat, StrftimeItems};
|
||||||
|
|
||||||
/// ISO 8601 time without timezone.
|
/// ISO 8601 time without timezone.
|
||||||
/// Allows for the nanosecond precision and optional leap second representation.
|
/// Allows for the nanosecond precision and optional leap second representation.
|
||||||
|
@ -119,10 +119,10 @@ impl NaiveTime {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Formats the time in the specified format string.
|
/// Formats the time in the specified format string.
|
||||||
/// See the `format` module on the supported escape sequences.
|
/// See the `format::strftime` module on the supported escape sequences.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> {
|
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a, StrftimeItems<'a>> {
|
||||||
DelayedFormat::new(None, Some(self.clone()), fmt)
|
DelayedFormat::new(None, Some(self.clone()), StrftimeItems::new(fmt))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a triple of the hour, minute and second numbers.
|
/// Returns a triple of the hour, minute and second numbers.
|
||||||
|
@ -381,6 +381,8 @@ mod tests {
|
||||||
|
|
||||||
// corner cases
|
// corner cases
|
||||||
assert_eq!(NaiveTime::from_hms(13, 57, 9).format("%r").to_string(), "01:57:09 PM");
|
assert_eq!(NaiveTime::from_hms(13, 57, 9).format("%r").to_string(), "01:57:09 PM");
|
||||||
|
assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(),
|
||||||
|
"23:59:60");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ use Timelike;
|
||||||
use offset::Offset;
|
use offset::Offset;
|
||||||
use duration::Duration;
|
use duration::Duration;
|
||||||
use naive::time::NaiveTime;
|
use naive::time::NaiveTime;
|
||||||
use format::DelayedFormat;
|
use format::{DelayedFormat, StrftimeItems};
|
||||||
|
|
||||||
/// ISO 8601 time with timezone.
|
/// ISO 8601 time with timezone.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -52,10 +52,11 @@ impl<Off:Offset> Time<Off> {
|
||||||
|
|
||||||
impl<Off: Offset + fmt::Display> Time<Off> {
|
impl<Off: Offset + fmt::Display> Time<Off> {
|
||||||
/// Formats the time in the specified format string.
|
/// Formats the time in the specified format string.
|
||||||
/// See the `format` module on the supported escape sequences.
|
/// See the `format::strftime` module on the supported escape sequences.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> {
|
pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a, StrftimeItems<'a>> {
|
||||||
DelayedFormat::new_with_offset(None, Some(self.local()), &self.offset, fmt)
|
DelayedFormat::new_with_offset(None, Some(self.local()), &self.offset,
|
||||||
|
StrftimeItems::new(fmt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue