1 //! Datetime-to-string routines. 2 3 use std::fmt::Display; 4 use std::io; 5 use std::io::Write; 6 use std::str::CharIndices; 7 8 use cal::{DatePiece, TimePiece}; 9 10 use locale; 11 use num_traits::PrimInt; 12 use pad::{PadStr, Alignment}; 13 14 15 #[derive(PartialEq, Eq, Clone, Copy, Debug)] 16 pub enum Field<'a> { 17 Literal(&'a str), 18 19 Year(NumArguments), 20 YearOfCentury(NumArguments), 21 22 MonthName(bool, TextArguments), 23 24 Day(NumArguments), 25 WeekdayName(bool, TextArguments), 26 27 Hour(NumArguments), 28 Minute(NumArguments), 29 Second(NumArguments), 30 } 31 32 impl<'a> Field<'a> { format<T>(&self, when: &T, w: &mut Vec<u8>, locale: &locale::Time) -> io::Result<()> where T: DatePiece+TimePiece33 fn format<T>(&self, when: &T, w: &mut Vec<u8>, locale: &locale::Time) -> io::Result<()> where T: DatePiece+TimePiece { 34 match *self { 35 Field::Literal(s) => w.write_all(s.as_bytes()), 36 Field::Year(a) => a.format(w, when.year()), 37 Field::YearOfCentury(a) => a.format(w, when.year_of_century()), 38 Field::MonthName(true, a) => a.format(w, &locale.long_month_name(when.month() as usize - 1)[..]), 39 Field::MonthName(false, a) => a.format(w, &locale.short_month_name(when.month() as usize - 1)[..]), 40 Field::Day(a) => a.format(w, when.day()), 41 Field::WeekdayName(true, a) => a.format(w, &locale.long_day_name(when.weekday() as usize)[..]), 42 Field::WeekdayName(false, a) => a.format(w, &locale.short_day_name(when.weekday() as usize)[..]), 43 Field::Hour(a) => a.format(w, when.hour()), 44 Field::Minute(a) => a.format(w, when.minute()), 45 Field::Second(a) => a.format(w, when.second()), 46 } 47 } 48 } 49 50 51 #[derive(PartialEq, Eq, Clone, Debug)] 52 pub struct DateFormat<'a> { 53 pub fields: Vec<Field<'a>>, 54 } 55 56 57 #[derive(PartialEq, Eq, Clone, Debug, Copy)] 58 pub enum FormatError { 59 InvalidChar { c: char, colon: bool, pos: Pos }, 60 OpenCurlyBrace { open_pos: Pos }, 61 CloseCurlyBrace { close_pos: Pos }, 62 MissingField { open_pos: Pos, close_pos: Pos }, 63 DoubleAlignment { open_pos: Pos, current_alignment: Alignment }, 64 DoubleWidth { open_pos: Pos, current_width: Width }, 65 } 66 67 pub type Width = usize; 68 pub type Pos = usize; 69 70 #[derive(PartialEq, Eq, Clone, Copy, Debug)] 71 pub struct Arguments { 72 pub alignment: Option<Alignment>, 73 pub width: Option<Width>, 74 pub pad_char: Option<char>, 75 } 76 77 impl Arguments { empty() -> Self78 pub fn empty() -> Self { 79 Self { 80 alignment: None, 81 width: None, 82 pad_char: None, 83 } 84 } 85 set_width(&mut self, width: Width) -> Self86 pub fn set_width(&mut self, width: Width) -> Self { 87 self.width = Some(width); 88 *self 89 } 90 set_alignment(&mut self, alignment: Alignment) -> Self91 pub fn set_alignment(&mut self, alignment: Alignment) -> Self { 92 self.alignment = Some(alignment); 93 *self 94 } 95 update_width(&mut self, width: Width, open_pos: Pos) -> Result<(), FormatError>96 pub fn update_width(&mut self, width: Width, open_pos: Pos) -> Result<(), FormatError> { 97 match self.width { 98 None => { self.width = Some(width); Ok(())}, 99 Some(existing) => Err(FormatError::DoubleWidth { open_pos, current_width: existing }), 100 } 101 } 102 update_alignment(&mut self, alignment: Alignment, open_pos: Pos) -> Result<(), FormatError>103 pub fn update_alignment(&mut self, alignment: Alignment, open_pos: Pos) -> Result<(), FormatError> { 104 match self.alignment { 105 None => { self.alignment = Some(alignment); Ok(())}, 106 Some(existing) => Err(FormatError::DoubleAlignment { open_pos, current_alignment: existing }), 107 } 108 } 109 format(self, w: &mut Vec<u8>, string: &str) -> io::Result<()>110 fn format(self, w: &mut Vec<u8>, string: &str) -> io::Result<()> { 111 let width = self.width.unwrap_or(0); 112 let pad_char = self.pad_char.unwrap_or(' '); 113 let alignment = self.alignment.unwrap_or(Alignment::Left); 114 let s = string.pad(width, pad_char, alignment, false); 115 116 w.write_all(s.as_bytes()) 117 } 118 is_empty(&self) -> bool119 pub fn is_empty(&self) -> bool { 120 self.alignment.is_none() && self.width.is_none() && self.pad_char.is_none() 121 } 122 } 123 124 125 #[derive(PartialEq, Eq, Clone, Copy, Debug)] 126 pub struct TextArguments(Arguments); 127 128 impl TextArguments { 129 #[cfg(test)] empty() -> TextArguments130 fn empty() -> TextArguments { 131 TextArguments(Arguments::empty()) 132 } 133 format(self, w: &mut Vec<u8>, string: &str) -> io::Result<()>134 fn format(self, w: &mut Vec<u8>, string: &str) -> io::Result<()> { 135 self.0.format(w, string) 136 } 137 } 138 139 140 #[derive(PartialEq, Eq, Clone, Copy, Debug)] 141 pub struct NumArguments(Arguments); 142 143 impl NumArguments { 144 #[cfg(test)] empty() -> NumArguments145 fn empty() -> NumArguments { 146 NumArguments(Arguments::empty()) 147 } 148 format<N: PrimInt + Display>(self, w: &mut Vec<u8>, number: N) -> io::Result<()>149 fn format<N: PrimInt + Display>(self, w: &mut Vec<u8>, number: N) -> io::Result<()> { 150 self.0.format(w, &number.to_string()) 151 } 152 } 153 154 impl<'a> DateFormat<'a> { format<T>(&self, when: &T, locale: &locale::Time) -> String where T: DatePiece+TimePiece155 pub fn format<T>(&self, when: &T, locale: &locale::Time) -> String where T: DatePiece+TimePiece{ 156 let mut buf = Vec::<u8>::new(); 157 158 for field in &self.fields { 159 // It's safe to just ignore the error when writing to an in-memory 160 // Vec<u8> buffer. If it fails then you have bigger problems 161 match field.format(when, &mut buf, locale) { _ => {} } 162 } 163 164 String::from_utf8(buf).unwrap() // Assume UTF-8 165 } 166 parse(input: &'a str) -> Result<DateFormat<'a>, FormatError>167 pub fn parse(input: &'a str) -> Result<DateFormat<'a>, FormatError> { 168 let mut parser = FormatParser::new(input); 169 parser.parse_format_string()?; 170 171 Ok(DateFormat { fields: parser.fields }) 172 } 173 } 174 175 176 struct FormatParser<'a> { 177 iter: CharIndices<'a>, 178 fields: Vec<Field<'a>>, 179 input: &'a str, 180 anchor: Option<Pos>, 181 peekee: Option<Option<(Pos, char)>>, 182 } 183 184 impl<'a> FormatParser<'a> { new(input: &'a str) -> FormatParser<'a>185 fn new(input: &'a str) -> FormatParser<'a> { 186 FormatParser { 187 iter: input.char_indices(), 188 fields: Vec::new(), 189 input, 190 anchor: None, 191 peekee: None, 192 } 193 } 194 next(&mut self) -> Option<(Pos, char)>195 fn next(&mut self) -> Option<(Pos, char)> { 196 match self.peekee { 197 Some(p) => { 198 self.peekee = None; 199 p 200 }, 201 None => { self.iter.next() }, 202 } 203 } 204 peek(&mut self) -> Option<(Pos, char)>205 fn peek(&mut self) -> Option<(Pos, char)> { 206 match self.peekee { 207 Some(thing) => thing, 208 None => { 209 self.peekee = Some(self.iter.next()); 210 self.peek() 211 } 212 } 213 } 214 collect_up_to_anchor(&mut self, position: Option<Pos>)215 fn collect_up_to_anchor(&mut self, position: Option<Pos>) { 216 if let Some(pos) = self.anchor { 217 self.anchor = None; 218 let text = match position { 219 Some(new_pos) => &self.input[pos..new_pos], 220 None => &self.input[pos..], 221 }; 222 self.fields.push(Field::Literal(text)); 223 } 224 } 225 parse_format_string(&mut self) -> Result<(), FormatError>226 fn parse_format_string(&mut self) -> Result<(), FormatError> { 227 loop { 228 match self.next() { 229 Some((new_pos, '{')) => { 230 self.collect_up_to_anchor(Some(new_pos) ); 231 232 let field = self.parse_a_thing(new_pos)?; 233 self.fields.push(field); 234 }, 235 Some((new_pos, '}')) => { 236 if let Some((_, '}')) = self.next() { 237 self.collect_up_to_anchor(Some(new_pos)); 238 239 let field = Field::Literal(&self.input[new_pos ..=new_pos]); 240 self.fields.push(field); 241 } 242 else { 243 return Err(FormatError::CloseCurlyBrace { close_pos: new_pos }); 244 } 245 }, 246 Some((pos, _)) => { 247 if self.anchor.is_none() { 248 self.anchor = Some(pos); 249 } 250 } 251 None => break, 252 } 253 } 254 255 // Finally, collect any literal characters after the last date field 256 // that haven't been turned into a Literal field yet. 257 self.collect_up_to_anchor(None); 258 Ok(()) 259 } 260 261 // The Literal strings are just slices of the original formatting string, 262 // which shares a lifetime with the formatter object, requiring fewer 263 // allocations. The parser is clever and combines consecutive literal 264 // strings. 265 // 266 // However, because they're slices, we can't transform them 267 // to escape {{ and }} characters. So instead, up to three adjacent 268 // Literal fields can be used to serve '{' or '}' characters, including 269 // one that's the *first character* of the "{{" part. This means it can 270 // still use slices. 271 parse_number(&mut self, just_parsed_character: char) -> usize272 fn parse_number(&mut self, just_parsed_character: char) -> usize { 273 let mut buf = just_parsed_character.to_string(); 274 275 loop { 276 if let Some((_, n)) = self.peek() { 277 if n.is_digit(10) { 278 buf.push(n); 279 let _ = self.next(); // ignore result - it's going to be the same! 280 } 281 else { 282 break; 283 } 284 } 285 else { 286 break; 287 } 288 } 289 290 buf.parse().unwrap() 291 } 292 parse_a_thing(&mut self, open_pos: Pos) -> Result<Field<'a>, FormatError>293 fn parse_a_thing(&mut self, open_pos: Pos) -> Result<Field<'a>, FormatError> { 294 let mut args = Arguments::empty(); 295 let mut bit = None; 296 let close_pos; 297 let mut first = true; 298 let mut long = false; 299 300 loop { 301 match self.next() { 302 Some((pos, '{')) if first => return Ok(Field::Literal(&self.input[pos ..=pos])), 303 Some((_, '<')) => { args.update_alignment(Alignment::Left, open_pos)?; continue }, 304 Some((_, '^')) => { args.update_alignment(Alignment::Middle, open_pos)?; continue }, 305 Some((_, '>')) => { args.update_alignment(Alignment::Right, open_pos)?; continue }, 306 Some((_, '0')) => { args.pad_char = Some('0'); continue }, 307 Some((_, n)) if n.is_digit(10) => { args.update_width(self.parse_number(n), open_pos)?; continue }, 308 Some((_, '_')) => { long = true; }, 309 Some((_, ':')) => { 310 let bitlet = match self.next() { 311 Some((_, 'Y')) => Field::Year(NumArguments(args)), 312 Some((_, 'y')) => Field::YearOfCentury(NumArguments(args)), 313 Some((_, 'M')) => Field::MonthName(long, TextArguments(args)), 314 Some((_, 'D')) => Field::Day(NumArguments(args)), 315 Some((_, 'E')) => Field::WeekdayName(long, TextArguments(args)), 316 Some((_, 'h')) => Field::Hour(NumArguments(args)), 317 Some((_, 'm')) => Field::Minute(NumArguments(args)), 318 Some((_, 's')) => Field::Second(NumArguments(args)), 319 Some((pos, c)) => return Err(FormatError::InvalidChar { c, colon: true, pos }), 320 None => return Err(FormatError::OpenCurlyBrace { open_pos }), 321 }; 322 323 bit = Some(bitlet); 324 }, 325 Some((pos, '}')) => { close_pos = pos; break; }, 326 Some((pos, c)) => return Err(FormatError::InvalidChar { c, colon: false, pos }), 327 None => return Err(FormatError::OpenCurlyBrace { open_pos }), 328 }; 329 330 first = false; 331 } 332 333 match bit { 334 Some(b) => Ok(b), 335 None => Err(FormatError::MissingField { open_pos, close_pos }), 336 } 337 } 338 } 339 340 341 #[cfg(test)] 342 mod test { 343 pub(crate) use super::{DateFormat, FormatError, Arguments, NumArguments, TextArguments}; 344 pub(crate) use super::Field::*; 345 pub(crate) use pad::Alignment; 346 347 mod parse { 348 use super::*; 349 350 macro_rules! test { 351 ($name: ident: $input: expr => $result: expr) => { 352 #[test] 353 fn $name() { 354 assert_eq!(DateFormat::parse($input), $result) 355 } 356 }; 357 } 358 359 test!(empty_string: "" => Ok(DateFormat { fields: vec![] })); 360 test!(entirely_literal: "Date!" => Ok(DateFormat { fields: vec![ Literal("Date!") ] })); 361 test!(single_element: "{:Y}" => Ok(DateFormat { fields: vec![ Year(NumArguments::empty()) ] })); 362 test!(two_long_years: "{:Y}{:Y}" => Ok(DateFormat { fields: vec![ Year(NumArguments::empty()), Year(NumArguments::empty()) ] })); 363 test!(surrounded: "({:D})" => Ok(DateFormat { fields: vec![ Literal("("), Day(NumArguments::empty()), Literal(")") ] })); 364 test!(a_bunch_of_elements: "{:Y}-{:M}-{:D}" => Ok(DateFormat { fields: vec![ Year(NumArguments::empty()), Literal("-"), MonthName(false, TextArguments::empty()), Literal("-"), Day(NumArguments::empty()) ] })); 365 366 test!(missing_field: "{}" => Err(FormatError::MissingField { open_pos: 0, close_pos: 1 })); 367 test!(invalid_char: "{a}" => Err(FormatError::InvalidChar { c: 'a', colon: false, pos: 1 })); 368 test!(invalid_char_after_colon: "{:7}" => Err(FormatError::InvalidChar { c: '7', colon: true, pos: 2 })); 369 test!(open_curly_brace: "{" => Err(FormatError::OpenCurlyBrace { open_pos: 0 })); 370 test!(mystery_close_brace: "}" => Err(FormatError::CloseCurlyBrace { close_pos: 0 })); 371 test!(another_mystery_close_brace: "This is a test: }" => Err(FormatError::CloseCurlyBrace { close_pos: 16 })); 372 373 test!(escaping_open: "{{" => Ok(DateFormat { fields: vec![ Literal("{") ] })); 374 test!(escaping_close: "}}" => Ok(DateFormat { fields: vec![ Literal("}") ] })); 375 376 test!(escaping_middle: "The character {{ is my favourite!" => Ok(DateFormat { fields: vec![ Literal("The character "), Literal("{"), Literal(" is my favourite!") ] })); 377 test!(escaping_middle_2: "It's way better than }}." => Ok(DateFormat { fields: vec![ Literal("It's way better than "), Literal("}"), Literal(".") ] })); 378 379 mod alignment { 380 use super::*; 381 382 test!(left: "{<:Y}" => Ok(DateFormat { fields: vec![ Year(NumArguments(Arguments::empty().set_alignment(Alignment::Left))) ]})); 383 test!(right: "{>:Y}" => Ok(DateFormat { fields: vec![ Year(NumArguments(Arguments::empty().set_alignment(Alignment::Right))) ]})); 384 test!(middle: "{^:Y}" => Ok(DateFormat { fields: vec![ Year(NumArguments(Arguments::empty().set_alignment(Alignment::Middle))) ]})); 385 } 386 387 mod alignment_fails { 388 use super::*; 389 390 test!(double_left: "{<<:Y}" => Err(FormatError::DoubleAlignment { open_pos: 0, current_alignment: Alignment::Left })); 391 test!(double_right: "{>>:Y}" => Err(FormatError::DoubleAlignment { open_pos: 0, current_alignment: Alignment::Right })); 392 test!(left_right: "{<>:Y}" => Err(FormatError::DoubleAlignment { open_pos: 0, current_alignment: Alignment::Left })); 393 test!(right_middle: "{>^:Y}" => Err(FormatError::DoubleAlignment { open_pos: 0, current_alignment: Alignment::Right })); 394 } 395 396 mod width { 397 use super::*; 398 399 test!(width_2: "{>2:D}" => Ok(DateFormat { fields: vec![ Day(NumArguments(Arguments::empty().set_width(2).set_alignment(Alignment::Right))) ] })); 400 test!(width_3: "{>3:D}" => Ok(DateFormat { fields: vec![ Day(NumArguments(Arguments::empty().set_width(3).set_alignment(Alignment::Right))) ] })); 401 test!(width_10: "{>10:D}" => Ok(DateFormat { fields: vec![ Day(NumArguments(Arguments::empty().set_width(10).set_alignment(Alignment::Right))) ] })); 402 test!(width_10_other: "{10>:D}" => Ok(DateFormat { fields: vec![ Day(NumArguments(Arguments::empty().set_width(10).set_alignment(Alignment::Right))) ] })); 403 test!(width_123456789: "{>123456789:D}" => Ok(DateFormat { fields: vec![ Day(NumArguments(Arguments::empty().set_width(123456789).set_alignment(Alignment::Right))) ] })); 404 } 405 } 406 } 407