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 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): class TestCurrencyFormat(unittest.TestCase):

View file

@ -17,7 +17,8 @@ import jinja2
from matemat import __version__ as matemat_version from matemat import __version__ as matemat_version
from matemat.exceptions import HttpException from matemat.exceptions import HttpException
from matemat.webserver import RequestArguments, PageletResponse, RedirectResponse, TemplateResponse 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 # 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.db import MatematDatabase
from matemat.primitives import Product from matemat.primitives import Product
from matemat.exceptions import DatabaseConsistencyError, HttpException from matemat.exceptions import DatabaseConsistencyError, HttpException
from matemat.webserver.util import parse_chf from matemat.util.currency_format import parse_chf
@pagelet('/modproduct') @pagelet('/modproduct')

View file

@ -8,7 +8,7 @@ from matemat.webserver import pagelet, RequestArguments, PageletResponse, Redire
from matemat.db import MatematDatabase from matemat.db import MatematDatabase
from matemat.primitives import User from matemat.primitives import User
from matemat.exceptions import DatabaseConsistencyError, HttpException from matemat.exceptions import DatabaseConsistencyError, HttpException
from matemat.webserver.util import parse_chf from matemat.util.currency_format import parse_chf
@pagelet('/moduser') @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}') raise ValueError(f'Unsupported Content-Type: {enctype}')
# Return the path and the parsed arguments # Return the path and the parsed arguments
return tokens.path, args 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