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