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