4 from decimal import Decimal
5 from typing import Optional, Tuple, TypeVar
11 """A class for representing monetary amounts potentially with
17 amount: Decimal = Decimal("0"),
18 currency: str = 'USD',
22 self.strict_mode = strict_mode
23 if isinstance(amount, str):
24 ret = Money._parse(amount)
26 raise Exception(f'Unable to parse money string "{amount}"')
29 if not isinstance(amount, Decimal):
30 amount = Decimal(float(amount))
33 self.currency: Optional[str] = None
35 self.currency = currency
38 a = float(self.amount)
41 if self.currency is not None:
42 return '%s %s' % (s, self.currency)
47 return Money(amount=self.amount, currency=self.currency)
50 return Money(amount=-self.amount, currency=self.currency)
52 def __add__(self, other):
53 if isinstance(other, Money):
54 if self.currency == other.currency:
55 return Money(amount=self.amount + other.amount, currency=self.currency)
57 raise TypeError('Incompatible currencies in add expression')
60 raise TypeError('In strict_mode only two moneys can be added')
63 amount=self.amount + Decimal(float(other)), currency=self.currency
66 def __sub__(self, other):
67 if isinstance(other, Money):
68 if self.currency == other.currency:
69 return Money(amount=self.amount - other.amount, currency=self.currency)
71 raise TypeError('Incompatible currencies in add expression')
74 raise TypeError('In strict_mode only two moneys can be added')
77 amount=self.amount - Decimal(float(other)), currency=self.currency
80 def __mul__(self, other):
81 if isinstance(other, Money):
82 raise TypeError('can not multiply monetary quantities')
85 amount=self.amount * Decimal(float(other)), currency=self.currency
88 def __truediv__(self, other):
89 if isinstance(other, Money):
90 raise TypeError('can not divide monetary quantities')
93 amount=self.amount / Decimal(float(other)), currency=self.currency
97 return self.amount.__float__()
99 def truncate_fractional_cents(self):
101 self.amount = Decimal(math_utils.truncate_float(x))
104 def round_fractional_cents(self):
106 self.amount = Decimal(round(x, 2))
111 def __rsub__(self, other):
112 if isinstance(other, Money):
113 if self.currency == other.currency:
114 return Money(amount=other.amount - self.amount, currency=self.currency)
116 raise TypeError('Incompatible currencies in sub expression')
119 raise TypeError('In strict_mode only two moneys can be added')
122 amount=Decimal(float(other)) - self.amount, currency=self.currency
128 # Override comparison operators to also compare currency.
130 def __eq__(self, other):
133 if isinstance(other, Money):
134 return self.amount == other.amount and self.currency == other.currency
136 raise TypeError("In strict mode only two Moneys can be compared")
138 return self.amount == Decimal(float(other))
140 def __ne__(self, other):
141 result = self.__eq__(other)
142 if result is NotImplemented:
146 def __lt__(self, other):
147 if isinstance(other, Money):
148 if self.currency == other.currency:
149 return self.amount < other.amount
151 raise TypeError('can not directly compare different currencies')
154 raise TypeError('In strict mode, only two Moneys can be compated')
156 return self.amount < Decimal(float(other))
158 def __gt__(self, other):
159 if isinstance(other, Money):
160 if self.currency == other.currency:
161 return self.amount > other.amount
163 raise TypeError('can not directly compare different currencies')
166 raise TypeError('In strict mode, only two Moneys can be compated')
168 return self.amount > Decimal(float(other))
170 def __le__(self, other):
171 return self < other or self == other
173 def __ge__(self, other):
174 return self > other or self == other
179 AMOUNT_RE = re.compile(r"^([+|-]?)(\d+)(\.\d+)$")
180 CURRENCY_RE = re.compile(r"^[A-Z][A-Z][A-Z]$")
183 def _parse(cls, s: str) -> Optional[Tuple[Decimal, str]]:
187 chunks = s.split(' ')
190 if Money.AMOUNT_RE.match(chunk) is not None:
191 amount = Decimal(chunk)
192 elif Money.CURRENCY_RE.match(chunk) is not None:
196 if amount is not None and currency is not None:
197 return (amount, currency)
198 elif amount is not None:
199 return (amount, 'USD')
203 def parse(cls, s: str) -> 'Money':
204 chunks = Money._parse(s)
205 if chunks is not None:
206 return Money(chunks[0], chunks[1])
207 raise Exception(f'Unable to parse money string "{s}"')