aboutsummaryrefslogtreecommitdiff
path: root/src/ee/money.py
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2019-03-14 12:23:57 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2019-03-15 08:22:01 +0100
commita8ec679349c3eb9c33a9d33e247fd86cb8e53f81 (patch)
tree094a498b7b30e402c419e8a588e86769f7d408c9 /src/ee/money.py
parent3a90ab0dbf5826bc7476971cd163c9a080d2fb2f (diff)
downloadee-python-a8ec679349c3eb9c33a9d33e247fd86cb8e53f81.tar.gz
ee-python-a8ec679349c3eb9c33a9d33e247fd86cb8e53f81.tar.bz2
ee-python-a8ec679349c3eb9c33a9d33e247fd86cb8e53f81.tar.xz
ee-python-a8ec679349c3eb9c33a9d33e247fd86cb8e53f81.zip
o Adding PriceBreak. Parsing price breaks from DK.
o Adding Money type with parsing.
Diffstat (limited to 'src/ee/money.py')
-rw-r--r--src/ee/money.py297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/ee/money.py b/src/ee/money.py
new file mode 100644
index 0000000..139d0d7
--- /dev/null
+++ b/src/ee/money.py
@@ -0,0 +1,297 @@
+from decimal import Decimal
+from typing import Union, Optional, Mapping, Set
+
+from ee import EeException
+
+__all__ = [
+ "Money",
+ "MoneyContext",
+ "get_default_context",
+ "set_default_context",
+]
+
+
+def _parse(amount: str) -> (str, str):
+ def read_space():
+ nonlocal idx
+ while idx < end:
+ if amount[idx] != " ":
+ break
+
+ idx += 1
+
+ def read_num():
+ nonlocal idx
+ tmp = ""
+ while idx < end:
+ c_ = amount[idx]
+ if not c_.isnumeric():
+ break
+ tmp += c_
+ idx += 1
+
+ return tmp
+
+ def read_word():
+ nonlocal idx
+ tmp = ""
+ while idx < end:
+ c_ = amount[idx]
+
+ if c_.isnumeric() or c_ in (" ", ",", "."):
+ break
+
+ tmp += c_
+ idx += 1
+
+ return tmp
+
+ amount = amount.strip()
+
+ idx = 0
+ end = len(amount)
+
+ if end == 0:
+ return None, None
+
+ # extract leading currency
+ prefix = read_word()
+
+ read_space()
+
+ # If the number starts with '.' or ',' we assume it is a whole separator
+ # if idx < end:
+ # c = amount[idx]
+ # if c == "." or c == ",":
+ # s1 = c
+ # idx += 1
+
+ num = read_num()
+ # if len(num) > 3:
+ # return None, None
+
+ # First and second separators
+ s1 = s2 = None
+ s0s = [] # stuff before first separator
+ s1s = []
+ s2s = []
+
+ if num:
+ s0s.append(num)
+
+ while idx < end:
+ c = amount[idx]
+
+ is_separator = c in (",", ".")
+
+ if not is_separator and c == " ":
+ next_c = amount[idx + 1] if idx + 1 < end else None
+
+ if next_c is None or not next_c.isnumeric():
+ idx += 1
+ break
+
+ is_separator = True
+
+ if not is_separator:
+ return None, None
+
+ if not s1:
+ s1 = c
+ elif s1 and c == s1:
+ pass
+ elif s1 and c != s1 and not s2:
+ s2 = c
+ else:
+ return None, None
+ idx += 1
+
+ num = read_num()
+ if not num:
+ if idx == end:
+ break
+ return None, None
+
+ if s2:
+ s2s.append(num)
+ else:
+ s1s.append(num)
+
+ del num
+
+ if s1 == ' ':
+ s1 = s2
+ s2 = None
+ s0s.extend(s1s)
+ s1s = s2s
+ s2s = []
+
+ if not s1 and not s2:
+ wholes = "".join(s0s)
+ parts = "0"
+ elif s1 and not s2:
+ wholes = "".join(s0s)
+ parts = "".join(s1s)
+ elif s1 and s2:
+ wholes = "".join(s0s) + "".join(s1s)
+ parts = "".join(s2s)
+ else:
+ return None, None
+
+ read_space()
+
+ postfix = read_word()
+
+ # Check that there is no junk left
+ if amount[idx:].strip():
+ return None, None
+
+ # wholes = wholes if wholes else "0"
+ # parts = parts if parts else "0"
+
+ currency = None
+ if len(prefix):
+ currency = prefix
+
+ if len(postfix):
+ if currency:
+ return None, None
+ currency = postfix
+
+ while parts.endswith("0"):
+ parts = parts[:-1]
+
+ return wholes + "." + parts, currency
+
+
+class Money(object):
+ def __init__(self, amount: Decimal, currency: Optional[str] = None):
+ self.amount: Decimal = amount
+ self.currency = currency
+
+ def assert_same_currency(self, other):
+ if isinstance(other, int):
+ return
+
+ if self.currency is None and other.currency is None:
+ return
+ if self.currency == other.currency:
+ return
+
+ raise EeException("Can't relate to instances of Money with different currencies.")
+
+ def __truediv__(self, other: Union["Money", int]):
+ self.assert_same_currency(other)
+
+ if isinstance(other, int):
+ return Money(amount=self.amount.__truediv__(other), currency=self.currency)
+
+ return Money(amount=self.amount.__truediv__(other), currency=self.currency)
+
+ def __mul__(self, other: Union["Money", int]):
+ self.assert_same_currency(other)
+
+ if isinstance(other, int):
+ return Money(amount=self.amount.__mul__(other), currency=self.currency)
+
+ return Money(amount=self.amount.__mul__(other), currency=self.currency)
+
+ def _amount_str(self):
+ amount = str(self.amount)
+
+ (sign, _int, _exp) = self.amount.as_tuple()
+ if _exp >= 0:
+ return amount
+
+ while amount.endswith("0"):
+ amount = amount[0:-1]
+
+ if amount.endswith("."):
+ amount = amount[0:-1]
+
+ return amount
+
+ def __repr__(self):
+ return self._amount_str() + ((" " + self.currency) if self.currency else "")
+
+ def __str__(self):
+ amount = self._amount_str()
+
+ if self.currency is None:
+ return amount
+
+ first, last = (self.currency, amount) if self.currency in _prefix_currencies else (amount, self.currency)
+
+ return first + " " + last
+
+ def __eq__(self, other: "Money"):
+ if not isinstance(other, Money):
+ return False
+
+ return self.amount == other.amount and self.currency == other.currency
+
+
+class MoneyContext(object):
+ def __init__(self, symbols_to_currency: Mapping[str, str], prefix_currencies: Set[str]):
+ self.symbols_to_currency = symbols_to_currency
+ self.prefix_currencies = prefix_currencies
+
+ @staticmethod
+ def parse(amount: Union[str, int, float, Decimal], currency: Optional[str] = None) -> Money:
+ m = MoneyContext.try_parse(amount, currency)
+
+ if m is None:
+ raise EeException("Could not parse money: '{}'".format(amount))
+
+ return m
+
+ @staticmethod
+ def try_parse(amount: Union[str, int, float, Decimal], currency: Optional[str] = None) -> Optional[Money]:
+ if isinstance(amount, str):
+ (a, c) = _parse(amount)
+
+ if a is None:
+ return None
+
+ amount = Decimal(a)
+
+ if c is not None:
+ if currency is not None and currency != c:
+ return None
+
+ currency = c
+ elif isinstance(amount, int) or isinstance(amount, float):
+ amount = Decimal(amount)
+ elif isinstance(amount, Decimal):
+ pass
+ else:
+ raise EeException("Unsupported value type: {}".format(type(amount)))
+
+ if currency is not None:
+ currency = _symbols_to_currency.get(currency, currency)
+
+ return Money(amount, currency)
+
+
+_symbols_to_currency = {
+ "$": "USD"
+}
+
+_prefix_currencies = {
+ "USD",
+}
+
+_default_context = None
+
+
+def get_default_context():
+ global _default_context
+ if _default_context is None:
+ _default_context = MoneyContext(_symbols_to_currency, _prefix_currencies)
+
+ return _default_context
+
+
+def set_default_context(ctx: MoneyContext):
+ global _default_context
+ _default_context = ctx