// This is a part of rust-chrono. // Copyright (c) 2014, Kang Seonghoon. // See README.md and LICENSE.txt for details. /*! * Formatting utilities for date and time. */ use std::fmt; use std::str::MaybeOwned; use std::io::{IoResult, IoError, InvalidInput}; 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 Writer, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(MaybeOwned<'static>, Duration)>, fmt: &str) -> IoResult<()> { 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 { // `%%` 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().val0())), (Some('g'), Some(d), _, _) => try!(write!(w, "{:02}", d.isoweekdate().val0() % 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 uint])), (Some('B'), Some(d), _, _) => try!(write!(w, "{}", LONG_MONTHS[d.month0() as uint])), // 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().val1())), // day of week (Some('a'), Some(d), _, _) => try!(write!(w, "{}", SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as uint])), (Some('A'), Some(d), _, _) => try!(write!(w, "{}", LONG_WEEKDAYS[d.weekday().num_days_from_monday() as uint])), (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 uint], 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().val1())), (Some('l'), _, Some(t), _) => try!(write!(w, "{:2}", t.hour12().val1())), (Some('P'), _, Some(t), _) => try!(write!(w, "{}", if t.hour12().val0() {"pm"} else {"am"})), (Some('p'), _, Some(t), _) => try!(write!(w, "{}", if t.hour12().val0() {"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 uint], SHORT_MONTHS[d.month0() as uint], 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")), (Some(c), _, _, _) => { return Err(IoError { kind: InvalidInput, desc: "invalid date/time format", detail: Some(format!("unsupported escape sequence %{}", c)) }); } (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 `%` Err(IoError { kind: InvalidInput, desc: "invalid date/time format: stray `%`", detail: None }) } 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. pub struct DelayedFormat<'a> { /// The date view, if any. date: Option, /// The time view, if any. time: Option, /// The name and local-to-UTC difference for the offset (timezone), if any. off: Option<(MaybeOwned<'static>, 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, time: Option, 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(date: Option, time: Option, offset: &Off, fmt: &'a str) -> DelayedFormat<'a> { let name_and_diff = (offset.name(), offset.local_minus_utc()); DelayedFormat { date: date, time: time, off: Some(name_and_diff), fmt: fmt } } } impl<'a> fmt::Show 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::WriteError) // XXX } }