Skip to content

Commit

Permalink
Added support for territory currency lookups.
Browse files Browse the repository at this point in the history
The main usecase of this is to figure out at what point in time did
a country use a certain currency.  The default behavior is to use
the current date.

This fixes python-babel#42
  • Loading branch information
mitsuhiko committed Jul 30, 2013
1 parent eead4a3 commit b409813
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Version 2.0

(release date to be decided, codename to be selected)

- Added support for looking up currencies that belong to a territory
through the :func:`babel.numbers.get_territory_currencies`
function.

Version 1.4
-----------

Expand Down
88 changes: 87 additions & 1 deletion babel/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
from decimal import Decimal, InvalidOperation
import math
import re
from datetime import date as date_, datetime as datetime_

from babel.core import default_locale, Locale
from babel.core import default_locale, Locale, get_global
from babel._compat import range_type


Expand Down Expand Up @@ -63,6 +64,91 @@ def get_currency_symbol(currency, locale=LC_NUMERIC):
return Locale.parse(locale).currency_symbols.get(currency, currency)


def get_territory_currencies(territory, start_date=None, end_date=None,
tender=True, non_tender=False,
include_details=False):
"""Returns the list of currencies for the given territory that are valid at
the given date range. In addition to that the currency database
distinguishes between tender and non-tender currencies. By default only
tender currencies are returned.
The return value is a list of all currencies roughly ordered by the time
of when the currency became active. The longer the currency is being in
use the more to the left of the list it will be.
The start date defaults to today. If no end date is given it will be the
same as the start date. Otherwise a range can be defined. For instance
this can be used to find the currencies in use in Austria between 1995 and
2011:
>>> from datetime import date
>>> get_territory_currencies('AT', date(1995, 1, 1), date(2011, 1, 1))
['ATS', 'EUR']
Likewise it's also possible to find all the currencies in use on a
single date:
>>> get_territory_currencies('AT', date(1995, 1, 1))
['ATS']
>>> get_territory_currencies('AT', date(2011, 1, 1))
['EUR']
By default the return value only includes tender currencies. This
however can be changed:
>>> get_territory_currencies('US')
['USD']
>>> get_territory_currencies('US', tender=False, non_tender=True)
['USN', 'USS']
.. versionadded:: 2.0
:param territory: the name of the territory to find the currency fo
:param start_date: the start date. If not given today is assumed.
:param end_date: the end date. If not given the start date is assumed.
:param tender: controls whether tender currencies should be included.
:param non_tender: controls whether non-tender currencies should be
included.
:param include_details: if set to `True`, instead of returning currency
codes the return value will be dictionaries
with detail information. In that case each
dictionary will have the keys ``'currency'``,
``'from'``, ``'to'``, and ``'tender'``.
"""
currencies = get_global('territory_currencies')
if start_date is None:
start_date = date_.today()
elif isinstance(start_date, datetime_):
start_date = start_date.date()
if end_date is None:
end_date = start_date
elif isinstance(end_date, datetime_):
end_date = end_date.date()

curs = currencies.get(territory.upper(), ())
# TODO: validate that the territory exists

def _is_active(start, end):
return (start is None or start <= end_date) and \
(end is None or end >= start_date)

result = []
for currency_code, start, end, is_tender in curs:
if ((is_tender and tender) or \
(not is_tender and non_tender)) and _is_active(start, end):
if include_details:
result.append({
'currency': currency_code,
'from': start,
'to': end,
'tender': is_tender,
})
else:
result.append(currency_code)

return result


def get_decimal_symbol(locale=LC_NUMERIC):
"""Return the symbol used by the locale to separate decimal fractions.
Expand Down
2 changes: 2 additions & 0 deletions docs/api/numbers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ Data Access
.. autofunction:: get_plus_sign_symbol

.. autofunction:: get_minus_sign_symbol

.. autofunction:: get_territory_currencies
28 changes: 28 additions & 0 deletions scripts/import_cldr.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
except ImportError:
from xml.etree import ElementTree

from datetime import date

# Make sure we're using Babel source, and not some previously installed version
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), '..'))

Expand Down Expand Up @@ -95,6 +97,18 @@ def _translate_alias(ctxt, path):
return keys


def _parse_currency_date(s):
if not s:
return None
parts = s.split('-', 2)
return date(*map(int, parts + [1] * (3 - len(parts))))


def _currency_sort_key(tup):
code, start, end, tender = tup
return int(not tender), start or date(1, 1, 1)


def main():
parser = OptionParser(usage='%prog path/to/cldr')
options, args = parser.parse_args()
Expand Down Expand Up @@ -128,6 +142,7 @@ def main():
script_aliases = global_data.setdefault('script_aliases', {})
variant_aliases = global_data.setdefault('variant_aliases', {})
likely_subtags = global_data.setdefault('likely_subtags', {})
territory_currencies = global_data.setdefault('territory_currencies', {})

# create auxiliary zone->territory map from the windows zones (we don't set
# the 'zones_territories' map directly here, because there are some zones
Expand Down Expand Up @@ -186,6 +201,19 @@ def main():
for likely_subtag in sup_likely.findall('.//likelySubtags/likelySubtag'):
likely_subtags[likely_subtag.attrib['from']] = likely_subtag.attrib['to']

# Currencies in territories
for region in sup.findall('.//currencyData/region'):
region_code = region.attrib['iso3166']
region_currencies = []
for currency in region.findall('./currency'):
cur_start = _parse_currency_date(currency.attrib.get('from'))
cur_end = _parse_currency_date(currency.attrib.get('to'))
region_currencies.append((currency.attrib['iso4217'],
cur_start, cur_end,
currency.attrib.get('tender', 'true') == 'true'))
region_currencies.sort(key=_currency_sort_key)
territory_currencies[region_code] = region_currencies

outfile = open(global_path, 'wb')
try:
pickle.dump(global_data, outfile, 2)
Expand Down
23 changes: 23 additions & 0 deletions tests/test_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import unittest
import pytest

from datetime import date

from babel import numbers


Expand Down Expand Up @@ -180,6 +182,27 @@ def test_get_currency_symbol():
assert numbers.get_currency_symbol('USD', 'en_US') == u'$'


def test_get_territory_currencies():
assert numbers.get_territory_currencies('AT', date(1995, 1, 1)) == ['ATS']
assert numbers.get_territory_currencies('AT', date(2011, 1, 1)) == ['EUR']

assert numbers.get_territory_currencies('US', date(2013, 1, 1)) == ['USD']
assert sorted(numbers.get_territory_currencies('US', date(2013, 1, 1),
non_tender=True)) == ['USD', 'USN', 'USS']

assert numbers.get_territory_currencies('US', date(2013, 1, 1),
include_details=True) == [{
'currency': 'USD',
'from': date(1792, 1, 1),
'to': None,
'tender': True
}]

assert numbers.get_territory_currencies('LS', date(2013, 1, 1)) == ['ZAR', 'LSL']

assert numbers.get_territory_currencies('QO', date(2013, 1, 1)) == []


def test_get_decimal_symbol():
assert numbers.get_decimal_symbol('en_US') == u'.'

Expand Down

0 comments on commit b409813

Please sign in to comment.