duration/
duration.rs

1use pom::parser::*;
2use pom::Parser;
3
4use std::str::{self, FromStr};
5
6#[derive(Debug, PartialEq)]
7struct Duration {
8	years: Option<f32>,
9	months: Option<f32>,
10	weeks: Option<f32>,
11	days: Option<f32>,
12	hours: Option<f32>,
13	minutes: Option<f32>,
14	seconds: Option<f32>,
15}
16
17fn number_separator() -> Parser<u8, ()> {
18	// either '.' or ',' can be used as a separator between the whole and decimal part of a number
19	one_of(b".,").discard()
20}
21
22fn number() -> Parser<u8, f32> {
23	let integer = one_of(b"0123456789").repeat(0..);
24	let frac = number_separator() + one_of(b"0123456789").repeat(1..);
25	let number = integer + frac.opt();
26	number
27		.collect()
28		.convert(str::from_utf8)
29		.convert(f32::from_str)
30}
31
32fn date_part() -> Parser<u8, (Option<f32>, Option<f32>, Option<f32>, Option<f32>)> {
33	((number() - sym(b'Y')).opt()
34		+ (number() - sym(b'M')).opt()
35		+ (number() - sym(b'W')).opt()
36		+ (number() - sym(b'D')).opt())
37	.map(|(((years, months), weeks), days)| (years, months, weeks, days))
38}
39
40fn time_part() -> Parser<u8, (Option<f32>, Option<f32>, Option<f32>)> {
41	sym(b'T')
42		* ((number() - sym(b'H')).opt()
43			+ (number() - sym(b'M')).opt()
44			+ (number() - sym(b'S')).opt())
45		.map(|((hours, minutes), seconds)| (hours, minutes, seconds))
46}
47
48fn parser() -> Parser<u8, Duration> {
49	sym(b'P')
50		* (time_part().map(|(hours, minutes, seconds)| Duration {
51			years: None,
52			months: None,
53			weeks: None,
54			days: None,
55			hours,
56			minutes,
57			seconds,
58		}) | (date_part() + time_part()).map(|(date_elements, time_elements)| {
59			let (years, months, weeks, days) = date_elements;
60			let (hours, minutes, seconds) = time_elements;
61			Duration {
62				years,
63				months,
64				weeks,
65				days,
66				hours,
67				minutes,
68				seconds,
69			}
70		}))
71}
72
73/// Parses the ISO 8601 Duration standard
74/// https://en.wikipedia.org/wiki/ISO_8601#Durations
75fn main() {
76	let input = "P3Y6M4DT12H30M5S";
77	let result = parser().parse(input.as_bytes());
78
79	assert_eq!(
80		Duration {
81			years: Some(3f32),
82			months: Some(6f32),
83			weeks: None,
84			days: Some(4f32),
85			hours: Some(12f32),
86			minutes: Some(30f32),
87			seconds: Some(5f32)
88		},
89		result.unwrap()
90	);
91}