Skip to content

Commit

Permalink
Merge pull request #320 from SamokhinIlya/DelayFormatAlign
Browse files Browse the repository at this point in the history
DelayFormat now works with alignment and width
  • Loading branch information
quodlibetor committed Jun 25, 2019
2 parents d8c68f6 + 5b0818e commit 77e2770
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 54 deletions.
30 changes: 30 additions & 0 deletions src/datetime.rs
Expand Up @@ -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));
}
}
151 changes: 97 additions & 54 deletions src/format/mod.rs
Expand Up @@ -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<Item=Item<'a>> {
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<Item=Item<'a>>
{
// 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"];
Expand All @@ -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::*;
Expand Down Expand Up @@ -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
}
},

Expand All @@ -423,108 +435,139 @@ 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
},
RFC3339 => // same to `%Y-%m-%dT%H:%M:%S%.f%:z`
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
},
Expand All @@ -540,7 +583,7 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt
}
}

Ok(())
w.pad(&result)
}

mod parsed;
Expand Down

0 comments on commit 77e2770

Please sign in to comment.