4 from typing import Optional, TypeVar, Tuple
9 T = TypeVar('T', bound='CentCount')
12 class CentCount(object):
13 """A class for representing monetary amounts potentially with
20 currency: str = 'USD',
24 self.strict_mode = strict_mode
25 if isinstance(centcount, str):
26 ret = CentCount._parse(centcount)
28 raise Exception(f'Unable to parse money string "{centcount}"')
31 if isinstance(centcount, float):
32 centcount = int(centcount * 100.0)
33 if not isinstance(centcount, int):
34 centcount = int(centcount)
35 self.centcount = centcount
37 self.currency: Optional[str] = None
39 self.currency: Optional[str] = currency
42 a = float(self.centcount)
46 if self.currency is not None:
47 return '%s %s' % (s, self.currency)
52 return CentCount(centcount=self.centcount, currency=self.currency)
55 return CentCount(centcount=-self.centcount, currency=self.currency)
57 def __add__(self, other):
58 if isinstance(other, CentCount):
59 if self.currency == other.currency:
61 centcount = self.centcount + other.centcount,
62 currency = self.currency
65 raise TypeError('Incompatible currencies in add expression')
68 raise TypeError('In strict_mode only two moneys can be added')
70 return self.__add__(CentCount(other, self.currency))
72 def __sub__(self, other):
73 if isinstance(other, CentCount):
74 if self.currency == other.currency:
76 centcount = self.centcount - other.centcount,
77 currency = self.currency
80 raise TypeError('Incompatible currencies in add expression')
83 raise TypeError('In strict_mode only two moneys can be added')
85 return self.__sub__(CentCount(other, self.currency))
87 def __mul__(self, other):
88 if isinstance(other, CentCount):
89 raise TypeError('can not multiply monetary quantities')
92 centcount = int(self.centcount * float(other)),
93 currency = self.currency
96 def __truediv__(self, other):
97 if isinstance(other, CentCount):
98 raise TypeError('can not divide monetary quantities')
101 centcount = int(float(self.centcount) / float(other)),
102 currency = self.currency
106 return self.centcount.__int__()
109 return self.centcount.__float__() / 100.0
111 def truncate_fractional_cents(self):
113 self.centcount = int(math_utils.truncate_float(x))
114 return self.centcount
116 def round_fractional_cents(self):
118 self.centcount = int(round(x, 2))
119 return self.centcount
123 def __rsub__(self, other):
124 if isinstance(other, CentCount):
125 if self.currency == other.currency:
127 centcount = other.centcount - self.centcount,
128 currency = self.currency
131 raise TypeError('Incompatible currencies in sub expression')
134 raise TypeError('In strict_mode only two moneys can be added')
137 centcount = int(other) - self.centcount,
138 currency = self.currency
144 # Override comparison operators to also compare currency.
146 def __eq__(self, other):
149 if isinstance(other, CentCount):
151 self.centcount == other.centcount and
152 self.currency == other.currency
155 raise TypeError("In strict mode only two CentCounts can be compared")
157 return self.centcount == int(other)
159 def __ne__(self, other):
160 result = self.__eq__(other)
161 if result is NotImplemented:
165 def __lt__(self, other):
166 if isinstance(other, CentCount):
167 if self.currency == other.currency:
168 return self.centcount < other.centcount
170 raise TypeError('can not directly compare different currencies')
173 raise TypeError('In strict mode, only two CentCounts can be compated')
175 return self.centcount < int(other)
177 def __gt__(self, other):
178 if isinstance(other, CentCount):
179 if self.currency == other.currency:
180 return self.centcount > other.centcount
182 raise TypeError('can not directly compare different currencies')
185 raise TypeError('In strict mode, only two CentCounts can be compated')
187 return self.centcount > int(other)
189 def __le__(self, other):
190 return self < other or self == other
192 def __ge__(self, other):
193 return self > other or self == other
198 CENTCOUNT_RE = re.compile("^([+|-]?)(\d+)(\.\d+)$")
199 CURRENCY_RE = re.compile("^[A-Z][A-Z][A-Z]$")
202 def _parse(cls, s: str) -> Optional[Tuple[int, str]]:
206 chunks = s.split(' ')
209 if CentCount.CENTCOUNT_RE.match(chunk) is not None:
210 centcount = int(float(chunk) * 100.0)
211 elif CentCount.CURRENCY_RE.match(chunk) is not None:
215 if centcount is not None and currency is not None:
216 return (centcount, currency)
217 elif centcount is not None:
218 return (centcount, 'USD')
222 def parse(cls, s: str) -> T:
223 chunks = CentCount._parse(s)
224 if chunks is not None:
225 return CentCount(chunks[0], chunks[1])
226 raise Exception(f'Unable to parse money string "{s}"')