From 8b2444c27f4843661fe52d4b6f0e8024f434a344 Mon Sep 17 00:00:00 2001 From: SamokhinIlya Date: Fri, 21 Jun 2019 23:44:49 +0300 Subject: [PATCH 1/3] DelayFormat now works with alignment and width --- src/format/mod.rs | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index bd8337b..d95ea78 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -350,8 +350,8 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt 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) => try!(w.pad(s)), + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => try!(w.pad(s)), Item::Numeric(spec, pad) => { use self::Numeric::*; @@ -402,15 +402,15 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt 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)), + Pad::None => try!(w.pad(&format!("{:+}", v))), + Pad::Zero => try!(w.pad(&format!("{:+01$}", v, width + 1))), + Pad::Space => try!(w.pad(&format!("{:+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)), + Pad::None => try!(w.pad(&v.to_string())), + Pad::Zero => try!(w.pad(&format!("{:01$}", v, width))), + Pad::Space => try!(w.pad(&format!("{:1$}", v, width))), } } } else { @@ -429,75 +429,75 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt 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) + w.pad(&format!("{}{:02}:{:02}", sign, off / 3600, off / 60 % 60)) } else { - write!(w, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) + w.pad(&format!("{}{:02}{:02}", sign, off / 3600, off / 60 % 60)) } } else { - write!(w, "Z") + w.pad("Z") } } let ret = match spec { ShortMonthName => - date.map(|d| write!(w, "{}", SHORT_MONTHS[d.month0() as usize])), + date.map(|d| w.pad(SHORT_MONTHS[d.month0() as usize])), LongMonthName => - date.map(|d| write!(w, "{}", LONG_MONTHS[d.month0() as usize])), + date.map(|d| w.pad(LONG_MONTHS[d.month0() as usize])), ShortWeekdayName => - date.map(|d| write!(w, "{}", + date.map(|d| w.pad( SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as usize])), LongWeekdayName => - date.map(|d| write!(w, "{}", + date.map(|d| w.pad( LONG_WEEKDAYS[d.weekday().num_days_from_monday() as usize])), LowerAmPm => - time.map(|t| write!(w, "{}", if t.hour12().0 {"pm"} else {"am"})), + time.map(|t| w.pad(if t.hour12().0 {"pm"} else {"am"})), UpperAmPm => - time.map(|t| write!(w, "{}", if t.hour12().0 {"PM"} else {"AM"})), + time.map(|t| w.pad(if t.hour12().0 {"PM"} else {"AM"})), 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) + w.pad(&format!(".{:03}", nano / 1_000_000)) } else if nano % 1_000 == 0 { - write!(w, ".{:06}", nano / 1_000) + w.pad(&format!(".{:06}", nano / 1_000)) } else { - write!(w, ".{:09}", nano) + w.pad(&format!(".{:09}", nano)) } }), Nanosecond3 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:03}", nano / 1_000_000) + w.pad(&format!(".{:03}", nano / 1_000_000)) }), Nanosecond6 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:06}", nano / 1_000) + w.pad(&format!(".{:06}", nano / 1_000)) }), Nanosecond9 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, ".{:09}", nano) + w.pad(&format!(".{: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) + w.pad(&format!("{: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) + w.pad(&format!("{:06}", nano / 1_000)) }), Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:09}", nano) + w.pad(&format!("{:09}", nano)) }), TimezoneName => - off.map(|&(ref name, _)| write!(w, "{}", *name)), + off.map(|&(ref name, _)| w.pad(name)), TimezoneOffsetColon => off.map(|&(_, off)| write_local_minus_utc(w, off, false, true)), TimezoneOffsetColonZ => @@ -511,10 +511,10 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt 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} ", + try!(w.pad(&format!("{}, {: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)); + t.hour(), t.minute(), sec))); Some(write_local_minus_utc(w, off, false, false)) } else { None @@ -523,7 +523,7 @@ 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)); + try!(w.pad(&format!("{:?}T{:?}", d, t))); Some(write_local_minus_utc(w, off, false, true)) } else { None From f57ac3671e730a702ebd174c4f03c398f84352fc Mon Sep 17 00:00:00 2001 From: Samokhin Ilya Date: Sun, 23 Jun 2019 09:43:06 +0300 Subject: [PATCH 2/3] add test and format every item into String first, then pad it --- src/datetime.rs | 30 ++++++++++ src/format/mod.rs | 149 +++++++++++++++++++++++++++++----------------- 2 files changed, 125 insertions(+), 54 deletions(-) diff --git a/src/datetime.rs b/src/datetime.rs index 8098410..e6a0657 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 d95ea78..565e648 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -334,9 +334,13 @@ 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>( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + items: impl Iterator>, +) -> fmt::Result { // 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 +352,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!(w.pad(s)), - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => try!(w.pad(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 +405,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!(w.pad(&format!("{:+}", v))), - Pad::Zero => try!(w.pad(&format!("{:+01$}", v, width + 1))), - Pad::Space => try!(w.pad(&format!("{:+1$}", v, width + 1))), + 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 { - match pad { - Pad::None => try!(w.pad(&v.to_string())), - Pad::Zero => try!(w.pad(&format!("{:01$}", v, width))), - Pad::Space => try!(w.pad(&format!("{:1$}", v, width))), - } - } + ) } else { - return Err(fmt::Error); // insufficient arguments for given format + return Err(fmt::Error) // insufficient arguments for given format } }, @@ -423,99 +433,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 { - w.pad(&format!("{}{:02}:{:02}", sign, off / 3600, off / 60 % 60)) + write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) } else { - w.pad(&format!("{}{:02}{:02}", sign, off / 3600, off / 60 % 60)) + write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) } } else { - w.pad("Z") + result.push_str("Z"); + Ok(()) } } let ret = match spec { ShortMonthName => - date.map(|d| w.pad(SHORT_MONTHS[d.month0() as usize])), + date.map(|d| { + result.push_str(SHORT_MONTHS[d.month0() as usize]); + Ok(()) + }), LongMonthName => - date.map(|d| w.pad(LONG_MONTHS[d.month0() as usize])), + date.map(|d| { + result.push_str(LONG_MONTHS[d.month0() as usize]); + Ok(()) + }), ShortWeekdayName => - date.map(|d| w.pad( - 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| w.pad( - 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| w.pad(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| w.pad(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 { - w.pad(&format!(".{:03}", nano / 1_000_000)) + write!(result, ".{:03}", nano / 1_000_000) } else if nano % 1_000 == 0 { - w.pad(&format!(".{:06}", nano / 1_000)) + write!(result, ".{:06}", nano / 1_000) } else { - w.pad(&format!(".{:09}", nano)) + write!(result, ".{:09}", nano) } }), Nanosecond3 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - w.pad(&format!(".{:03}", nano / 1_000_000)) + write!(result, ".{:03}", nano / 1_000_000) }), Nanosecond6 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - w.pad(&format!(".{:06}", nano / 1_000)) + write!(result, ".{:06}", nano / 1_000) }), Nanosecond9 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - w.pad(&format!(".{:09}", nano)) + write!(result, ".{:09}", nano) }), Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; - w.pad(&format!("{: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; - w.pad(&format!("{: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; - w.pad(&format!("{:09}", nano)) + write!(result, "{:09}", nano) }), TimezoneName => - off.map(|&(ref name, _)| w.pad(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!(w.pad(&format!("{}, {: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 +564,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!(w.pad(&format!("{:?}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 +581,7 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt } } - Ok(()) + w.pad(&result) } mod parsed; From 5b0818e0a1ba1dac59b984b4d6ad26f29aad2b41 Mon Sep 17 00:00:00 2001 From: Samokhin Ilya Date: Sun, 23 Jun 2019 10:09:47 +0300 Subject: [PATCH 3/3] changed back from impl Trait in fn parameter to support older compiler versions --- src/format/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 565e648..a0a9333 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -334,13 +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>( +pub fn format<'a, I>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, - items: impl Iterator>, -) -> fmt::Result { + 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"];