diff --git a/src/datetime.rs b/src/datetime.rs index 8098410c40..e6a0657b9b 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1721,4 +1721,34 @@ mod tests { assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); } + + #[test] + fn test_datetime_format_alignment() { + let datetime = Utc.ymd(2007, 01, 02); + + // Item::Literal + let percent = datetime.format("%%"); + assert_eq!(" %", format!("{:>3}", percent)); + assert_eq!("% ", format!("{:<3}", percent)); + assert_eq!(" % ", format!("{:^3}", percent)); + + // Item::Numeric + let year = datetime.format("%Y"); + assert_eq!(" 2007", format!("{:>6}", year)); + assert_eq!("2007 ", format!("{:<6}", year)); + assert_eq!(" 2007 ", format!("{:^6}", year)); + + // Item::Fixed + let tz = datetime.format("%Z"); + assert_eq!(" UTC", format!("{:>5}", tz)); + assert_eq!("UTC ", format!("{:<5}", tz)); + assert_eq!(" UTC ", format!("{:^5}", tz)); + + // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] + let ymd = datetime.format("%Y %B %d"); + let ymd_formatted = "2007 January 02"; + assert_eq!(format!(" {}", ymd_formatted), format!("{:>17}", ymd)); + assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd)); + assert_eq!(format!(" {} ", ymd_formatted), format!("{:^17}", ymd)); + } } diff --git a/src/format/mod.rs b/src/format/mod.rs index bd8337ba00..a0a9333f5f 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -334,9 +334,15 @@ const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); /// 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, FixedOffset)>, items: I) -> fmt::Result - where I: Iterator> { +pub fn format<'a, I>( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + items: I, +) -> fmt::Result + where I: Iterator> +{ // full and abbreviated month and weekday names static SHORT_MONTHS: [&'static str; 12] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; @@ -348,10 +354,13 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt static LONG_WEEKDAYS: [&'static str; 7] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; + use std::fmt::Write; + let mut result = String::new(); + for item in items { match item { - Item::Literal(s) | Item::Space(s) => try!(write!(w, "{}", s)), - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => try!(write!(w, "{}", s)), + Item::Literal(s) | Item::Space(s) => result.push_str(s), + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), Item::Numeric(spec, pad) => { use self::Numeric::*; @@ -398,23 +407,26 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt Internal(ref int) => match int._dummy {}, }; + if let Some(v) = v { - if (spec == Year || spec == IsoYear) && !(0 <= v && v < 10_000) { - // non-four-digit years require an explicit sign as per ISO 8601 - match pad { - Pad::None => try!(write!(w, "{:+}", v)), - Pad::Zero => try!(write!(w, "{:+01$}", v, width + 1)), - Pad::Space => try!(write!(w, "{:+1$}", v, width + 1)), - } - } else { - match pad { - Pad::None => try!(write!(w, "{}", v)), - Pad::Zero => try!(write!(w, "{:01$}", v, width)), - Pad::Space => try!(write!(w, "{:1$}", v, width)), + try!( + if (spec == Year || spec == IsoYear) && !(0 <= v && v < 10_000) { + // non-four-digit years require an explicit sign as per ISO 8601 + match pad { + Pad::None => write!(result, "{:+}", v), + Pad::Zero => write!(result, "{:+01$}", v, width + 1), + Pad::Space => write!(result, "{:+1$}", v, width + 1), + } + } else { + match pad { + Pad::None => write!(result, "{}", v), + Pad::Zero => write!(result, "{:01$}", v, width), + Pad::Space => write!(result, "{:1$}", v, width), + } } - } + ) } else { - return Err(fmt::Error); // insufficient arguments for given format + return Err(fmt::Error) // insufficient arguments for given format } }, @@ -423,99 +435,130 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt /// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. /// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. - fn write_local_minus_utc(w: &mut fmt::Formatter, off: FixedOffset, - allow_zulu: bool, use_colon: bool) -> fmt::Result { + fn write_local_minus_utc( + result: &mut String, + off: FixedOffset, + allow_zulu: bool, + use_colon: bool, + ) -> fmt::Result { let off = off.local_minus_utc(); if !allow_zulu || off != 0 { let (sign, off) = if off < 0 {('-', -off)} else {('+', off)}; if use_colon { - write!(w, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) + write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) } else { - write!(w, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) + write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) } } else { - write!(w, "Z") + result.push_str("Z"); + Ok(()) } } let ret = match spec { ShortMonthName => - date.map(|d| write!(w, "{}", SHORT_MONTHS[d.month0() as usize])), + date.map(|d| { + result.push_str(SHORT_MONTHS[d.month0() as usize]); + Ok(()) + }), LongMonthName => - date.map(|d| write!(w, "{}", LONG_MONTHS[d.month0() as usize])), + date.map(|d| { + result.push_str(LONG_MONTHS[d.month0() as usize]); + Ok(()) + }), ShortWeekdayName => - date.map(|d| write!(w, "{}", - SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as usize])), + date.map(|d| { + result.push_str( + SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as usize] + ); + Ok(()) + }), LongWeekdayName => - date.map(|d| write!(w, "{}", - LONG_WEEKDAYS[d.weekday().num_days_from_monday() as usize])), + date.map(|d| { + result.push_str( + LONG_WEEKDAYS[d.weekday().num_days_from_monday() as usize] + ); + Ok(()) + }), LowerAmPm => - time.map(|t| write!(w, "{}", if t.hour12().0 {"pm"} else {"am"})), + time.map(|t| { + result.push_str(if t.hour12().0 {"pm"} else {"am"}); + Ok(()) + }), UpperAmPm => - time.map(|t| write!(w, "{}", if t.hour12().0 {"PM"} else {"AM"})), + time.map(|t| { + result.push_str(if t.hour12().0 {"PM"} else {"AM"}); + Ok(()) + }), Nanosecond => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; if nano == 0 { Ok(()) } else if nano % 1_000_000 == 0 { - write!(w, ".{:03}", nano / 1_000_000) + write!(result, ".{:03}", nano / 1_000_000) } else if nano % 1_000 == 0 { - write!(w, ".{:06}", nano / 1_000) + write!(result, ".{:06}", nano / 1_000) } else { - write!(w, ".{:09}", nano) + write!(result, ".{:09}", nano) } }), Nanosecond3 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:03}", nano / 1_000_000) + write!(result, ".{:03}", nano / 1_000_000) }), Nanosecond6 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:06}", nano / 1_000) + write!(result, ".{:06}", nano / 1_000) }), Nanosecond9 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:09}", nano) + write!(result, ".{:09}", nano) }), Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:03}", nano / 1_000_000) + write!(result, "{:03}", nano / 1_000_000) }), Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:06}", nano / 1_000) + write!(result, "{:06}", nano / 1_000) }), Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:09}", nano) + write!(result, "{:09}", nano) }), TimezoneName => - off.map(|&(ref name, _)| write!(w, "{}", *name)), + off.map(|&(ref name, _)| { + result.push_str(name); + Ok(()) + }), TimezoneOffsetColon => - off.map(|&(_, off)| write_local_minus_utc(w, off, false, true)), + off.map(|&(_, off)| write_local_minus_utc(&mut result, off, false, true)), TimezoneOffsetColonZ => - off.map(|&(_, off)| write_local_minus_utc(w, off, true, true)), + off.map(|&(_, off)| write_local_minus_utc(&mut result, off, true, true)), TimezoneOffset => - off.map(|&(_, off)| write_local_minus_utc(w, off, false, false)), + off.map(|&(_, off)| write_local_minus_utc(&mut result, off, false, false)), TimezoneOffsetZ => - off.map(|&(_, off)| write_local_minus_utc(w, off, true, false)), + off.map(|&(_, off)| write_local_minus_utc(&mut result, off, true, false)), Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => panic!("Do not try to write %#z it is undefined"), RFC2822 => // same to `%a, %e %b %Y %H:%M:%S %z` if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { let sec = t.second() + t.nanosecond() / 1_000_000_000; - try!(write!(w, "{}, {:2} {} {:04} {:02}:{:02}:{:02} ", - SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as usize], - d.day(), SHORT_MONTHS[d.month0() as usize], d.year(), - t.hour(), t.minute(), sec)); - Some(write_local_minus_utc(w, off, false, false)) + try!(write!( + result, + "{}, {:2} {} {:04} {:02}:{:02}:{:02} ", + SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as usize], + d.day(), SHORT_MONTHS[d.month0() as usize], d.year(), + t.hour(), t.minute(), sec + )); + Some(write_local_minus_utc(&mut result, off, false, false)) } else { None }, @@ -523,8 +566,8 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { // reuse `Debug` impls which already print ISO 8601 format. // this is faster in this way. - try!(write!(w, "{:?}T{:?}", d, t)); - Some(write_local_minus_utc(w, off, false, true)) + try!(write!(result, "{:?}T{:?}", d, t)); + Some(write_local_minus_utc(&mut result, off, false, true)) } else { None }, @@ -540,7 +583,7 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt } } - Ok(()) + w.pad(&result) } mod parsed;