1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#![doc = include_str ! ("../README.md")]
extern crate core;
extern crate strum;

use strum::{Display, EnumMessage};

mod date;
mod datetime;
mod duration;
mod numbers;
mod time;

pub use date::Date;
pub use datetime::DateTime;
pub use duration::Duration;
pub use time::{MicrosecondsPrecisionOverflowBehavior, Time, TimeConfig, TimeConfigBuilder};

pub use numbers::{float_parse_bytes, float_parse_str, int_parse_bytes, int_parse_str, IntFloat};

/// Parsing datetime, date, time & duration values

// get a character from the bytes as as a decimal
macro_rules! get_digit {
    ($bytes:ident, $index:expr, $error:ident) => {
        match $bytes.get($index) {
            Some(c) if c.is_ascii_digit() => c - b'0',
            _ => return Err(ParseError::$error),
        }
    };
}
pub(crate) use get_digit;

// as above without bounds check, requires length to checked first!
macro_rules! get_digit_unchecked {
    ($bytes:ident, $index:expr, $error:ident) => {
        match $bytes.get_unchecked($index) {
            c if c.is_ascii_digit() => c - b'0',
            _ => return Err(ParseError::$error),
        }
    };
}
pub(crate) use get_digit_unchecked;

/// Details about errors when parsing datetime, date, time & duration values
///
/// As well as comparing enum values, machine and human readable representations of
/// errors are provided.
///
/// # Examples
/// (Note: the `strum::EnumMessage` trait must be used to support `.get_documentation()`)
/// ```
/// use strum::EnumMessage;
/// use speedate::{Date, ParseError};
///
/// match Date::parse_str("invalid") {
///      Ok(_) => println!("Parsed successfully"),
///      Err(error) => {
///          assert_eq!(error, ParseError::TooShort);
///          assert_eq!(error.to_string(), "too_short");
///          assert_eq!(error.get_documentation(), Some("input is too short"));
///      }
/// };
/// ```
#[derive(Debug, Display, EnumMessage, PartialEq, Eq, Clone)]
#[strum(serialize_all = "snake_case")]
pub enum ParseError {
    /// input is too short
    TooShort,
    /// unexpected extra characters at the end of the input
    ExtraCharacters,
    /// invalid datetime separator, expected `T`, `t`, `_` or space
    InvalidCharDateTimeSep,
    /// invalid date separator, expected `-`
    InvalidCharDateSep,
    /// Timestamp is not an exact date
    DateNotExact,
    /// invalid character in year
    InvalidCharYear,
    /// invalid character in month
    InvalidCharMonth,
    /// invalid character in day
    InvalidCharDay,
    /// invalid time separator, expected `:`
    InvalidCharTimeSep,
    /// invalid character in hour
    InvalidCharHour,
    /// invalid character in minute
    InvalidCharMinute,
    /// invalid character in second
    InvalidCharSecond,
    /// invalid character in second fraction
    InvalidCharSecondFraction,
    /// invalid timezone sign
    InvalidCharTzSign,
    /// invalid timezone hour
    InvalidCharTzHour,
    /// invalid timezone minute
    InvalidCharTzMinute,
    /// timezone minute value is outside expected range of 0-59
    OutOfRangeTzMinute,
    /// timezone offset must be less than 24 hours
    OutOfRangeTz,
    /// timezone is required to adjust to a new timezone
    TzRequired,
    /// Error getting system time
    SystemTimeError,
    /// month value is outside expected range of 1-12
    OutOfRangeMonth,
    /// day value is outside expected range
    OutOfRangeDay,
    /// hour value is outside expected range of 0-23
    OutOfRangeHour,
    /// minute value is outside expected range of 0-59
    OutOfRangeMinute,
    /// second value is outside expected range of 0-59
    OutOfRangeSecond,
    /// second fraction value is more than 6 digits long
    SecondFractionTooLong,
    /// second fraction digits missing after `.`
    SecondFractionMissing,
    /// millisecond fraction value is more than 3 digits long
    MillisecondFractionTooLong,
    /// invalid digit in duration
    DurationInvalidNumber,
    /// `t` character repeated in duration
    DurationTRepeated,
    /// quantity fraction invalid in duration
    DurationInvalidFraction,
    /// quantity invalid in time part of duration
    DurationInvalidTimeUnit,
    /// quantity invalid in date part of duration
    DurationInvalidDateUnit,
    /// "day" identifier in duration not correctly formatted
    DurationInvalidDays,
    /// a numeric value in the duration is too large
    DurationValueTooLarge,
    /// durations may not exceed 999,999,999 days
    DurationHourValueTooLarge,
    /// durations hours must less than 1,000,000,000
    DurationDaysTooLarge,
    /// dates before 1600 are not supported as unix timestamps
    DateTooSmall,
    /// dates after 9999 are not supported as unix timestamps
    DateTooLarge,
    /// numeric times may not exceed 86,399 seconds
    TimeTooLarge,
}

#[derive(Debug, Display, EnumMessage, PartialEq, Eq, Clone)]
#[strum(serialize_all = "snake_case")]
pub enum ConfigError {
    // SecondsPrecisionOverflowBehavior string representation, must be one of "error" or "truncate"
    UnknownMicrosecondsPrecisionOverflowBehaviorString,
}

/// Used internally to write numbers to a buffer for `Display` of speedate types
fn display_num_buf(num: usize, start: usize, value: u32, buf: &mut [u8]) {
    for i in 0..num {
        if (i + 1) == num {
            buf[i + start] = b'0' + (value % 10) as u8;
        } else if num <= 2 {
            buf[i + start] = b'0' + (value / (10i32.pow((num - 1 - i) as u32)) as u32) as u8;
        } else {
            buf[i + start] = b'0' + (value / (10i32.pow((num - 1 - i) as u32)) as u32 % 10) as u8;
        }
    }
}