Merge pull request #8139 from SomberNight/202301_locale_decimal_point
locale amounts: consistently use "." as dec point, and " " as thou sep
This commit is contained in:
@@ -1339,8 +1339,8 @@ class Commands:
|
||||
except InvalidOperation:
|
||||
raise Exception("from_amount is not a number")
|
||||
return {
|
||||
"from_amount": self.daemon.fx.ccy_amount_str(from_amount, False, from_ccy),
|
||||
"to_amount": self.daemon.fx.ccy_amount_str(to_amount, False, to_ccy),
|
||||
"from_amount": self.daemon.fx.ccy_amount_str(from_amount, add_thousands_sep=False, ccy=from_ccy),
|
||||
"to_amount": self.daemon.fx.ccy_amount_str(to_amount, add_thousands_sep=False, ccy=to_ccy),
|
||||
"from_ccy": from_ccy,
|
||||
"to_ccy": to_ccy,
|
||||
"source": self.daemon.fx.exchange.name(),
|
||||
|
||||
@@ -556,17 +556,24 @@ class FxThread(ThreadJob, EventListener):
|
||||
return d.get(ccy, [])
|
||||
|
||||
@staticmethod
|
||||
def remove_thousands_separator(text):
|
||||
return text.replace(',', '') # FIXME use THOUSAND_SEPARATOR in util
|
||||
def remove_thousands_separator(text: str) -> str:
|
||||
return text.replace(util.THOUSANDS_SEP, "")
|
||||
|
||||
def ccy_amount_str(self, amount, commas, ccy=None):
|
||||
def ccy_amount_str(self, amount, *, add_thousands_sep: bool = False, ccy=None) -> str:
|
||||
prec = CCY_PRECISIONS.get(self.ccy if ccy is None else ccy, 2)
|
||||
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec)) # FIXME use util.THOUSAND_SEPARATOR and util.DECIMAL_POINT
|
||||
fmt_str = "{:%s.%df}" % ("," if add_thousands_sep else "", max(0, prec))
|
||||
try:
|
||||
rounded_amount = round(amount, prec)
|
||||
except decimal.InvalidOperation:
|
||||
rounded_amount = amount
|
||||
return fmt_str.format(rounded_amount)
|
||||
text = fmt_str.format(rounded_amount)
|
||||
# replace "," -> THOUSANDS_SEP
|
||||
# replace "." -> DECIMAL_POINT
|
||||
dp_loc = text.find(".")
|
||||
text = text.replace(",", util.THOUSANDS_SEP)
|
||||
if dp_loc == -1:
|
||||
return text
|
||||
return text[:dp_loc] + util.DECIMAL_POINT + text[dp_loc+1:]
|
||||
|
||||
async def run(self):
|
||||
while True:
|
||||
@@ -683,7 +690,7 @@ class FxThread(ThreadJob, EventListener):
|
||||
def format_fiat(self, value: Decimal) -> str:
|
||||
if value.is_nan():
|
||||
return _("No data")
|
||||
return "%s" % (self.ccy_amount_str(value, True))
|
||||
return self.ccy_amount_str(value, add_thousands_sep=True)
|
||||
|
||||
def history_rate(self, d_t: Optional[datetime]) -> Decimal:
|
||||
if d_t is None:
|
||||
|
||||
@@ -108,7 +108,7 @@ class QEFX(QObject, QtEventListener):
|
||||
except:
|
||||
return ''
|
||||
if plain:
|
||||
return self.fx.ccy_amount_str(self.fx.fiat_value(satoshis, rate), False)
|
||||
return self.fx.ccy_amount_str(self.fx.fiat_value(satoshis, rate), add_thousands_sep=False)
|
||||
else:
|
||||
return self.fx.value_str(satoshis, rate)
|
||||
|
||||
@@ -133,7 +133,7 @@ class QEFX(QObject, QtEventListener):
|
||||
return ''
|
||||
dt = datetime.fromtimestamp(int(td))
|
||||
if plain:
|
||||
return self.fx.ccy_amount_str(self.fx.historical_value(satoshis, dt), False)
|
||||
return self.fx.ccy_amount_str(self.fx.historical_value(satoshis, dt), add_thousands_sep=False)
|
||||
else:
|
||||
return self.fx.historical_value_str(satoshis, dt)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy)
|
||||
from .util import char_width_in_lineedit, ColorScheme
|
||||
|
||||
from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_name,
|
||||
FEERATE_PRECISION, quantize_feerate)
|
||||
FEERATE_PRECISION, quantize_feerate, DECIMAL_POINT)
|
||||
from electrum.bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
|
||||
|
||||
|
||||
@@ -66,13 +66,13 @@ class AmountEdit(SizedFreezableLineEdit):
|
||||
return
|
||||
pos = self.cursorPosition()
|
||||
chars = '0123456789'
|
||||
if not self.is_int: chars +='.'
|
||||
if not self.is_int: chars += DECIMAL_POINT
|
||||
s = ''.join([i for i in text if i in chars])
|
||||
if not self.is_int:
|
||||
if '.' in s:
|
||||
p = s.find('.')
|
||||
s = s.replace('.','')
|
||||
s = s[:p] + '.' + s[p:p+self.max_precision()]
|
||||
if DECIMAL_POINT in s:
|
||||
p = s.find(DECIMAL_POINT)
|
||||
s = s.replace(DECIMAL_POINT, '')
|
||||
s = s[:p] + DECIMAL_POINT + s[p:p+self.max_precision()]
|
||||
if self.max_amount:
|
||||
if (amt := self._get_amount_from_text(s)) and amt >= self.max_amount:
|
||||
s = self._get_text_from_amount(self.max_amount)
|
||||
@@ -95,6 +95,7 @@ class AmountEdit(SizedFreezableLineEdit):
|
||||
|
||||
def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]:
|
||||
try:
|
||||
text = text.replace(DECIMAL_POINT, '.')
|
||||
return (int if self.is_int else Decimal)(text)
|
||||
except:
|
||||
return None
|
||||
@@ -127,6 +128,7 @@ class BTCAmountEdit(AmountEdit):
|
||||
def _get_amount_from_text(self, text):
|
||||
# returns amt in satoshis
|
||||
try:
|
||||
text = text.replace(DECIMAL_POINT, '.')
|
||||
x = Decimal(text)
|
||||
except:
|
||||
return None
|
||||
@@ -141,7 +143,9 @@ class BTCAmountEdit(AmountEdit):
|
||||
return Decimal(amount) if not self.is_int else int(amount)
|
||||
|
||||
def _get_text_from_amount(self, amount_sat):
|
||||
return format_satoshis_plain(amount_sat, decimal_point=self.decimal_point())
|
||||
text = format_satoshis_plain(amount_sat, decimal_point=self.decimal_point())
|
||||
text = text.replace('.', DECIMAL_POINT)
|
||||
return text
|
||||
|
||||
def setAmount(self, amount_sat):
|
||||
if amount_sat is None:
|
||||
|
||||
@@ -926,7 +926,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
else:
|
||||
fiat_e.follows = True
|
||||
fiat_e.setText(self.fx.ccy_amount_str(
|
||||
amount * Decimal(rate) / COIN, False))
|
||||
amount * Decimal(rate) / COIN, add_thousands_sep=False))
|
||||
fiat_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
|
||||
fiat_e.follows = False
|
||||
|
||||
|
||||
@@ -133,19 +133,19 @@ class TestFiat(ElectrumTestCase):
|
||||
self.fx = FakeFxThread(FakeExchange(Decimal('1000.001')))
|
||||
default_fiat = Abstract_Wallet.default_fiat_value(self.wallet, txid, self.fx, self.value_sat)
|
||||
self.assertEqual(Decimal('1000.001'), default_fiat)
|
||||
self.assertEqual('1,000.00', self.fx.ccy_amount_str(default_fiat, commas=True))
|
||||
self.assertEqual('1 000.00', self.fx.ccy_amount_str(default_fiat, add_thousands_sep=True))
|
||||
|
||||
def test_save_fiat_and_reset(self):
|
||||
self.assertEqual(False, Abstract_Wallet.set_fiat_value(self.wallet, txid, ccy, '1000.01', self.fx, self.value_sat))
|
||||
saved = self.fiat_value[ccy][txid]
|
||||
self.assertEqual('1,000.01', self.fx.ccy_amount_str(Decimal(saved), commas=True))
|
||||
self.assertEqual('1 000.01', self.fx.ccy_amount_str(Decimal(saved), add_thousands_sep=True))
|
||||
self.assertEqual(True, Abstract_Wallet.set_fiat_value(self.wallet, txid, ccy, '', self.fx, self.value_sat))
|
||||
self.assertNotIn(txid, self.fiat_value[ccy])
|
||||
# even though we are not setting it to the exact fiat value according to the exchange rate, precision is truncated away
|
||||
self.assertEqual(True, Abstract_Wallet.set_fiat_value(self.wallet, txid, ccy, '1,000.002', self.fx, self.value_sat))
|
||||
self.assertEqual(True, Abstract_Wallet.set_fiat_value(self.wallet, txid, ccy, '1 000.002', self.fx, self.value_sat))
|
||||
|
||||
def test_too_high_precision_value_resets_with_no_saved_value(self):
|
||||
self.assertEqual(True, Abstract_Wallet.set_fiat_value(self.wallet, txid, ccy, '1,000.001', self.fx, self.value_sat))
|
||||
self.assertEqual(True, Abstract_Wallet.set_fiat_value(self.wallet, txid, ccy, '1 000.001', self.fx, self.value_sat))
|
||||
|
||||
def test_empty_resets(self):
|
||||
self.assertEqual(True, Abstract_Wallet.set_fiat_value(self.wallet, txid, ccy, '', self.fx, self.value_sat))
|
||||
|
||||
@@ -33,7 +33,7 @@ import urllib
|
||||
import threading
|
||||
import hmac
|
||||
import stat
|
||||
from locale import localeconv
|
||||
import locale
|
||||
import asyncio
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import builtins
|
||||
@@ -698,7 +698,11 @@ def format_satoshis_plain(
|
||||
# We enforce that we have at least that available.
|
||||
assert decimal.getcontext().prec >= 28, f"PyDecimal precision too low: {decimal.getcontext().prec}"
|
||||
|
||||
DECIMAL_POINT = localeconv()['decimal_point'] # type: str
|
||||
# DECIMAL_POINT = locale.localeconv()['decimal_point'] # type: str
|
||||
DECIMAL_POINT = "."
|
||||
THOUSANDS_SEP = " "
|
||||
assert len(DECIMAL_POINT) == 1, f"DECIMAL_POINT has unexpected len. {DECIMAL_POINT!r}"
|
||||
assert len(THOUSANDS_SEP) == 1, f"THOUSANDS_SEP has unexpected len. {THOUSANDS_SEP!r}"
|
||||
|
||||
|
||||
def format_satoshis(
|
||||
@@ -737,9 +741,9 @@ def format_satoshis(
|
||||
sign = integer_part[0] if integer_part[0] in ("+", "-") else ""
|
||||
if sign == "-":
|
||||
integer_part = integer_part[1:]
|
||||
integer_part = "{:,}".format(int(integer_part)).replace(',', " ")
|
||||
integer_part = "{:,}".format(int(integer_part)).replace(',', THOUSANDS_SEP)
|
||||
integer_part = sign + integer_part
|
||||
fract_part = " ".join(fract_part[i:i+3] for i in range(0, len(fract_part), 3))
|
||||
fract_part = THOUSANDS_SEP.join(fract_part[i:i+3] for i in range(0, len(fract_part), 3))
|
||||
result = integer_part + DECIMAL_POINT + fract_part
|
||||
# add leading/trailing whitespaces so that numbers can be aligned in a column
|
||||
if whitespaces:
|
||||
|
||||
@@ -612,13 +612,13 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
# and not util, also have fx remove it
|
||||
text = fx.remove_thousands_separator(text)
|
||||
def_fiat = self.default_fiat_value(txid, fx, value_sat)
|
||||
formatted = fx.ccy_amount_str(def_fiat, commas=False)
|
||||
formatted = fx.ccy_amount_str(def_fiat, add_thousands_sep=False)
|
||||
def_fiat_rounded = Decimal(formatted)
|
||||
reset = not text
|
||||
if not reset:
|
||||
try:
|
||||
text_dec = Decimal(text)
|
||||
text_dec_rounded = Decimal(fx.ccy_amount_str(text_dec, commas=False))
|
||||
text_dec_rounded = Decimal(fx.ccy_amount_str(text_dec, add_thousands_sep=False))
|
||||
reset = text_dec_rounded == def_fiat_rounded
|
||||
except:
|
||||
# garbage. not resetting, but not saving either
|
||||
|
||||
Reference in New Issue
Block a user