scan_rules/scanner/std/
time.rs

1/*
2Copyright ⓒ 2016 Daniel Keep.
3
4Licensed under the MIT license (see LICENSE or <http://opensource.org
5/licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
6<http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
7files in the project carrying such notice may not be copied, modified,
8or distributed except according to those terms.
9*/
10/*!
11Scanner implementations for `std::time` types.
12*/
13use std::time::Duration;
14use strcursor::StrCursor;
15use ::ScanError;
16use ::input::ScanInput;
17use ::scanner::ScanFromStr;
18use ::util::MsgErr;
19
20/**
21Parses an ISO 8601 format duration into a `std::time::Duration`.
22
23Specifically, it supports the following syntax:
24
25```text
26PT[nH][nM][nS]
27```
28
29Each `n` is either an integer or a fractional value using `.` or `,` as the decimal point.  It supports durations down to nanosecond precision using fractional seconds.  Each component may have an arbitrarily large value (*e.g.* `PT2H76M`).
30
31## `duration-iso8601-dates` feature
32
33By default, durations involving date components (years, months, weeks, and days) are not enabled.  This is because the `Duration` type stores nanoseconds.  It is impossible to convert date components into seconds.  As a simple example, consider that "1 month", depending on context, could reasonably be converted into the equivalent of 28 days, 29 days, 30 days, 31 days, 30 days and 8 hours, 30 days 8 hours and 30 minutes, or some other value offset by plus or minus one leap second in order to account for changes in the Earth's rotational and/or orbital speeds.
34
35The only *correct* way to store such durations is in their fully decomposed, component form.  Rust does not support this, thus they are disabled.
36
37However, parsing such durations may occasionally be useful in limited contexts.  Provided you understand the above drawbacks, support for these durations can be enabled with the `duration-iso8601-dates` feature.  It uses the following conversions:
38
39* 1 year = 365.25 days (one fourth of 3×365 + 366 days)
40* 1 month = 30 days, 10 hours, 30 minutes (one twelfth of 365.25 days)
41* 1 week = 7 days
42* 1 day = 24 hours
43
44With this feature, the following additional syntaxes are enabled:
45
46* `P[nY][nM][nD][T[nH][nM][nS]]` - a fuller form of the above syntax.
47* `Pyyyy-mm-ddThh:mm:ss`, `PyyyymmddThhmmss` - shorthand for a "full" date duration; note that the individual components *may not* exceed their "conventional" maximum value; *e.g.* you cannot have 25 hours.
48* `PnW` - number of weeks.
49*/
50pub enum Iso8601Duration {}
51
52impl<'a> ScanFromStr<'a> for Iso8601Duration {
53    type Output = Duration;
54    fn scan_from<I: ScanInput<'a>>(s: I) -> Result<(Self::Output, usize), ScanError> {
55        let cur = StrCursor::new_at_start(s.as_str());
56        let (dur, cur) = try!(scan_8601(cur));
57        Ok((dur, cur.byte_pos()))
58    }
59}
60
61const SECS_IN_SEC: u64 = 1;
62const SECS_IN_MIN: u64 = 60;
63const SECS_IN_HOUR: u64 = 60 * SECS_IN_MIN;
64
65#[cfg(feature="duration-iso8601-dates")] const SECS_IN_DAY: u64 = 24 * SECS_IN_HOUR;
66#[cfg(feature="duration-iso8601-dates")] const SECS_IN_WEEK: u64 = 7 * SECS_IN_DAY;
67#[cfg(feature="duration-iso8601-dates")] const SECS_IN_MONTH: u64 = 30 * SECS_IN_DAY + 10 * SECS_IN_HOUR + 30 * SECS_IN_MIN;
68#[cfg(feature="duration-iso8601-dates")] const SECS_IN_YEAR: u64 = 365 * SECS_IN_DAY + 6 * SECS_IN_HOUR;
69
70const NANOS_IN_SEC: u32 = 1_000_000_000;
71
72#[cfg(not(feature="duration-iso8601-dates"))]
73#[cfg(test)]
74#[test]
75fn test_iso_8601_duration_dates() {
76    use ::ScanError as SE;
77    use ::ScanErrorKind as SEK;
78
79    let scan = Iso8601Duration::scan_from;
80
81    assert_match!(
82        scan("P1W"),
83        Err(SE { kind: SEK::Syntax(_), ref at, .. }) if at.offset() == 0
84    );
85    assert_match!(
86        scan("P1Y"),
87        Err(SE { kind: SEK::Syntax(_), ref at, .. }) if at.offset() == 0
88    );
89    assert_match!(
90        scan("P1M"),
91        Err(SE { kind: SEK::Syntax(_), ref at, .. }) if at.offset() == 0
92    );
93    assert_match!(
94        scan("P1D"),
95        Err(SE { kind: SEK::Syntax(_), ref at, .. }) if at.offset() == 0
96    );
97}
98
99#[cfg(feature="duration-iso8601-dates")]
100#[cfg(test)]
101#[test]
102fn test_iso_8601_duration_dates() {
103    let scan = Iso8601Duration::scan_from;
104
105    assert_match!(
106        scan("P1W"),
107        Ok((d, 3)) if d == Duration::new(SECS_IN_WEEK, 0)
108    );
109    assert_match!(
110        scan("P1Y"),
111        Ok((d, 3)) if d == Duration::new(SECS_IN_YEAR, 0)
112    );
113    assert_match!(
114        scan("P1M"),
115        Ok((d, 3)) if d == Duration::new(SECS_IN_MONTH, 0)
116    );
117    assert_match!(
118        scan("P1D"),
119        Ok((d, 3)) if d == Duration::new(SECS_IN_DAY, 0)
120    );
121
122    assert_match!(
123        scan("P1Y2M3DT4H5M6.7S"),
124        Ok((d, 16)) if d == Duration::new(
125            1*SECS_IN_YEAR
126            + 2*SECS_IN_MONTH
127            + 3*SECS_IN_DAY
128            + 4*SECS_IN_HOUR
129            + 5*SECS_IN_MIN
130            + 6*SECS_IN_SEC,
131            700_000_000
132        )
133    );
134
135    assert_match!(
136        scan("P6789-01-23T12:34:56"),
137        Ok((d, 20)) if d == Duration::new(
138            6789*SECS_IN_YEAR
139            + 01*SECS_IN_MONTH
140            + 23*SECS_IN_DAY
141            + 12*SECS_IN_HOUR
142            + 34*SECS_IN_MIN
143            + 56*SECS_IN_SEC,
144            0
145        )
146    );
147
148    assert_match!(
149        scan("P67890123T123456"),
150        Ok((d, 16)) if d == Duration::new(
151            6789*SECS_IN_YEAR
152            + 01*SECS_IN_MONTH
153            + 23*SECS_IN_DAY
154            + 12*SECS_IN_HOUR
155            + 34*SECS_IN_MIN
156            + 56*SECS_IN_SEC,
157            0
158        )
159    );
160}
161
162#[cfg(test)]
163#[test]
164fn test_iso_8601_duration() {
165    use ::ScanError as SE;
166    use ::ScanErrorKind as SEK;
167
168    let scan = Iso8601Duration::scan_from;
169
170    assert_match!(scan("PT1H"), Ok((d, 4)) if d == Duration::new(SECS_IN_HOUR, 0));
171    assert_match!(scan("PT1M"), Ok((d, 4)) if d == Duration::new(SECS_IN_MIN, 0));
172    assert_match!(scan("PT1S"), Ok((d, 4)) if d == Duration::new(SECS_IN_SEC, 0));
173    assert_match!(
174        scan("PT12H34M56.78S"),
175        Ok((d, 14)) if d == Duration::new(12*SECS_IN_HOUR + 34*SECS_IN_MIN + 56, 780_000_000)
176    );
177    assert_match!(
178        scan("PT34M56.78S"),
179        Ok((d, 11)) if d == Duration::new(34*SECS_IN_MIN + 56, 780_000_000)
180    );
181    assert_match!(
182        scan("PT12H56.78S"),
183        Ok((d, 11)) if d == Duration::new(12*SECS_IN_HOUR + 56, 780_000_000)
184    );
185    assert_match!(
186        scan("PT12H34M56S"),
187        Ok((d, 11)) if d == Duration::new(12*SECS_IN_HOUR + 34*SECS_IN_MIN + 56, 0)
188    );
189    assert_match!(
190        scan("PT12H34M"),
191        Ok((d, 8)) if d == Duration::new(12*SECS_IN_HOUR + 34*SECS_IN_MIN, 0)
192    );
193
194    assert_match!(
195        scan("PT0.5H"),
196        Ok((d, 6)) if d == Duration::new(30*SECS_IN_MIN, 0)
197    );
198    assert_match!(
199        scan("PT0.5M"),
200        Ok((d, 6)) if d == Duration::new(30*SECS_IN_SEC, 0)
201    );
202    assert_match!(
203        scan("PT0.5S"),
204        Ok((d, 6)) if d == Duration::new(0, NANOS_IN_SEC/2)
205    );
206
207    assert_match!(
208        scan("PT0.000000001S"),
209        Ok((d, 14)) if d == Duration::new(0, 1)
210    );
211    assert_match!(
212        scan("PT0.000000000016666666666666667M"),
213        Ok((d, 32)) if d == Duration::new(0, 1)
214    );
215    assert_match!(
216        scan("PT0.0000000000002777777777777778H"),
217        Ok((d, 33)) if d == Duration::new(0, 1)
218    );
219
220    assert_match!(
221        scan(""),
222        Err(SE { kind: SEK::Syntax(_), .. })
223    );
224    assert_match!(
225        scan("a while"),
226        Err(SE { kind: SEK::Syntax(_), .. })
227    );
228    assert_match!(
229        scan("P"),
230        Err(SE { kind: SEK::Syntax(_), .. })
231    );
232    assert_match!(
233        scan("Px"),
234        Err(SE { kind: SEK::Syntax(_), .. })
235    );
236    assert_match!(
237        scan("PY"),
238        Err(SE { kind: SEK::Syntax(_), .. })
239    );
240    assert_match!(
241        scan("PM"),
242        Err(SE { kind: SEK::Syntax(_), .. })
243    );
244    assert_match!(
245        scan("PD"),
246        Err(SE { kind: SEK::Syntax(_), .. })
247    );
248    assert_match!(
249        scan("PW"),
250        Err(SE { kind: SEK::Syntax(_), .. })
251    );
252    assert_match!(
253        scan("P1H"),
254        Err(SE { kind: SEK::Syntax(_), .. })
255    );
256    assert_match!(
257        scan("P1S"),
258        Err(SE { kind: SEK::Syntax(_), .. })
259    );
260}
261
262type ScanResult<T, L=usize> = Result<(T, L), ScanError>;
263
264fn scan_8601(cur: StrCursor) -> ScanResult<Duration, StrCursor> {
265    /*
266    See: <https://en.wikipedia.org/wiki/ISO_8601#Durations>,
267    <https://html.spec.whatwg.org/multipage/infrastructure.html
268     #valid-duration-string>.
269    */
270    let cur = match cur.next_cp() {
271        Some(('P', cur)) => cur,
272        _ => return Err(ScanError::syntax("expected `P`")
273            .add_offset(cur.byte_pos()))
274    };
275
276    return match cur.next_cp() {
277        Some(('T', cur)) => given_date(Duration::new(0, 0), cur),
278        _ => date(cur)
279    };
280
281    #[cfg(not(feature="duration-iso8601-dates"))]
282    fn date(_: StrCursor) -> ScanResult<Duration, StrCursor> {
283        Err(ScanError::syntax("durations with date components not supported"))
284    }
285
286    #[cfg(feature="duration-iso8601-dates")]
287    fn date(cur: StrCursor) -> ScanResult<Duration, StrCursor> {
288        let (int, int_cur) = try!(scan_integer(cur));
289        let int_len = cur.slice_between(int_cur).unwrap().len();
290
291        match int_cur.next_cp() {
292            Some(('.', cur)) | Some((',', cur)) => date_leading_frac(int, cur),
293            Some(('T', cur)) => {
294                if int_len != "YYYYMMDD".len() {
295                    return Err(ScanError::syntax("expected date in `YYYYMMDD` format")
296                        .add_offset(cur.byte_pos()));
297                }
298                time_compound(int, cur)
299            },
300            Some(('-', cur)) => {
301                if int_len != "YYYY".len() {
302                    return Err(ScanError::syntax("expected year in `YYYY-MM-DD` format")
303                        .add_offset(cur.byte_pos()));
304                }
305                date_split_month(try!(dur_years(int, 0.0)), cur)
306            },
307            Some(('Y', cur)) => {
308                let y = try!(dur_years(int, 0.0));
309                given_year(y, cur)
310            },
311            Some(('M', cur)) => {
312                let m = try!(dur_months(int, 0.0));
313                given_month(m, cur)
314            },
315            Some(('D', cur)) => {
316                let d = try!(dur_days(int, 0.0));
317                given_day(d, cur)
318            },
319            Some(('W', cur)) => {
320                let w = try!(dur_weeks(int, 0.0));
321                Ok((w, cur))
322            },
323            _ => Err(ScanError::syntax("expected number followed by one of `T`, `Y`, `M`, `D`, or `W`")
324                .add_offset(cur.byte_pos()))
325        }
326    }
327
328    #[cfg(feature="duration-iso8601-dates")]
329    fn date_leading_frac(int: u64, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
330        let ((int, frac), cur) = try!(scan_real_frac(int, cur));
331        match cur.next_cp() {
332            Some(('Y', cur)) => given_year(try!(dur_years(int, frac)), cur),
333            Some(('M', cur)) => given_month(try!(dur_months(int, frac)), cur),
334            Some(('D', cur)) => given_day(try!(dur_days(int, frac)), cur),
335            Some(('W', cur)) => {
336                let w = try!(dur_weeks(int, frac));
337                Ok((w, cur))
338            },
339            _ => Err(ScanError::syntax("expected real number followed by one of `Y`, `M`, `D`, or `W`")
340                .add_offset(cur.byte_pos()))
341        }
342    }
343
344    #[cfg(feature="duration-iso8601-dates")]
345    fn time_compound(date: u64, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
346        let (time, time_cur) = try!(scan_integer(cur));
347        let time_len = cur.slice_between(time_cur).unwrap().len();
348
349        if time_len != "HHMMSS".len() {
350            return Err(ScanError::syntax("expected time in `hhmmss` format")
351                .add_offset(cur.byte_pos()));
352        }
353
354        let years = date / 1_00_00;
355        let months = (date / 1_00) % 1_00;
356        let days = date % 1_00;
357
358        if months > 12 {
359            return Err(ScanError::syntax("months cannot exceed 12 in this format"));
360        }
361
362        if days > 31 {
363            return Err(ScanError::syntax("days cannot exceed 31 in this format"));
364        }
365
366        let hours = time / 1_00_00;
367        let mins = (time / 1_00) % 1_00;
368        let secs = time % 1_00;
369
370        if hours > 24 {
371            return Err(ScanError::syntax("hours cannot exceed 24 in this format"));
372        }
373
374        if mins > 60 {
375            return Err(ScanError::syntax("minutes cannot exceed 60 in this format"));
376        }
377
378        if secs > 61 {
379            return Err(ScanError::syntax("days cannot exceed 61 in this format"));
380        }
381
382        let years_dur = try!(dur_years(years, 0.0));
383        let months_dur = try!(dur_months(months, 0.0));
384        let days_dur = try!(dur_days(days, 0.0));
385        let hours_dur = try!(dur_hours(hours, 0.0));
386        let mins_dur = try!(dur_mins(mins, 0.0));
387        let secs_dur = try!(dur_secs(secs, 0.0));
388
389        checked_add_dur(years_dur, months_dur)
390            .and_then(|lhs| checked_add_dur(lhs, days_dur))
391            .and_then(|lhs| checked_add_dur(lhs, hours_dur))
392            .and_then(|lhs| checked_add_dur(lhs, mins_dur))
393            .and_then(|lhs| checked_add_dur(lhs, secs_dur))
394            .map(|dur| (dur, time_cur))
395            .ok_or_else(|| ScanError::other(MsgErr("overflow in duration")))
396    }
397
398    #[cfg(feature="duration-iso8601-dates")]
399    fn date_split_month(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
400        let (months, months_cur) = try!(scan_integer(cur));
401        let months_len = cur.slice_between(months_cur).unwrap().len();
402
403        if months_len != "MM".len() {
404            return Err(ScanError::syntax("expected month in `YYYY-MM-DD` format")
405                .add_offset(cur.byte_pos()));
406        }
407
408        match months_cur.next_cp() {
409            Some(('-', cur)) => date_split_day(dur + try!(dur_months(months, 0.0)), cur),
410            _ => Err(ScanError::syntax("expected `-` after month in `YYYY-MM-DD` format")
411                .add_offset(cur.byte_pos()))
412        }
413    }
414
415    #[cfg(feature="duration-iso8601-dates")]
416    fn date_split_day(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
417        let (days, days_cur) = try!(scan_integer(cur));
418        let days_len = cur.slice_between(days_cur).unwrap().len();
419
420        if days_len != "DD".len() {
421            return Err(ScanError::syntax("expected day in `YYYY-MM-DD` format")
422                .add_offset(cur.byte_pos()));
423        }
424
425        match days_cur.next_cp() {
426            Some(('T', cur)) => date_split_hour(dur + try!(dur_days(days, 0.0)), cur),
427            _ => Err(ScanError::syntax("expected `T` following date")
428                .add_offset(cur.byte_pos()))
429        }
430    }
431
432    #[cfg(feature="duration-iso8601-dates")]
433    fn date_split_hour(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
434        let (hours, hours_cur) = try!(scan_integer(cur));
435        let hours_len = cur.slice_between(hours_cur).unwrap().len();
436
437        if hours_len != "hh".len() {
438            return Err(ScanError::syntax("expected time in `hh:mm:ss` format")
439                .add_offset(cur.byte_pos()));
440        }
441
442        match hours_cur.next_cp() {
443            Some((':', cur)) => date_split_min(dur + try!(dur_hours(hours, 0.0)), cur),
444            _ => Err(ScanError::syntax("expected time in `hh:mm:ss` format")
445                .add_offset(cur.byte_pos()))
446        }
447    }
448
449    #[cfg(feature="duration-iso8601-dates")]
450    fn date_split_min(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
451        let (mins, mins_cur) = try!(scan_integer(cur));
452        let mins_len = cur.slice_between(mins_cur).unwrap().len();
453
454        if mins_len != "mm".len() {
455            return Err(ScanError::syntax("expected time in `hh:mm:ss` format")
456                .add_offset(cur.byte_pos()));
457        }
458
459        match mins_cur.next_cp() {
460            Some((':', cur)) => date_split_sec(dur + try!(dur_mins(mins, 0.0)), cur),
461            _ => Err(ScanError::syntax("expected time in `hh:mm:ss` format")
462                .add_offset(cur.byte_pos()))
463        }
464    }
465
466    #[cfg(feature="duration-iso8601-dates")]
467    fn date_split_sec(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
468        let (secs, secs_cur) = try!(scan_integer(cur));
469        let secs_len = cur.slice_between(secs_cur).unwrap().len();
470
471        if secs_len != "ss".len() {
472            return Err(ScanError::syntax("expected time in `hh:mm:ss` format")
473                .add_offset(cur.byte_pos()));
474        }
475
476        Ok((dur + try!(dur_secs(secs, 0.0)), secs_cur))
477    }
478
479    macro_rules! add_dur {
480        ($a:expr, $b:expr) => {
481            try!(checked_add_dur($a, try!($b))
482                .ok_or_else(|| ScanError::other(MsgErr("duration overflowed"))))
483        };
484    }
485
486    #[cfg(feature="duration-iso8601-dates")]
487    fn given_year(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
488        match cur.next_cp() {
489            Some(('0'...'9', _)) => (),
490            Some(('T', cur)) => return given_date(dur, cur),
491            _ => return Ok((dur, cur))
492        }
493
494        let ((int, frac), cur) = try!(scan_real(cur));
495        match cur.next_cp() {
496            Some(('M', cur)) => given_month(add_dur!(dur, dur_months(int, frac)), cur),
497            Some(('D', cur)) => given_day(add_dur!(dur, dur_days(int, frac)), cur),
498            _ => Err(ScanError::syntax("expected number followed by one of `M`, or `D`")
499                .add_offset(cur.byte_pos()))
500        }
501    }
502
503    #[cfg(feature="duration-iso8601-dates")]
504    fn given_month(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
505        match cur.next_cp() {
506            Some(('0'...'9', _)) => (),
507            Some(('T', cur)) => return given_date(dur, cur),
508            _ => return Ok((dur, cur))
509        }
510
511        let ((int, frac), cur) = try!(scan_real(cur));
512        match cur.next_cp() {
513            Some(('D', cur)) => given_day(add_dur!(dur, dur_days(int, frac)), cur),
514            _ => Err(ScanError::syntax("expected number followed by `D`")
515                .add_offset(cur.byte_pos()))
516        }
517    }
518
519    #[cfg(feature="duration-iso8601-dates")]
520    fn given_day(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
521        match cur.next_cp() {
522            Some(('T', cur)) => given_date(dur, cur),
523            _ => Ok((dur, cur))
524        }
525    }
526
527    fn given_date(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
528        let ((int, frac), cur) = try!(scan_real(cur));
529        match cur.next_cp() {
530            Some(('H', cur)) => given_hour(add_dur!(dur, dur_hours(int, frac)), cur),
531            Some(('M', cur)) => given_min(add_dur!(dur, dur_mins(int, frac)), cur),
532            Some(('S', cur)) => given_sec(add_dur!(dur, dur_secs(int, frac)), cur),
533            _ => Err(ScanError::syntax("expected number followed by one of `H`, `M`, or `S`")
534                .add_offset(cur.byte_pos()))
535        }
536    }
537
538    fn given_hour(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
539        match cur.next_cp() {
540            Some(('0'...'9', _)) => (),
541            _ => return Ok((dur, cur))
542        }
543
544        let ((int, frac), cur) = try!(scan_real(cur));
545        match cur.next_cp() {
546            Some(('M', cur)) => given_min(add_dur!(dur, dur_mins(int, frac)), cur),
547            Some(('S', cur)) => given_sec(add_dur!(dur, dur_secs(int, frac)), cur),
548            _ => Err(ScanError::syntax("expected number followed by one of `M`, or `S`")
549                .add_offset(cur.byte_pos()))
550        }
551    }
552
553    fn given_min(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
554        match cur.next_cp() {
555            Some(('0'...'9', _)) => (),
556            _ => return Ok((dur, cur))
557        }
558
559        let ((int, frac), cur) = try!(scan_real(cur));
560        match cur.next_cp() {
561            Some(('S', cur)) => given_sec(add_dur!(dur, dur_secs(int, frac)), cur),
562            _ => Err(ScanError::syntax("expected number followed by `S`")
563                .add_offset(cur.byte_pos()))
564        }
565    }
566
567    fn given_sec(dur: Duration, cur: StrCursor) -> ScanResult<Duration, StrCursor> {
568        Ok((dur, cur))
569    }
570}
571
572fn checked_add_dur(a: Duration, b: Duration) -> Option<Duration> {
573    let (a_s, a_ns) = (a.as_secs(), a.subsec_nanos());
574    let (b_s, b_ns) = (b.as_secs(), b.subsec_nanos());
575    let c_ns = a_ns + b_ns;
576    let (c_ns, c_carry) = match c_ns {
577        c_ns if c_ns > NANOS_IN_SEC => (c_ns - NANOS_IN_SEC, 1),
578        c_ns => (c_ns, 0)
579    };
580    a_s.checked_add(b_s)
581        .and_then(|c_s| c_s.checked_add(c_carry))
582        .map(|c_s| Duration::new(c_s, c_ns))
583}
584
585macro_rules! dur_conv {
586    (
587        $($(#[$attrs:meta])* fn $fn_name:ident($name:expr, $scale:expr);)*
588    ) => {
589        $(
590            $(#[$attrs])*
591            fn $fn_name(int: u64, frac: f64) -> Result<Duration, ScanError> {
592                const MSG: &'static str = concat!("overflow converting ",
593                    $name, " into seconds");
594                assert!(0.0f64 <= frac && frac < 1.0f64);
595                let secs = try!(int.checked_mul($scale)
596                    .ok_or_else(|| ScanError::other(MsgErr(MSG))));
597                
598                let nanos = frac * ($scale as f64);
599                let secs = try!(secs.checked_add(nanos as u64)
600                    .ok_or_else(|| ScanError::other(MsgErr(MSG))));
601                let nanos = (nanos.fract() * (NANOS_IN_SEC as f64)) as u32;
602
603                Ok(Duration::new(secs, nanos))
604            }
605        )*
606    };
607}
608
609dur_conv! {
610    #[cfg(feature="duration-iso8601-dates")] fn dur_years("years", SECS_IN_YEAR);
611    #[cfg(feature="duration-iso8601-dates")] fn dur_months("months", SECS_IN_MONTH);
612    #[cfg(feature="duration-iso8601-dates")] fn dur_weeks("weeks", SECS_IN_WEEK);
613    #[cfg(feature="duration-iso8601-dates")] fn dur_days("days", SECS_IN_DAY);
614
615    fn dur_hours("hours", SECS_IN_HOUR);
616    fn dur_mins("mins", SECS_IN_MIN);
617    fn dur_secs("secs", SECS_IN_SEC);
618}
619
620fn scan_integer(cur: StrCursor) -> ScanResult<u64, StrCursor> {
621    let start = cur;
622    let mut cur = match cur.next_cp() {
623        Some(('0'...'9', cur)) => cur,
624        _ => return Err(ScanError::syntax("expected digit")
625            .add_offset(cur.byte_pos()))
626    };
627
628    loop {
629        cur = match cur.next_cp() {
630            Some(('0'...'9', cur)) => cur,
631            _ => {
632                let v = try!(start.slice_between(cur).unwrap().parse()
633                    .map_err(|e| ScanError::int(e)
634                        .add_offset(cur.byte_pos())));
635                return Ok((v, cur));
636            }
637        }
638    }
639}
640
641/*
642**NOTE**: This is pretty horrible.  The issue is that because `,` is a valid decimal point, we can't just use `f64::from_str`.  One possibility would be to throw the string into a stack array, mutate it, *then* pass it on... but that means *yet another dependency*.  I'm not sure it's worth it for the moderate horribleness of the following code.
643
644So yes, I know this sucks, but it's *calculated suckage*.
645
646Also, it would be nice if this could accurately parse (say) nanoseconds as fractional years... but that would again require us to forward to `f64::from_str` for the fractional part.  That's why this function returns `(u64, f64)`; it's essentially that way on the hope that one day it'll actually be able to *use* that precision.  :P
647*/
648fn scan_real(cur: StrCursor) -> ScanResult<(u64, f64), StrCursor> {
649    let (int, cur) = try!(scan_integer(cur));
650    let cur = match cur.next_cp() {
651        Some(('.', cur)) | Some((',', cur)) => cur,
652        _ => return Ok(((int, 0.0), cur))
653    };
654    scan_real_frac(int, cur)
655}
656
657fn scan_real_frac(int: u64, cur: StrCursor) -> ScanResult<(u64, f64), StrCursor> {
658    let (frac, frac_cur) = try!(scan_integer(cur));
659    let frac_len = cur.slice_between(frac_cur).unwrap().len();
660    let frac = frac as f64;
661    let frac = frac / (10.0f64).powf(frac_len as f64);
662    Ok(((int, frac), frac_cur))
663}