67 lines
1.7 KiB
Python
67 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
|
|
def _group_thousands(value: str) -> str:
|
|
parts = []
|
|
while value:
|
|
parts.append(value[-3:])
|
|
value = value[:-3]
|
|
return ".".join(reversed(parts))
|
|
|
|
|
|
def number_de(value: Any, decimals: int = 1) -> str:
|
|
if value is None:
|
|
return "-"
|
|
try:
|
|
numeric = float(value)
|
|
except (TypeError, ValueError):
|
|
return "-"
|
|
|
|
sign = "-" if numeric < 0 else ""
|
|
numeric = abs(numeric)
|
|
raw = f"{numeric:.{decimals}f}"
|
|
integer, _, fraction = raw.partition(".")
|
|
grouped = _group_thousands(integer)
|
|
return f"{sign}{grouped},{fraction}" if decimals else f"{sign}{grouped}"
|
|
|
|
|
|
def currency_de(value: Any, currency: str = "EUR") -> str:
|
|
symbol = {"EUR": "€", "USD": "$", "GBP": "£"}.get(currency, currency)
|
|
return f"{number_de(value, 2)} {symbol}"
|
|
|
|
|
|
def percent_de(value: Any, decimals: int = 1) -> str:
|
|
return f"{number_de(value, decimals)} %"
|
|
|
|
|
|
def delta_de(value: Any, decimals: int = 1, suffix: str = "%") -> str:
|
|
if value is None:
|
|
return "-"
|
|
numeric = float(value)
|
|
prefix = "+" if numeric > 0 else ""
|
|
return f"{prefix}{number_de(numeric, decimals)} {suffix}".strip()
|
|
|
|
|
|
def date_de(value: Any) -> str:
|
|
if not value:
|
|
return "-"
|
|
if isinstance(value, datetime):
|
|
dt = value
|
|
else:
|
|
try:
|
|
dt = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
except ValueError:
|
|
return str(value)
|
|
return dt.strftime("%d.%m.%Y")
|
|
|
|
|
|
def clamp(value: Any, lower: float = 0, upper: float = 100) -> float:
|
|
try:
|
|
number = float(value)
|
|
except (TypeError, ValueError):
|
|
return lower
|
|
return max(lower, min(upper, number))
|