Skip to main content

vn-money

Vietnamese money on integers, because VND has no minor unit. Format, parse, apportion without losing a single dong, and read an amount out in Vietnamese words. Standard library only, zero runtime dependencies.

pip install vn-money
from vn_money import format_vnd, parse_vnd, allocate, to_words, is_vnd

format_vnd(12345678)            # '12.345.678 ₫'  (U+00A0, then U+20AB)
parse_vnd("12.345.678")         # 12345678
parse_vnd("1.5")                # VndError, reason 'has-decimal'
allocate(1_000_000, [1, 1, 1])  # [333334, 333333, 333333]  — sums back exactly
to_words(15000)                 # 'mười lăm nghìn'
is_vnd(0.1 + 0.2)               # False

The TypeScript port is @vntoolkit/vn-money and the two are held to the same conformance suite — 1232 cases, read by both. That suite is the contract; this package is one implementation of it.

Why integers

ISO 4217 gives VND a minor unit of 0. There is no half a dong, and a price that arrives with a decimal point is either an error or a different currency.

So a fractional amount is refused, not rounded:

parse_vnd("1.5")     # VndError('has-decimal')
is_vnd(1.5)          # False

A money parser that guesses is worse than one that fails: under a dot-as-separator reading "1.5" is either fifteen hundred or a typo, and the two are not interchangeable on an invoice.

The default format matches ICU byte for byte, and every formatting case in the suite was read out of Intl.NumberFormat('vi-VN', {style: 'currency', currency: 'VND'}) rather than written by hand. vi-VN groups thousands with a dot and separates decimals with a comma — the same two characters as en-US with the opposite meaning, which is the single easiest way to produce a wrong number that still looks fine.

Errors carry a reason

VndError subclasses ValueError, and .reason is a closed set of strings rather than a message, because the conformance suite compares it across two languages and a caller needs to branch on it:

try:
    parse_vnd(user_input)
except VndError as exc:
    if exc.reason == "has-decimal":
        form.errors["amount"] = "VND không có phần thập phân"

empty · has-decimal · unexpected-character · out-of-range · not-integer · empty-shares · invalid-shares

Reading an amount out

to_words follows one stated convention among several published ones, and pins it case by case in the suite. mốt and tư need a tens digit of at least 2, so 11, 14 and 15 are where a careless implementation shows up:

to_words(15)       # 'mười lăm'
to_words(14)       # 'mười bốn'
to_words(105)      # 'một trăm linh năm'
to_words(1001)     # 'một nghìn không trăm linh một'
to_words(-1)       # 'âm một'

If your house style differs, that is an issue to open, not a case to edit.

Validation

pytest packages/vn-money-py/tests -q
python packages/vn-money-py/tests/test_vn_money.py    # needs nothing installed

The parity check runs both real ports over 17091 operations and compares the answers directly, which covers the inputs nobody thought to write a case for:

node scripts/verify-money-parity.mjs
# money parity: 17091/17091 operations identical across both ports

Notes for this port

bool is rejected wherever an int is expected, even though it is a subclass: True is not an amount, and accepting it lets a form's checkbox through as a price. format_vnd takes keyword arguments rather than an options object, because that is how the language already does optional arguments.

SYMBOL_SPACER is chr(0x00A0) and CURRENCY_SYMBOL is the literal ₫ (U+20AB). The spacer is built from a code point on purpose — a literal no-break space is invisible in an editor, so it survives review and is then silently replaced by a reformat, and the formatter stops matching ICU by one code point.

Licence

MIT

Metadata

Release files for vn-money 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vn-money 0.1.0
File Size Uploaded
vn_money-0.1.0.tar.gz 13.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for vn-money 0.1.0
File Interpreter ABI Platform
vn_money-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 25.1 kB

Release files / vn_money-0.1.0.tar.gz

Download URL vn_money-0.1.0.tar.gz
Size 13.3 kB
Tags Source
SHA-256 checksum
How to use checksums
2d50615ef27e279a8fd5a0b4594c7d269bbecf8262cfbe9072620381645476ad
BLAKE2b-256 checksum
How to use checksums
dcb72aebdbe92c20bfd3721fc3380a369fe467900e517ea4c144d308bdfa435c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / vn_money-0.1.0-py3-none-any.whl

Download URL vn_money-0.1.0-py3-none-any.whl
Size 11.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
09d69b835dc1f239e622621d33e2804b8b9550358dca0a46e6a7c4e08be6d0ce
BLAKE2b-256 checksum
How to use checksums
30a4f9fda3138f711cd16392b290676f4b2ddfc1a422323890ab403d250c3c69
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page