@@ -8,7 +8,7 @@ use crate::{ParseError, ParserResult};
8
8
9
9
#[ cfg( feature = "duration" ) ]
10
10
use records:: DurationParseRecord ;
11
- use records:: IxdtfParseRecord ;
11
+ use records:: { IxdtfParseRecord , UtcOffsetRecord } ;
12
12
13
13
use self :: records:: Annotation ;
14
14
@@ -61,9 +61,11 @@ macro_rules! assert_syntax {
61
61
/// assert_eq!(date.day, 2);
62
62
/// assert_eq!(time.hour, 8);
63
63
/// assert_eq!(time.minute, 48);
64
- /// assert_eq!(offset.sign, Sign::Negative);
65
- /// assert_eq!(offset.hour, 5);
66
- /// assert_eq!(offset.minute, 0);
64
+ /// assert_eq!(offset.sign(), Sign::Negative);
65
+ /// assert_eq!(offset.hour(), 5);
66
+ /// assert_eq!(offset.minute(), 0);
67
+ /// assert_eq!(offset.second(), None);
68
+ /// assert_eq!(offset.fraction(), None);
67
69
/// assert!(!tz_annotation.critical);
68
70
/// assert_eq!(
69
71
/// tz_annotation.tz,
@@ -202,9 +204,9 @@ impl<'a> IxdtfParser<'a> {
202
204
/// assert_eq!(time.hour, 12);
203
205
/// assert_eq!(time.minute, 1);
204
206
/// assert_eq!(time.second, 4);
205
- /// assert_eq!(offset.sign, Sign::Negative);
206
- /// assert_eq!(offset.hour, 5);
207
- /// assert_eq!(offset.minute, 0);
207
+ /// assert_eq!(offset.sign() , Sign::Negative);
208
+ /// assert_eq!(offset.hour() , 5);
209
+ /// assert_eq!(offset.minute() , 0);
208
210
/// assert!(!tz_annotation.critical);
209
211
/// assert_eq!(
210
212
/// tz_annotation.tz,
@@ -228,6 +230,95 @@ impl<'a> IxdtfParser<'a> {
228
230
}
229
231
}
230
232
233
+ /// A parser for time zone offset and IANA identifier strings.
234
+ ///
235
+ /// ✨ *Enabled with the `timezone` Cargo feature.*
236
+ ///
237
+ #[ derive( Debug ) ]
238
+ pub struct TimeZoneParser < ' a > {
239
+ cursor : Cursor < ' a > ,
240
+ }
241
+
242
+ impl < ' a > TimeZoneParser < ' a > {
243
+ /// Creates a new `TimeZoneParser` from a slice of utf-8 bytes.
244
+ #[ inline]
245
+ #[ must_use]
246
+ pub fn from_utf8 ( source : & ' a [ u8 ] ) -> Self {
247
+ Self {
248
+ cursor : Cursor :: new ( source) ,
249
+ }
250
+ }
251
+
252
+ /// Creates a new `TimeZoneParser` from a source `&str`.
253
+ #[ inline]
254
+ #[ must_use]
255
+ #[ allow( clippy:: should_implement_trait) ]
256
+ pub fn from_str ( source : & ' a str ) -> Self {
257
+ Self :: from_utf8 ( source. as_bytes ( ) )
258
+ }
259
+
260
+ /// Parse a UTC offset from the provided source.
261
+ ///
262
+ /// This method can parse both a minute precision and full
263
+ /// precision offset.
264
+ ///
265
+ /// ## Minute precision offset example
266
+ ///
267
+ /// ```rust
268
+ /// use ixdtf::parsers::{TimeZoneParser, records::Sign};
269
+ ///
270
+ /// let offset_src = "-05:00";
271
+ /// let parse_result = TimeZoneParser::from_str(offset_src).parse_offset().unwrap();
272
+ /// assert_eq!(parse_result.sign(), Sign::Negative);
273
+ /// assert_eq!(parse_result.hour(), 5);
274
+ /// assert_eq!(parse_result.minute(), 0);
275
+ /// assert_eq!(parse_result.second(), None);
276
+ /// assert_eq!(parse_result.fraction(), None);
277
+ /// ```
278
+ ///
279
+ /// ## Full precision offset example
280
+ ///
281
+ /// ```rust
282
+ /// use ixdtf::parsers::{TimeZoneParser, records::Sign};
283
+ ///
284
+ /// let offset_src = "-05:00:30.123456789";
285
+ /// let parse_result = TimeZoneParser::from_str(offset_src).parse_offset().unwrap();
286
+ /// assert_eq!(parse_result.sign(), Sign::Negative);
287
+ /// assert_eq!(parse_result.hour(), 5);
288
+ /// assert_eq!(parse_result.minute(), 0);
289
+ /// assert_eq!(parse_result.second(), Some(30));
290
+ /// let fraction = parse_result.fraction().unwrap();
291
+ /// assert_eq!(fraction.to_nanoseconds(), Some(123456789));
292
+ /// ```
293
+ #[ inline]
294
+ pub fn parse_offset ( & mut self ) -> ParserResult < UtcOffsetRecord > {
295
+ let result = timezone:: parse_utc_offset ( & mut self . cursor ) ?;
296
+ self . cursor . close ( ) ?;
297
+ Ok ( result)
298
+ }
299
+
300
+ /// Parse an IANA identifier name.
301
+ ///
302
+ ///
303
+ /// ```rust
304
+ /// use ixdtf::parsers::{TimeZoneParser, records::Sign};
305
+ ///
306
+ /// let iana_identifier = "America/Chicago";
307
+ /// let parse_result = TimeZoneParser::from_str(iana_identifier).parse_iana_identifier().unwrap();
308
+ /// assert_eq!(parse_result, iana_identifier.as_bytes());
309
+ ///
310
+ /// let iana_identifier = "Europe/Berlin";
311
+ /// let parse_result = TimeZoneParser::from_str(iana_identifier).parse_iana_identifier().unwrap();
312
+ /// assert_eq!(parse_result, iana_identifier.as_bytes());
313
+ /// ```
314
+ #[ inline]
315
+ pub fn parse_iana_identifier ( & mut self ) -> ParserResult < & ' a [ u8 ] > {
316
+ let result = timezone:: parse_tz_iana_name ( & mut self . cursor ) ?;
317
+ self . cursor . close ( ) ?;
318
+ Ok ( result)
319
+ }
320
+ }
321
+
231
322
/// A parser for ISO8601 Duration strings.
232
323
///
233
324
/// ✨ *Enabled with the `duration` Cargo feature.*
@@ -406,7 +497,7 @@ impl<'a> Cursor<'a> {
406
497
/// # Errors
407
498
/// - Returns an AbruptEnd error if cursor ends.
408
499
fn next_digit ( & mut self ) -> ParserResult < Option < u8 > > {
409
- let ascii_char = self . next_or ( ParseError :: InvalidEnd ) ?;
500
+ let ascii_char = self . next_or ( ParseError :: AbruptEnd { location : "digit" } ) ?;
410
501
if ascii_char. is_ascii_digit ( ) {
411
502
Ok ( Some ( ascii_char - 48 ) )
412
503
} else {
0 commit comments