1use super::{
2 constants::{
3 MONTH_ABBREVIATED, MONTH_WIDE, SECS_PER_HOUR, SECS_PER_HOUR_U64, SECS_PER_MINUTE,
4 SECS_PER_MINUTE_U64, WDAY_WIDE,
5 },
6 format::get_length,
7};
8use crate::{
9 errors::{invalid_format::create_invalid_format, AstrolabeError},
10 Date, DateUtilities,
11};
12
13pub(crate) fn parse_offset(string: &str) -> Result<i32, AstrolabeError> {
15 if string.starts_with('Z') {
16 return Ok(0);
17 }
18 if string.len() != 6 {
19 return Err(create_invalid_format(
20 "Failed parsing the offset from the RFC 3339 string. Format should be +XX:XX or -XX:XX"
21 .to_string(),
22 ));
23 }
24
25 let hour = string[1..3].parse::<u32>().map_err(|_| {
26 create_invalid_format(
27 "Failed parsing the hour of the offset from the RFC 3339 string".to_string(),
28 )
29 })?;
30 let min = string[4..6].parse::<u32>().map_err(|_| {
31 create_invalid_format(
32 "Failed parsing the minute of the offset from the RFC 3339 string".to_string(),
33 )
34 })?;
35
36 if hour > 23 {
37 return Err(create_invalid_format(
38 "Failed parsing the hour of the offset from the RFC 3339 string. Hour has to be less than 24".to_string(),
39 ));
40 } else if min > 59 {
41 return Err(create_invalid_format(
42 "Failed parsing the minute of the offset from the RFC 3339 string. Minute has to be less than 60".to_string(),
43 ));
44 }
45
46 let offset = hour * SECS_PER_HOUR + min * SECS_PER_MINUTE;
47
48 Ok(if string.starts_with('+') {
49 offset as i32
50 } else {
51 -(offset as i32)
52 })
53}
54
55pub(crate) fn parse_format_string(format: &str) -> Vec<String> {
57 let escaped_format = format.replace("''", "\u{0000}");
58
59 let mut parts: Vec<String> = Vec::new();
60 let mut currently_escaped = false;
61
62 for char in escaped_format.chars() {
63 match char {
64 '\'' => {
65 if !currently_escaped {
66 parts.push(char.to_string());
67 } else {
68 parts.last_mut().unwrap().push(char);
70 }
71 currently_escaped = !currently_escaped;
72 }
73 _ => {
74 if currently_escaped || parts.last().unwrap_or(&"".to_string()).starts_with(char) {
75 parts.last_mut().unwrap().push(char);
77 } else {
78 parts.push(char.to_string());
79 }
80 }
81 };
82 }
83 parts
84}
85
86pub(crate) struct ParsedPart {
87 pub(crate) value: i64,
88 pub(crate) unit: ParseUnit,
89}
90
91pub(crate) enum ParseUnit {
92 Year,
93 Month,
94 DayOfMonth,
95 DayOfYear,
96 Hour,
97 Period,
98 PeriodHour,
99 Minute,
100 Second,
101 Decis,
102 Centis,
103 Millis,
104 Micros,
105 Nanos,
106 Offset,
107}
108
109#[derive(Default)]
110pub(crate) struct ParsedDate {
111 pub(crate) year: Option<i32>,
112 pub(crate) month: Option<u32>,
113 pub(crate) day_of_month: Option<u32>,
114 pub(crate) day_of_year: Option<u32>,
115}
116
117#[derive(Default)]
118pub(crate) struct ParsedTime {
119 pub(crate) hour: Option<u64>,
120 pub(crate) period_hour: Option<u64>,
121 pub(crate) period: Option<Period>,
122 pub(crate) minute: Option<u64>,
123 pub(crate) second: Option<u64>,
124 pub(crate) decis: Option<u64>,
125 pub(crate) centis: Option<u64>,
126 pub(crate) millis: Option<u64>,
127 pub(crate) micros: Option<u64>,
128 pub(crate) nanos: Option<u64>,
129 pub(crate) offset: Option<i32>,
130}
131
132pub(crate) enum Period {
133 AM = 0,
134 PM = 12,
135}
136
137pub(crate) fn parse_part(
140 chars: &str,
141 string: &mut String,
142) -> Result<Option<ParsedPart>, AstrolabeError> {
143 let first_char = chars.chars().next().unwrap();
145 Ok(match first_char {
146 'G' | 'y' | 'q' | 'M' | 'w' | 'd' | 'D' | 'e' => parse_date_part(chars, string)?,
147 'a' | 'b' | 'h' | 'H' | 'K' | 'k' | 'm' | 's' | 'n' | 'X' | 'x' => {
148 parse_time_part(chars, string)?
149 }
150 _ => {
151 remove_part(chars.len(), string)?;
152 None
153 }
154 })
155}
156
157pub(crate) fn parse_date_part(
160 chars: &str,
161 string: &mut String,
162) -> Result<Option<ParsedPart>, AstrolabeError> {
163 let first_char = chars.chars().next().unwrap();
165 Ok(match first_char {
166 'G' => match chars.len() {
167 1..=3 => {
168 remove_part(2, string)?;
169 None
170 }
171 5 => {
172 remove_part(1, string)?;
173 None
174 }
175 _ => {
176 if string.starts_with("Before Christ") {
177 remove_part("Before Christ".len(), string).unwrap();
179 None
180 } else if string.starts_with("Anno Domini") {
181 remove_part("Anno Domini".len(), string).unwrap();
183 None
184 } else {
185 return Err(create_invalid_format(format!(
186 "Could not parse '{}' from given string.",
187 chars
188 )));
189 }
190 }
191 },
192 'y' => match chars.len() {
193 2 => {
194 if string.starts_with('-') {
195 let year = pick_part::<i32>(3, string, "year")?;
196 Some(ParsedPart {
197 value: year as i64,
198 unit: ParseUnit::Year,
199 })
200 } else {
201 let sub_century_year = pick_part::<i32>(2, string, "year")?;
202 let current_century = Date::now().year() / 1000 * 1000;
203 Some(ParsedPart {
204 value: (current_century + sub_century_year) as i64,
205 unit: ParseUnit::Year,
206 })
207 }
208 }
209 1 | 3 | 4 => {
210 let mut year_length = usize::from(string.starts_with('-'));
211 let string_length = string.chars().count();
212 while string_length > year_length
213 && string.chars().nth(year_length).unwrap().is_ascii_digit()
214 {
215 year_length += 1;
216 }
217
218 let year = pick_part::<i32>(year_length, string, "year")?;
219
220 Some(ParsedPart {
221 value: year as i64,
222 unit: ParseUnit::Year,
223 })
224 }
225 _ => {
226 let year = if string.starts_with('-') {
227 pick_part::<i32>(chars.len() + 1, string, "year")?
228 } else {
229 pick_part::<i32>(chars.len(), string, "year")?
230 };
231
232 Some(ParsedPart {
233 value: year as i64,
234 unit: ParseUnit::Year,
235 })
236 }
237 },
238 'q' => match chars.len() {
239 1 | 2 => {
240 remove_part(chars.len(), string)?;
241 None
242 }
243 3 => {
244 remove_part(2, string)?;
245 None
246 }
247 4 => {
248 for quarter in ["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"] {
249 if string.starts_with(quarter) {
250 remove_part(quarter.len(), string).unwrap();
252 return Ok(None);
253 };
254 }
255 return Err(create_invalid_format(format!(
256 "Could not parse '{}' from given string.",
257 chars
258 )));
259 }
260 _ => {
261 remove_part(1, string)?;
262 None
263 }
264 },
265 'M' => parse_month(chars.len(), string)?,
266 'w' => match chars.len() {
267 1 => match string.chars().nth(1) {
268 Some(char) if char.is_ascii_digit() => {
269 remove_part(2, string).unwrap();
271 None
272 }
273 _ => {
274 remove_part(1, string)?;
275 None
276 }
277 },
278 _ => {
279 remove_part(get_length(chars.len(), 2, 2), string)?;
280 None
281 }
282 },
283 'd' => match chars.len() {
284 1 => match string.chars().nth(1) {
285 Some(char) if char.is_ascii_digit() => {
286 let day = pick_part::<u32>(2, string, "day of month")?;
287
288 Some(ParsedPart {
289 value: day as i64,
290 unit: ParseUnit::DayOfMonth,
291 })
292 }
293 _ => {
294 let day = pick_part::<u32>(1, string, "day of month")?;
295
296 Some(ParsedPart {
297 value: day as i64,
298 unit: ParseUnit::DayOfMonth,
299 })
300 }
301 },
302 _ => {
303 let day = pick_part::<u32>(2, string, "day of month")?;
304
305 Some(ParsedPart {
306 value: day as i64,
307 unit: ParseUnit::DayOfMonth,
308 })
309 }
310 },
311 'D' => match chars.len() {
312 2 => match string.chars().nth(2) {
313 Some(char) if char.is_ascii_digit() => {
314 let day = pick_part::<u32>(3, string, "day of year").unwrap();
316
317 Some(ParsedPart {
318 value: day as i64,
319 unit: ParseUnit::DayOfYear,
320 })
321 }
322 _ => {
323 let day = pick_part::<u32>(2, string, "day of year")?;
324
325 Some(ParsedPart {
326 value: day as i64,
327 unit: ParseUnit::DayOfYear,
328 })
329 }
330 },
331 3 => {
332 let day = pick_part::<u32>(3, string, "day of year")?;
333
334 Some(ParsedPart {
335 value: day as i64,
336 unit: ParseUnit::DayOfYear,
337 })
338 }
339 _ => match string.chars().nth(1) {
340 Some(char) if char.is_ascii_digit() => match string.chars().nth(2) {
341 Some(char) if char.is_ascii_digit() => {
342 let day = pick_part::<u32>(3, string, "day of year").unwrap();
344
345 Some(ParsedPart {
346 value: day as i64,
347 unit: ParseUnit::DayOfYear,
348 })
349 }
350 _ => {
351 let day = pick_part::<u32>(2, string, "day of year").unwrap();
353
354 Some(ParsedPart {
355 value: day as i64,
356 unit: ParseUnit::DayOfYear,
357 })
358 }
359 },
360 _ => {
361 let day = pick_part::<u32>(1, string, "day of year")?;
362
363 Some(ParsedPart {
364 value: day as i64,
365 unit: ParseUnit::DayOfYear,
366 })
367 }
368 },
369 },
370 'e' => parse_wday(chars.len(), string)?,
371 _ => {
372 remove_part(chars.len(), string)?;
373 None
374 }
375 })
376}
377
378pub(crate) fn parse_time_part(
381 chars: &str,
382 string: &mut String,
383) -> Result<Option<ParsedPart>, AstrolabeError> {
384 let first_char = chars.chars().next().unwrap();
386 Ok(match first_char {
387 'a' => match chars.len() {
388 4 => {
389 let period = pick_part::<String>(4, string, "period")?;
390 match period.as_str() {
391 "a.m." => Some(ParsedPart {
392 value: 0,
393 unit: ParseUnit::Period,
394 }),
395 "p.m." => Some(ParsedPart {
396 value: 1,
397 unit: ParseUnit::Period,
398 }),
399 _ => {
400 return Err(create_invalid_format(format!(
401 "Could not parse '{}' from given string.",
402 chars
403 )));
404 }
405 }
406 }
407 5 => {
408 let period = pick_part::<String>(1, string, "period")?;
409 match period.as_str() {
410 "a" => Some(ParsedPart {
411 value: 0,
412 unit: ParseUnit::Period,
413 }),
414 "p" => Some(ParsedPart {
415 value: 1,
416 unit: ParseUnit::Period,
417 }),
418 _ => {
419 return Err(create_invalid_format(format!(
420 "Could not parse '{}' from given string.",
421 chars
422 )));
423 }
424 }
425 }
426 _ => {
427 let period = pick_part::<String>(2, string, "period")?;
428 match period.as_str() {
429 "am" | "AM" => Some(ParsedPart {
430 value: 0,
431 unit: ParseUnit::Period,
432 }),
433 "pm" | "PM" => Some(ParsedPart {
434 value: 1,
435 unit: ParseUnit::Period,
436 }),
437 _ => {
438 return Err(create_invalid_format(format!(
439 "Could not parse '{}' from given string.",
440 chars
441 )));
442 }
443 }
444 }
445 },
446 'b' => match chars.len() {
447 4 => {
448 for (n, period) in ["a.m.", "midnight", "p.m.", "noon"].iter().enumerate() {
449 if string.starts_with(period) {
450 remove_part(period.len(), string).unwrap();
452 return Ok(Some(ParsedPart {
453 value: if n <= 1 { 0 } else { 1 },
454 unit: ParseUnit::Period,
455 }));
456 };
457 }
458 return Err(create_invalid_format(format!(
459 "Could not parse '{}' from given string.",
460 chars
461 )));
462 }
463 5 => {
464 for (n, period) in ["a", "mi", "p", "n"].iter().enumerate() {
465 if string.starts_with(period) {
466 remove_part(period.len(), string).unwrap();
468 return Ok(Some(ParsedPart {
469 value: if n <= 1 { 0 } else { 1 },
470 unit: ParseUnit::Period,
471 }));
472 };
473 }
474 return Err(create_invalid_format(format!(
475 "Could not parse '{}' from given string.",
476 chars
477 )));
478 }
479 _ => {
480 for (n, period) in ["am", "AM", "midnight", "pm", "PM", "noon"]
481 .iter()
482 .enumerate()
483 {
484 if string.starts_with(period) {
485 remove_part(period.len(), string).unwrap();
487 return Ok(Some(ParsedPart {
488 value: if n <= 2 { 0 } else { 1 },
489 unit: ParseUnit::Period,
490 }));
491 };
492 }
493 return Err(create_invalid_format(format!(
494 "Could not parse '{}' from given string.",
495 chars
496 )));
497 }
498 },
499 'h' => match chars.len() {
500 1 => match string.chars().nth(1) {
501 Some(char) if char.is_ascii_digit() => {
502 let hour = pick_part::<u32>(2, string, "hour")?;
503 Some(ParsedPart {
504 value: if hour == 12 { 0 } else { hour } as i64,
505 unit: ParseUnit::PeriodHour,
506 })
507 }
508 _ => {
509 let hour = pick_part::<u32>(1, string, "hour")?;
510 Some(ParsedPart {
511 value: hour as i64,
513 unit: ParseUnit::PeriodHour,
514 })
515 }
516 },
517 _ => {
518 let hour = pick_part::<u32>(2, string, "hour")?;
519 Some(ParsedPart {
520 value: if hour == 12 { 0 } else { hour } as i64,
521 unit: ParseUnit::PeriodHour,
522 })
523 }
524 },
525 'H' => match chars.len() {
526 1 => match string.chars().nth(1) {
527 Some(char) if char.is_ascii_digit() => {
528 let hour = pick_part::<u32>(2, string, "hour")?;
529 Some(ParsedPart {
530 value: hour as i64,
531 unit: ParseUnit::Hour,
532 })
533 }
534 _ => {
535 let hour = pick_part::<u32>(1, string, "hour")?;
536 Some(ParsedPart {
537 value: hour as i64,
538 unit: ParseUnit::Hour,
539 })
540 }
541 },
542 _ => {
543 let hour = pick_part::<u32>(2, string, "hour")?;
544 Some(ParsedPart {
545 value: hour as i64,
546 unit: ParseUnit::Hour,
547 })
548 }
549 },
550 'K' => match chars.len() {
551 1 => match string.chars().nth(1) {
552 Some(char) if char.is_ascii_digit() => {
553 let hour = pick_part::<u32>(2, string, "hour")?;
554 Some(ParsedPart {
555 value: hour as i64,
556 unit: ParseUnit::PeriodHour,
557 })
558 }
559 _ => {
560 let hour = pick_part::<u32>(1, string, "hour")?;
561 Some(ParsedPart {
562 value: hour as i64,
563 unit: ParseUnit::PeriodHour,
564 })
565 }
566 },
567 _ => {
568 let hour = pick_part::<u32>(2, string, "hour")?;
569 Some(ParsedPart {
570 value: hour as i64,
571 unit: ParseUnit::PeriodHour,
572 })
573 }
574 },
575 'k' => match chars.len() {
576 1 => match string.chars().nth(1) {
577 Some(char) if char.is_ascii_digit() => {
578 let hour = pick_part::<u32>(2, string, "hour")?;
579 Some(ParsedPart {
580 value: if hour == 24 { 0 } else { hour } as i64,
581 unit: ParseUnit::Hour,
582 })
583 }
584 _ => {
585 let hour = pick_part::<u32>(1, string, "hour")?;
586 Some(ParsedPart {
587 value: hour as i64,
589 unit: ParseUnit::Hour,
590 })
591 }
592 },
593 _ => {
594 let hour = pick_part::<u32>(2, string, "hour")?;
595 Some(ParsedPart {
596 value: if hour == 24 { 0 } else { hour } as i64,
597 unit: ParseUnit::Hour,
598 })
599 }
600 },
601 'm' => match chars.len() {
602 1 => match string.chars().nth(1) {
603 Some(char) if char.is_ascii_digit() => {
604 let minute = pick_part::<u32>(2, string, "minute")?;
605 Some(ParsedPart {
606 value: minute as i64,
607 unit: ParseUnit::Minute,
608 })
609 }
610 _ => {
611 let minute = pick_part::<u32>(1, string, "minute")?;
612 Some(ParsedPart {
613 value: minute as i64,
614 unit: ParseUnit::Minute,
615 })
616 }
617 },
618 _ => {
619 let minute = pick_part::<u32>(2, string, "minute")?;
620 Some(ParsedPart {
621 value: minute as i64,
622 unit: ParseUnit::Minute,
623 })
624 }
625 },
626 's' => match chars.len() {
627 1 => match string.chars().nth(1) {
628 Some(char) if char.is_ascii_digit() => {
629 let seconds = pick_part::<u32>(2, string, "seconds")?;
630 Some(ParsedPart {
631 value: seconds as i64,
632 unit: ParseUnit::Second,
633 })
634 }
635 _ => {
636 let seconds = pick_part::<u32>(1, string, "seconds")?;
637 Some(ParsedPart {
638 value: seconds as i64,
639 unit: ParseUnit::Second,
640 })
641 }
642 },
643 _ => {
644 let seconds = pick_part::<u32>(2, string, "seconds")?;
645 Some(ParsedPart {
646 value: seconds as i64,
647 unit: ParseUnit::Second,
648 })
649 }
650 },
651 'n' => match chars.len() {
652 1 => {
653 let subsecond = pick_part::<u32>(1, string, "subseconds")?;
654
655 Some(ParsedPart {
656 value: subsecond as i64,
657 unit: ParseUnit::Decis,
658 })
659 }
660 2 => {
661 let subsecond = pick_part::<u32>(2, string, "subseconds")?;
662
663 Some(ParsedPart {
664 value: subsecond as i64,
665 unit: ParseUnit::Centis,
666 })
667 }
668 4 => {
669 let subsecond = pick_part::<u32>(6, string, "subseconds")?;
670
671 Some(ParsedPart {
672 value: subsecond as i64,
673 unit: ParseUnit::Micros,
674 })
675 }
676 5 => {
677 let subsecond = pick_part::<u32>(9, string, "subseconds")?;
678
679 Some(ParsedPart {
680 value: subsecond as i64,
681 unit: ParseUnit::Nanos,
682 })
683 }
684 _ => {
685 let subsecond = pick_part::<u32>(3, string, "subseconds")?;
686
687 Some(ParsedPart {
688 value: subsecond as i64,
689 unit: ParseUnit::Millis,
690 })
691 }
692 },
693 'X' => parse_zone(chars.len(), string, true)?,
694 'x' => parse_zone(chars.len(), string, false)?,
695 _ => {
696 remove_part(chars.len(), string)?;
697 None
698 }
699 })
700}
701
702fn parse_month(length: usize, string: &mut String) -> Result<Option<ParsedPart>, AstrolabeError> {
704 Ok(match length {
705 1 | 2 => {
706 let month = pick_part::<u32>(length, string, "month")?;
707
708 Some(ParsedPart {
709 value: month as i64,
710 unit: ParseUnit::Month,
711 })
712 }
713 3 => {
714 for (n, month) in MONTH_ABBREVIATED.iter().enumerate() {
715 if string.starts_with(month) {
716 remove_part(month.len(), string).unwrap();
718 return Ok(Some(ParsedPart {
719 value: (n + 1) as i64,
720 unit: ParseUnit::Month,
721 }));
722 };
723 }
724 return Err(create_invalid_format(
725 "Could not parse month from given string.".to_string(),
726 ));
727 }
728 5 => {
730 remove_part(1, string)?;
731 None
732 }
733 _ => {
734 for (n, month) in MONTH_WIDE.iter().enumerate() {
735 if string.starts_with(month) {
736 remove_part(month.len(), string).unwrap();
738 return Ok(Some(ParsedPart {
739 value: (n + 1) as i64,
740 unit: ParseUnit::Month,
741 }));
742 };
743 }
744 return Err(create_invalid_format(
745 "Could not parse month from given string.".to_string(),
746 ));
747 }
748 })
749}
750
751fn parse_wday(length: usize, string: &mut String) -> Result<Option<ParsedPart>, AstrolabeError> {
753 Ok(match length {
754 2 | 3 => {
755 remove_part(length, string)?;
756 None
757 }
758 4 => {
759 for wday in WDAY_WIDE {
760 if string.starts_with(wday) {
761 remove_part(wday.len(), string).unwrap();
763 return Ok(None);
764 };
765 }
766 return Err(create_invalid_format(
767 "Could not parse week day from given string.".to_string(),
768 ));
769 }
770 6 | 8 => {
771 remove_part(2, string)?;
772 None
773 }
774 _ => {
776 remove_part(1, string)?;
777 None
778 }
779 })
780}
781
782fn parse_zone(
784 length: usize,
785 string: &mut String,
786 with_z: bool,
787) -> Result<Option<ParsedPart>, AstrolabeError> {
788 let prefix = pick_part::<String>(1, string, "timezone prefix")?;
789
790 let multiplier = match prefix.as_str() {
791 "Z" if with_z => {
792 return Ok(Some(ParsedPart {
793 value: 0,
794 unit: ParseUnit::Offset,
795 }));
796 }
797 "+" => 1,
798 "-" => -1,
799 _ => {
800 return Err(create_invalid_format(
801 "Couldn't parse prefix of timezone offset. Prefix has to be either '+' or '-'."
802 .to_string(),
803 ))
804 }
805 };
806
807 let hour = pick_part::<u32>(2, string, "timezone hour")?;
808
809 Ok(match length {
810 1 => match string.chars().next() {
811 Some(char) if char.is_ascii_digit() => {
812 let minute = pick_part::<u32>(2, string, "timezone minute")?;
813
814 let offset = (hour * SECS_PER_HOUR_U64 as u32 + minute * SECS_PER_MINUTE_U64 as u32)
815 as i64
816 * multiplier;
817
818 Some(ParsedPart {
819 value: offset,
820 unit: ParseUnit::Offset,
821 })
822 }
823 _ => {
824 let offset = (hour * SECS_PER_HOUR_U64 as u32) as i64 * multiplier;
825
826 Some(ParsedPart {
827 value: offset,
828 unit: ParseUnit::Offset,
829 })
830 }
831 },
832 2 => {
833 let minute = pick_part::<u32>(2, string, "timezone minute")?;
834
835 let offset = (hour * SECS_PER_HOUR_U64 as u32 + minute * SECS_PER_MINUTE_U64 as u32)
836 as i64
837 * multiplier;
838
839 Some(ParsedPart {
840 value: offset,
841 unit: ParseUnit::Offset,
842 })
843 }
844 4 => match string.chars().nth(2) {
845 Some(char) if char.is_ascii_digit() => {
846 let minute = pick_part::<u32>(2, string, "timezone minute")?;
847 let second = pick_part::<u32>(2, string, "timezone second")?;
848
849 let offset = (hour * SECS_PER_HOUR_U64 as u32
850 + minute * SECS_PER_MINUTE_U64 as u32
851 + second) as i64
852 * multiplier;
853
854 Some(ParsedPart {
855 value: offset,
856 unit: ParseUnit::Offset,
857 })
858 }
859 _ => {
860 let minute = pick_part::<u32>(2, string, "timezone minute")?;
861
862 let offset = (hour * SECS_PER_HOUR_U64 as u32 + minute * SECS_PER_MINUTE_U64 as u32)
863 as i64
864 * multiplier;
865
866 Some(ParsedPart {
867 value: offset,
868 unit: ParseUnit::Offset,
869 })
870 }
871 },
872 5 => match string.chars().nth(4) {
873 Some(char) if char.is_ascii_digit() => {
874 remove_part(1, string).unwrap();
876 let minute = pick_part::<u32>(2, string, "timezone minute")?;
877 remove_part(1, string).unwrap();
879 let second = pick_part::<u32>(2, string, "timezone second")?;
880
881 let offset = (hour * SECS_PER_HOUR_U64 as u32
882 + minute * SECS_PER_MINUTE_U64 as u32
883 + second) as i64
884 * multiplier;
885
886 Some(ParsedPart {
887 value: offset,
888 unit: ParseUnit::Offset,
889 })
890 }
891 _ => {
892 remove_part(1, string)?;
893 let minute = pick_part::<u32>(2, string, "timezone minute")?;
894
895 let offset = (hour * SECS_PER_HOUR_U64 as u32 + minute * SECS_PER_MINUTE_U64 as u32)
896 as i64
897 * multiplier;
898
899 Some(ParsedPart {
900 value: offset,
901 unit: ParseUnit::Offset,
902 })
903 }
904 },
905 _ => {
906 remove_part(1, string)?;
907 let minute = pick_part::<u32>(2, string, "timezone minute")?;
908
909 let offset = (hour * SECS_PER_HOUR_U64 as u32 + minute * SECS_PER_MINUTE_U64 as u32)
910 as i64
911 * multiplier;
912
913 Some(ParsedPart {
914 value: offset,
915 unit: ParseUnit::Offset,
916 })
917 }
918 })
919}
920
921fn remove_part(length: usize, string: &mut String) -> Result<(), AstrolabeError> {
922 if string.chars().count() < length {
923 Err(create_invalid_format(
924 "String to parse is too short. Please check your format string.".to_string(),
925 ))
926 } else {
927 string.replace_range(0..length, "");
928 Ok(())
929 }
930}
931
932fn pick_part<T: std::str::FromStr>(
933 length: usize,
934 string: &mut String,
935 part_name: &str,
936) -> Result<T, AstrolabeError> {
937 if string.chars().count() < length {
938 Err(create_invalid_format(
939 "String to parse is too short. Please check your format string.".to_string(),
940 ))
941 } else {
942 let part = string[0..length].parse::<T>().map_err(|_| {
943 create_invalid_format(format!(
944 "Failed parsing {} from given string. Value is '{}'.",
945 part_name,
946 &string[0..length]
947 ))
948 })?;
949 string.replace_range(0..length, "");
950 Ok(part)
951 }
952}