A Python library for date-aware currency operations

Updated 2025-08-15: v2.x with DM factory, robust fallback for historical rates, and SQLite/PostgreSQL serialization helpers.

dated-money represents monetary values with an optional date and performs conversions using historical exchange rates with automatic fallback.

https://github.com/juanre/dated-money

xkcd Alternate Currency

Installation

uv add dated-money

or

pip install dated-money

Quickstart

from dated_money import DM, DatedMoney, Currency

# Factory: default currency/date
Eur = DM('EUR', '2024-01-01')

# Create amounts
price = Eur(100)                 # €100.00
from_usd = Eur(50, '$')          # $50 → EUR (on 2024-01-01)

# Arithmetic: result in the second operand's currency/date
a = DatedMoney(100, 'EUR', '2024-01-01')
b = DatedMoney(50, 'USD', '2024-01-01')
res = a + b                      # result in USD on 2024-01-01

# Convert and format
print(res.to('EUR'))             # €...
print(str(res))                  # e.g. $... (rounded to cents)
print(repr(res))                 # '2024-01-01 USD ...' (parseable)

# Parse from repr
parsed = DatedMoney.parse('2024-01-01 EUR 100.50')
assert parsed == DatedMoney(100.50, 'EUR', '2024-01-01')

Notes:

  • Amounts accept strings with trailing ‘c’ for cents, e.g. DatedMoney('1234c', 'EUR').
  • Money is kept as an alias to DM for backwards compatibility.

Exchange rates and caching

Sources (in order):

  1. SQLite cache (auto-created)
  2. Local git repo DMON_RATES_REPO (expects money/yyyy-mm-dd-rates.json)
  3. Supabase (SUPABASE_URL, SUPABASE_KEY)
  4. exchangerate-api.com (DMON_EXCHANGERATE_API_KEY)

Fallback: if a date is missing, it searches up to 10 days back and logs the date used. Once fetched, rates are cached locally.

Rate file format (yyyy-mm-dd-rates.json):

{"conversion_rates": {"USD": 1, "EUR": 0.85, "GBP": 0.73}}

Default cache locations:

  • macOS: ~/Library/Caches/dated_money/exchange-rates.db
  • Linux: ~/.cache/dated_money/exchange-rates.db
  • Windows: %LOCALAPPDATA%/dated_money/cache/exchange-rates.db
  • Override with DMON_RATES_CACHE

CLI

# Create cache table
dmon-rates --create-table

# Fill cache from local repo
dmon-rates -C --update-cache

# Download historical rates (paid API key required)
dmon-rates --fetch-rates 2023-01-01:2023-12-31

# Inspect rates on a date
dmon-rates --rate-on 2024-01-15
dmon-rates --rate-on 2024-01-15 --currency EUR

Environment variables:

  • DMON_RATES_CACHE, DMON_RATES_REPO, SUPABASE_URL, SUPABASE_KEY, DMON_EXCHANGERATE_API_KEY

Database serialization

SQLite (automatic adapters):

import sqlite3
from dated_money import DatedMoney
from dated_money.db_serialization import register_sqlite_converters

register_sqlite_converters()
conn = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
cur = conn.cursor()
cur.execute('CREATE TABLE t (amount DATEDMONEY)')
cur.execute('INSERT INTO t (amount) VALUES (?)', (DatedMoney(100.50, 'EUR', '2024-01-01'),))
retrieved = cur.execute('SELECT amount FROM t').fetchone()[0]
assert isinstance(retrieved, DatedMoney)

PostgreSQL (helpers):

from dated_money import DatedMoney
from dated_money.db_serialization import to_postgres, from_postgres

value = to_postgres(DatedMoney(100.50, 'EUR', '2024-01-01'))
restored = from_postgres(value)

API reference

Exported symbols

  • Currency — ISO 4217 currency enum (e.g., Currency.EUR, Currency.USD)
  • DM — factory for creating currency/date-specific constructors
  • Money — alias of DM (backwards compatibility)
  • DatedMoney — monetary value with optional date
  • register_sqlite_converters — SQLite adapter/convertor registration

DM

DM(base_currency, base_date: str | date | None = None) -> (amount, currency=None, on_date=None) -> DatedMoney
  • base_currency: accepts ISO code (e.g. 'EUR'), symbol ('€', '$', '£', '¥'), or Currency enum.
  • base_date: optional default date ('YYYY-MM-DD' or date).
  • Returns a function that:
    • When called as Factory(amount), creates DatedMoney(amount, base_currency, base_date).
    • When called as Factory(amount, other_currency, on_date), creates then converts to base_currency.

Examples:

Eur = DM('EUR', '2024-01-01')
Eur(50)                 # €50.00 on 2024-01-01
Eur(50, '$')            # $50 → €… on 2024-01-01
Usd = DM('$')
Usd(20, 'EUR', '2024-02-10')  # €20 → $… on 2024-02-10

DatedMoney

DatedMoney(amount, currency, on_date: str | date | None = None)

Arguments:

  • amount: int | float | Decimal | str. A trailing 'c' means cents (e.g. '1234c').
  • currency: ISO code, symbol, or Currency enum.
  • on_date: 'YYYY-MM-DD' or date, optional.

Attributes:

  • currency: Currency
  • on_date: date | None
  • precision: int (class var) — cents-level tolerance for equality (default 0).

Methods:

  • cents(in_currency: str|Currency|None = None, on_date: str|date|None = None) -> Decimal
    • Converts to cents in target currency/date (defaults to instance).
  • amount(currency: str|Currency|None = None, rounding: bool = False) -> Decimal
    • Major units (e.g., euros). Rounds to cents if rounding=True.
  • to(currency, on_date: str|date|None = None) -> DatedMoney
    • New instance in currency (date preserved unless overridden).
  • on(on_date: str) -> DatedMoney
    • New instance with a different date.
  • parse(string: str) -> DatedMoney (classmethod)
    • Accepts 'YYYY-MM-DD CODE AMOUNT' or 'CODE AMOUNT'.

Operators:

  • + and - between money: both sides normalize to the second operand’s currency/date.
  • * and scalar /: scale amount, preserve currency/date.
  • / money-to-money: returns Decimal ratio.
  • Comparisons (==, !=, <, <=, >, >=) normalize to the second operand’s currency/date. == uses precision.

Display/serialization:

  • str(m): symbol + amount rounded to cents (e.g., €100.00).
  • repr(m): parseable: 'YYYY-MM-DD CODE 100.00' or 'CODE 100.00'.
  • SQLite: __conform__ returns repr() for storage (see SQLite section).

Errors:

  • ValueError for invalid parse formats or invalid currency codes.
  • TypeError for wrong argument types.
  • RuntimeError when exchange rates are unavailable for requested date/currencies.

License

MIT