]> git.proxmox.com Git - cargo.git/blob - vendor/toml_edit/src/parser/datetime.rs
New upstream version 0.63.1
[cargo.git] / vendor / toml_edit / src / parser / datetime.rs
1 use crate::datetime::*;
2 use crate::parser::errors::CustomError;
3 use crate::parser::trivia::from_utf8_unchecked;
4 use combine::parser::byte::byte;
5 use combine::parser::range::{recognize, take_while1};
6 use combine::stream::RangeStream;
7 use combine::*;
8
9 // ;; Date and Time (as defined in RFC 3339)
10
11 // date-time = offset-date-time / local-date-time / local-date / local-time
12 // offset-date-time = full-date "T" full-time
13 // local-date-time = full-date "T" partial-time
14 // local-date = full-date
15 // local-time = partial-time
16 // full-time = partial-time time-offset
17 parse!(date_time() -> Datetime, {
18 choice!(
19 (
20 full_date(),
21 optional((
22 attempt((
23 satisfy(is_time_delim),
24 look_ahead(time_hour())
25 )),
26 partial_time(),
27 optional(time_offset()),
28 ))
29 )
30 .map(|(date, opt)| {
31 match opt {
32 // Offset Date-Time
33 Some((_, time, offset)) => {
34 Datetime { date: Some(date), time: Some(time), offset }
35 }
36 // Local Date
37 None => {
38 Datetime { date: Some(date), time: None, offset: None}
39 },
40 }
41 }),
42 // Local Time
43 partial_time()
44 .message("While parsing a Time")
45 .map(|t| {
46 t.into()
47 })
48 )
49 .message("While parsing a Date-Time")
50 });
51
52 // full-date = date-fullyear "-" date-month "-" date-mday
53 parse!(full_date() -> Date, {
54 (
55 attempt((date_fullyear(), byte(b'-'))),
56 date_month(),
57 byte(b'-'),
58 date_mday(),
59 ).map(|((year, _), month, _, day)| {
60 Date { year, month, day }
61 })
62 });
63
64 // partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]
65 parse!(partial_time() -> Time, {
66 (
67 attempt((
68 time_hour(),
69 byte(b':'),
70 )),
71 time_minute(),
72 byte(b':'),
73 time_second(),
74 optional(attempt(time_secfrac())),
75 ).map(|((hour, _), minute, _, second, nanosecond)| {
76 Time { hour, minute, second, nanosecond: nanosecond.unwrap_or_default() }
77 })
78 });
79
80 // time-offset = "Z" / time-numoffset
81 // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
82 parse!(time_offset() -> Offset, {
83 attempt(satisfy(|c| c == b'Z' || c == b'z')).map(|_| Offset::Z)
84 .or(
85 (
86 attempt(choice([byte(b'+'), byte(b'-')])),
87 time_hour(),
88 byte(b':'),
89 time_minute(),
90 ).map(|(sign, hours, _, minutes)| {
91 let hours = hours as i8;
92 let hours = match sign {
93 b'+' => hours,
94 b'-' => -hours,
95 _ => unreachable!("Parser prevents this"),
96 };
97 Offset::Custom { hours, minutes }
98 })
99 ).message("While parsing a Time Offset")
100 });
101
102 // date-fullyear = 4DIGIT
103 parse!(date_fullyear() -> u16, {
104 signed_digits(4).map(|d| d as u16)
105 });
106
107 // date-month = 2DIGIT ; 01-12
108 parse!(date_month() -> u8, {
109 unsigned_digits(2).map(|d| d as u8).and_then(|v| {
110 if (1..=12).contains(&v) {
111 Ok(v)
112 } else {
113 Err(CustomError::OutOfRange)
114 }
115 })
116 });
117
118 // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
119 parse!(date_mday() -> u8, {
120 unsigned_digits(2).map(|d| d as u8).and_then(|v| {
121 if (1..=31).contains(&v) {
122 Ok(v)
123 } else {
124 Err(CustomError::OutOfRange)
125 }
126 })
127 });
128
129 // time-delim = "T" / %x20 ; T, t, or space
130 fn is_time_delim(c: u8) -> bool {
131 matches!(c, b'T' | b't' | b' ')
132 }
133
134 // time-hour = 2DIGIT ; 00-23
135 parse!(time_hour() -> u8, {
136 unsigned_digits(2).map(|d| d as u8).and_then(|v| {
137 if (0..=23).contains(&v) {
138 Ok(v)
139 } else {
140 Err(CustomError::OutOfRange)
141 }
142 })
143 });
144
145 // time-minute = 2DIGIT ; 00-59
146 parse!(time_minute() -> u8, {
147 unsigned_digits(2).map(|d| d as u8).and_then(|v| {
148 if (0..=59).contains(&v) {
149 Ok(v)
150 } else {
151 Err(CustomError::OutOfRange)
152 }
153 })
154 });
155
156 // time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules
157 parse!(time_second() -> u8, {
158 unsigned_digits(2).map(|d| d as u8).and_then(|v| {
159 if (0..=60).contains(&v) {
160 Ok(v)
161 } else {
162 Err(CustomError::OutOfRange)
163 }
164 })
165 });
166
167 // time-secfrac = "." 1*DIGIT
168 parse!(time_secfrac() -> u32, {
169 static SCALE: [u32; 10] =
170 [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
171 byte(b'.').and(take_while1(|c: u8| c.is_ascii_digit())).and_then::<_, _, CustomError>(|(_, repr): (u8, &[u8])| {
172 let mut repr = unsafe { from_utf8_unchecked(repr, "`is_ascii_digit` filters out on-ASCII") };
173 let max_digits = SCALE.len() - 1;
174 if max_digits < repr.len() {
175 // Millisecond precision is required. Further precision of fractional seconds is
176 // implementation-specific. If the value contains greater precision than the
177 // implementation can support, the additional precision must be truncated, not rounded.
178 repr = &repr[0..max_digits];
179 }
180
181 let v = repr.parse::<u32>().map_err(|_| CustomError::OutOfRange)?;
182 let num_digits = repr.len();
183
184 // scale the number accordingly.
185 let scale = SCALE.get(num_digits).ok_or(CustomError::OutOfRange)?;
186 let v = v.checked_mul(*scale).ok_or(CustomError::OutOfRange)?;
187 Ok(v)
188 })
189 });
190
191 parse!(signed_digits(count: usize) -> i32, {
192 recognize(skip_count_min_max(
193 *count, *count,
194 satisfy(|c: u8| c.is_ascii_digit()),
195 )).and_then(|b: &[u8]| {
196 let s = unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") };
197 s.parse::<i32>()
198 })
199 });
200
201 parse!(unsigned_digits(count: usize) -> u32, {
202 recognize(skip_count_min_max(
203 *count, *count,
204 satisfy(|c: u8| c.is_ascii_digit()),
205 )).and_then(|b: &[u8]| {
206 let s = unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") };
207 s.parse::<u32>()
208 })
209 });