Moved currency formatting/parsing into generic util package

This commit is contained in:
s3lph 2018-07-20 15:30:58 +02:00
parent a4930d268d
commit b1a56f59fc
9 changed files with 62 additions and 61 deletions

2
doc

@ -1 +1 @@
Subproject commit 92e168a288b396a7a97d200b80affdf8690bd03d
Subproject commit 5335524d3e57c7551f31c7e21fc04c464b23429a

0
matemat/util/__init__.py Normal file
View file

View file

@ -0,0 +1,56 @@
def format_chf(value: int, with_currencysign: bool = True) -> str:
"""
Formats a centime value into a commonly understood representation ("CHF -13.37").
:param value: The value to format, in centimes.
:param with_currencysign: Whether to include the currency prefix ("CHF ") in the output.
:return: A human-readable string representation.
"""
sign: str = ''
if value < 0:
# As // and % round towards -Inf, convert into a positive value and prepend the negative sign
sign = '-'
value = -value
# Split into full francs and fractions (centimes)
full: int = value // 100
frac: int = value % 100
csign: str = 'CHF ' if with_currencysign else ''
# Put it all together; centimes are always padded with 2 zeros
return f'{csign}{sign}{full}.{frac:02}'
def parse_chf(value: str) -> int:
"""
Parse a currency value into machine-readable format (integer centimes). The prefix "CHF", the decimal point, and
digits after the decimal point are optional.
:param value: The value to parse.
:return: An integer representation of the value.
:raises: Value error: If more than two digits after the decimal point are present.
"""
# Remove optional leading "CHF" and strip whitespace
value = value.strip()
if value.startswith('CHF'):
value = value[3:]
value = value.strip()
if '.' not in value:
# already is an integer; parse and turn into centimes
return int(value, 10) * 100
# Split at the decimal point
full, frac = value.split('.', 1)
if len(frac) > 2:
raise ValueError('Needs max. 2 digits after decimal point')
elif len(frac) < 2:
# Right-pad fraction with zeros ("x." -> "x.00", "x.x" -> "x.x0")
frac = frac + '0' * (2 - len(frac))
# Parse both parts
ifrac: int = int(frac, 10)
ifull: int = int(full, 10)
if ifrac < 0:
raise ValueError('Fraction part must not be negative.')
if full.startswith('-'):
ifrac = -ifrac
# Combine into centime integer
return ifull * 100 + ifrac

View file

View file

@ -1,7 +1,7 @@
import unittest
from matemat.webserver.util import format_chf, parse_chf
from matemat.util.currency_format import format_chf, parse_chf
class TestCurrencyFormat(unittest.TestCase):

View file

@ -17,7 +17,8 @@ import jinja2
from matemat import __version__ as matemat_version
from matemat.exceptions import HttpException
from matemat.webserver import RequestArguments, PageletResponse, RedirectResponse, TemplateResponse
from matemat.webserver.util import parse_args, format_chf
from matemat.webserver.util import parse_args
from matemat.util.currency_format import format_chf
#
# Python internal class hacks

View file

@ -8,7 +8,7 @@ from matemat.webserver import pagelet, RequestArguments, PageletResponse, Redire
from matemat.db import MatematDatabase
from matemat.primitives import Product
from matemat.exceptions import DatabaseConsistencyError, HttpException
from matemat.webserver.util import parse_chf
from matemat.util.currency_format import parse_chf
@pagelet('/modproduct')

View file

@ -8,7 +8,7 @@ from matemat.webserver import pagelet, RequestArguments, PageletResponse, Redire
from matemat.db import MatematDatabase
from matemat.primitives import User
from matemat.exceptions import DatabaseConsistencyError, HttpException
from matemat.webserver.util import parse_chf
from matemat.util.currency_format import parse_chf
@pagelet('/moduser')

View file

@ -136,59 +136,3 @@ def parse_args(request: str, postbody: Optional[bytes] = None, enctype: str = 't
raise ValueError(f'Unsupported Content-Type: {enctype}')
# Return the path and the parsed arguments
return tokens.path, args
def format_chf(value: int, with_currencysign: bool = True) -> str:
"""
Formats a centime value into a commonly understood representation ("CHF -13.37").
:param value: The value to format, in centimes.
:param with_currencysign: Whether to include the currency prefix ("CHF ") in the output.
:return: A human-readable string representation.
"""
sign: str = ''
if value < 0:
# As // and % round towards -Inf, convert into a positive value and prepend the negative sign
sign = '-'
value = -value
# Split into full francs and fractions (centimes)
full: int = value // 100
frac: int = value % 100
csign: str = 'CHF ' if with_currencysign else ''
# Put it all together; centimes are always padded with 2 zeros
return f'{csign}{sign}{full}.{frac:02}'
def parse_chf(value: str) -> int:
"""
Parse a currency value into machine-readable format (integer centimes). The prefix "CHF", the decimal point, and
digits after the decimal point are optional.
:param value: The value to parse.
:return: An integer representation of the value.
:raises: Value error: If more than two digits after the decimal point are present.
"""
# Remove optional leading "CHF" and strip whitespace
value = value.strip()
if value.startswith('CHF'):
value = value[3:]
value = value.strip()
if '.' not in value:
# already is an integer; parse and turn into centimes
return int(value, 10) * 100
# Split at the decimal point
full, frac = value.split('.', 1)
if len(frac) > 2:
raise ValueError('Needs max. 2 digits after decimal point')
elif len(frac) < 2:
# Right-pad fraction with zeros ("x." -> "x.00", "x.x" -> "x.x0")
frac = frac + '0' * (2 - len(frac))
# Parse both parts
ifrac: int = int(frac, 10)
ifull: int = int(full, 10)
if ifrac < 0:
raise ValueError('Fraction part must not be negative.')
if full.startswith('-'):
ifrac = -ifrac
# Combine into centime integer
return ifull * 100 + ifrac