1from collections import OrderedDict
2from decimal import Decimal, InvalidOperation
3
4import arrow # type: ignore
5
6from isoduration.parser.exceptions import (
7 IncorrectDesignator,
8 NoTime,
9 OutOfDesignators,
10 UnknownToken,
11 UnparseableValue,
12)
13from isoduration.parser.util import (
14 is_letter,
15 is_number,
16 is_time,
17 is_week,
18 parse_designator,
19)
20from isoduration.types import DateDuration, Duration, TimeDuration
21
22
23def parse_datetime_duration(duration_str: str, sign: int) -> Duration:
24 try:
25 duration: arrow.Arrow = arrow.get(duration_str)
26 except (arrow.ParserError, ValueError):
27 raise UnparseableValue(f"Value could not be parsed as datetime: {duration_str}")
28
29 return Duration(
30 DateDuration(
31 years=sign * duration.year,
32 months=sign * duration.month,
33 days=sign * duration.day,
34 ),
35 TimeDuration(
36 hours=sign * duration.hour,
37 minutes=sign * duration.minute,
38 seconds=sign * duration.second,
39 ),
40 )
41
42
43def parse_date_duration(date_str: str, sign: int) -> Duration:
44 date_designators = OrderedDict(
45 (("Y", "years"), ("M", "months"), ("D", "days"), ("W", "weeks"))
46 )
47
48 duration = DateDuration()
49 tmp_value = ""
50
51 for idx, ch in enumerate(date_str):
52 if is_time(ch):
53 if tmp_value != "" and tmp_value == date_str[:idx]:
54 # PYYYY-MM-DDThh:mm:ss
55 # PYYYYMMDDThhmmss
56 return parse_datetime_duration(date_str, sign)
57
58 time_idx = idx + 1
59 time_str = date_str[time_idx:]
60
61 if time_str == "":
62 raise NoTime("Wanted time, no time provided")
63
64 return Duration(duration, parse_time_duration(time_str, sign))
65
66 if is_letter(ch):
67 try:
68 key = parse_designator(date_designators, ch)
69 value = sign * Decimal(tmp_value)
70 except OutOfDesignators as exc:
71 raise IncorrectDesignator(
72 f"Wrong date designator, or designator in the wrong order: {ch}"
73 ) from exc
74 except InvalidOperation as exc:
75 raise UnparseableValue(
76 f"Value could not be parsed as decimal: {tmp_value}"
77 ) from exc
78
79 if is_week(ch) and duration != DateDuration():
80 raise IncorrectDesignator(
81 "Week is incompatible with any other date designator"
82 )
83
84 setattr(duration, key, value)
85 tmp_value = ""
86
87 continue
88
89 if is_number(ch):
90 if ch == ",":
91 tmp_value += "."
92 else:
93 tmp_value += ch
94
95 continue
96
97 raise UnknownToken(f"Token not recognizable: {ch}")
98
99 return Duration(duration, TimeDuration())
100
101
102def parse_time_duration(time_str: str, sign: int) -> TimeDuration:
103 time_designators = OrderedDict((("H", "hours"), ("M", "minutes"), ("S", "seconds")))
104
105 duration = TimeDuration()
106 tmp_value = ""
107
108 for ch in time_str:
109 if is_letter(ch):
110 try:
111 key = parse_designator(time_designators, ch)
112 value = sign * Decimal(tmp_value)
113 except OutOfDesignators as exc:
114 raise IncorrectDesignator(
115 f"Wrong time designator, or designator in the wrong order: {ch}"
116 ) from exc
117 except InvalidOperation as exc:
118 raise UnparseableValue(
119 f"Value could not be parsed as decimal: {tmp_value}"
120 ) from exc
121
122 setattr(duration, key, value)
123 tmp_value = ""
124
125 continue
126
127 if is_number(ch):
128 if ch == ",":
129 tmp_value += "."
130 else:
131 tmp_value += ch
132
133 continue
134
135 raise UnknownToken(f"Token not recognizable: {ch}")
136
137 return duration