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