Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DelayFormat now works with alignment and width #320

Merged
merged 3 commits into from Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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