1 //! Parser example for ISO8601 dates. This does not handle the entire specification but it should
2 //! show the gist of it and be easy to extend to parse additional forms.
3 
4 use std::{
5     env, fmt,
6     fs::File,
7     io::{self, Read},
8 };
9 
10 use combine::{
11     choice,
12     many, optional,
13     parser::char::{char, digit},
14     stream::position,
15     Parser, Stream,
16 };
17 
18 #[cfg(feature = "std")]
19 use combine::{
20     stream::{easy, position::SourcePosition},
21     EasyParser,
22 };
23 
24 enum Error<E> {
25     Io(io::Error),
26     Parse(E),
27 }
28 
29 impl<E> fmt::Display for Error<E>
30 where
31     E: fmt::Display,
32 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result33     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34         match *self {
35             Error::Io(ref err) => write!(f, "{}", err),
36             Error::Parse(ref err) => write!(f, "{}", err),
37         }
38     }
39 }
40 
41 #[derive(PartialEq, Debug)]
42 pub struct Date {
43     pub year: i32,
44     pub month: i32,
45     pub day: i32,
46 }
47 
48 #[derive(PartialEq, Debug)]
49 pub struct Time {
50     pub hour: i32,
51     pub minute: i32,
52     pub second: i32,
53     pub time_zone: i32,
54 }
55 
56 #[derive(PartialEq, Debug)]
57 pub struct DateTime {
58     pub date: Date,
59     pub time: Time,
60 }
61 
two_digits<Input>() -> impl Parser<Input, Output = i32> where Input: Stream<Token = char>,62 fn two_digits<Input>() -> impl Parser<Input, Output = i32>
63 where
64     Input: Stream<Token = char>,
65 {
66     (digit(), digit()).map(|(x, y): (char, char)| {
67         let x = x.to_digit(10).expect("digit");
68         let y = y.to_digit(10).expect("digit");
69         (x * 10 + y) as i32
70     })
71 }
72 
73 /// Parses a time zone
74 /// +0012
75 /// -06:30
76 /// -01
77 /// Z
time_zone<Input>() -> impl Parser<Input, Output = i32> where Input: Stream<Token = char>,78 fn time_zone<Input>() -> impl Parser<Input, Output = i32>
79 where
80     Input: Stream<Token = char>,
81 {
82     let utc = char('Z').map(|_| 0);
83     let offset = (
84         choice([char('-'), char('+')]),
85         two_digits(),
86         optional(optional(char(':')).with(two_digits())),
87     )
88         .map(|(sign, hour, minute)| {
89             let offset = hour * 60 + minute.unwrap_or(0);
90             if sign == '-' {
91                 -offset
92             } else {
93                 offset
94             }
95         });
96 
97     utc.or(offset)
98 }
99 
100 /// Parses a date
101 /// 2010-01-30
date<Input>() -> impl Parser<Input, Output = Date> where Input: Stream<Token = char>,102 fn date<Input>() -> impl Parser<Input, Output = Date>
103 where
104     Input: Stream<Token = char>,
105 {
106     (
107         many::<String, _, _>(digit()),
108         char('-'),
109         two_digits(),
110         char('-'),
111         two_digits(),
112     )
113         .map(|(year, _, month, _, day)| {
114             // Its ok to just unwrap since we only parsed digits
115             Date {
116                 year: year.parse().unwrap(),
117                 month,
118                 day,
119             }
120         })
121 }
122 
123 /// Parses a time
124 /// 12:30:02
time<Input>() -> impl Parser<Input, Output = Time> where Input: Stream<Token = char>,125 fn time<Input>() -> impl Parser<Input, Output = Time>
126 where
127     Input: Stream<Token = char>,
128 {
129     (
130         two_digits(),
131         char(':'),
132         two_digits(),
133         char(':'),
134         two_digits(),
135         time_zone(),
136     )
137         .map(|(hour, _, minute, _, second, time_zone)| {
138             // Its ok to just unwrap since we only parsed digits
139             Time {
140                 hour,
141                 minute,
142                 second,
143                 time_zone,
144             }
145         })
146 }
147 
148 /// Parses a date time according to ISO8601
149 /// 2015-08-02T18:54:42+02
date_time<Input>() -> impl Parser<Input, Output = DateTime> where Input: Stream<Token = char>,150 fn date_time<Input>() -> impl Parser<Input, Output = DateTime>
151 where
152     Input: Stream<Token = char>,
153 {
154     (date(), char('T'), time()).map(|(date, _, time)| DateTime { date, time })
155 }
156 
157 #[test]
test()158 fn test() {
159     // A parser for
160     let result = date_time().parse("2015-08-02T18:54:42+02");
161     let d = DateTime {
162         date: Date {
163             year: 2015,
164             month: 8,
165             day: 2,
166         },
167         time: Time {
168             hour: 18,
169             minute: 54,
170             second: 42,
171             time_zone: 2 * 60,
172         },
173     };
174     assert_eq!(result, Ok((d, "")));
175 
176     let result = date_time().parse("50015-12-30T08:54:42Z");
177     let d = DateTime {
178         date: Date {
179             year: 50015,
180             month: 12,
181             day: 30,
182         },
183         time: Time {
184             hour: 8,
185             minute: 54,
186             second: 42,
187             time_zone: 0,
188         },
189     };
190     assert_eq!(result, Ok((d, "")));
191 }
192 
main()193 fn main() {
194     let result = match env::args().nth(1) {
195         Some(file) => File::open(file).map_err(Error::Io).and_then(main_),
196         None => main_(io::stdin()),
197     };
198     match result {
199         Ok(_) => println!("OK"),
200         Err(err) => println!("{}", err),
201     }
202 }
203 
204 #[cfg(feature = "std")]
main_<R>(mut read: R) -> Result<(), Error<easy::Errors<char, String, SourcePosition>>> where R: Read,205 fn main_<R>(mut read: R) -> Result<(), Error<easy::Errors<char, String, SourcePosition>>>
206 where
207     R: Read,
208 {
209     let mut text = String::new();
210     read.read_to_string(&mut text).map_err(Error::Io)?;
211     date_time()
212         .easy_parse(position::Stream::new(&*text))
213         .map_err(|err| Error::Parse(err.map_range(|s| s.to_string())))?;
214     Ok(())
215 }
216 
217 #[cfg(not(feature = "std"))]
main_<R>(mut read: R) -> Result<(), Error<::combine::error::StringStreamError>> where R: Read,218 fn main_<R>(mut read: R) -> Result<(), Error<::combine::error::StringStreamError>>
219 where
220     R: Read,
221 {
222     let mut text = String::new();
223     read.read_to_string(&mut text).map_err(Error::Io)?;
224     date_time()
225         .parse(position::Stream::new(&*text))
226         .map_err(Error::Parse)?;
227     Ok(())
228 }
229