Skip to main content

xlformat

xlformat applies an Excel number format to a value and returns the text Excel displays. It is pure Python with no runtime dependencies, and its output is tested against text captured from Excel 16 for Mac.

>>> from xlformat import format_value
>>> format_value(1234.5, "#,##0.00")
'1,234.50'

Documentation: https://xlformat.readthedocs.io.

Installation

pip install xlformat

Python 3.11 or later.

Usage

format_value returns the displayed text as a string.

>>> format_value(-1234.5, "#,##0.00;(#,##0.00)")
'(1,234.50)'
>>> format_value(44927.75, "yyyy-mm-dd hh:mm")
'2023-01-01 18:00'
>>> format_value(0.125, "# ?/?")
' 1/8'
>>> import datetime
>>> format_value(datetime.date(2023, 1, 1), "dddd, mmmm d, yyyy")
'Sunday, January 1, 2023'

Values may be int, float, Decimal, bool, str, None, date, datetime, time or timedelta. Pass date1904=True for workbooks that use the 1904 date system.

format_cell takes the same arguments and returns a FormattedCell with what Excel shows around the text: the section color, the default alignment, the position of a * fill, whether the value overflowed, and the text without any width applied.

>>> from xlformat import format_cell
>>> cell = format_cell(-1234.5, "[Red]0.00;[Blue]-0.00")
>>> cell.text, cell.color, cell.align
('-1234.50', 'Blue', 'right')

xlformat.DEFAULT_PALETTE maps color names such as Blue or Color12 to RRGGBB.

NumberFormat.parse compiles a format string once. Parsing is cached, and the compiled object has format and format_cell methods and facts about the pattern such as is_date. An invalid format string raises FormatError, but only when Excel itself would refuse to open a file containing it. Callers reading files catch it and render the cell as General: Excel's repair resets each cell with such a format to General and keeps every other cell's format.

Width

Without a width, the result is what copy and paste from Excel gives: * fills add nothing, _x pads add one space, and a value that cannot be shown is 255 # characters. Two parameters add a width, and they cannot be combined.

  • width is a number of characters. It suits terminals and plain text. * fills repeat to the width, a number or date that does not fit becomes width # characters, General drops digits before it overflows, and text never overflows.
  • column_width is the number from Excel's Column Width dialog, such as 8.43. The text is measured with the glyph widths of font, so the result matches the sheet character for character, including how many # and fill characters fit. font is a name or a (name, size) pair or a Font, and defaults to ("Calibri", 11), the font openpyxl writes. When the cell's font differs from the workbook's Normal font, font is the cell's and normal_font the Normal one, which sets the column's units. A width read from a file (<col width>, or none) is not the dialog number: file_column_metrics converts it.
>>> format_value(1234567.891, "General", width=7)
'1234568'
>>> format_value(1000, "#,##0.0", width=6)
'######'
>>> format_value(1000, "#,##0.0", column_width=6)
'1,000.0'
>>> format_value(1234.5, "0.00*-", column_width=12, font=("Calibri", 14))
'1234.50---------'

The two modes differ on purpose. In Excel a comma or a point is narrower than a digit, so 1,000.0 fits a column 6 wide, while in character mode it is seven characters. A measure callable can replace the character count when neither mode fits, and width is then in its units.

Quirks

Three Excel behaviors are reproduced by default and can be switched off with quirks=Quirks.STRICT or by passing Quirks with individual fields set. QUIRKS.md has the evidence for each.

quirk Excel strict
leap_year_1900 serial 60 under mmmm d, yyyy is February 29, 1900 February 28, 1900
fifteen_digit_precision 0.1234512345123456 under sixteen decimals is 0.1234512345123460 0.1234512345123456
fraction_denominators 0.91 under # ?/? is 1 8/9, the closest fraction

Scope and known differences

  • Excel mode measures Calibri and Aptos Narrow, regular, bold, italic and bold italic, at 9, 10, 11, 12, 14 and 16 points, in Excel for Mac (which bundles Aptos Narrow, the Windows default). Other sizes are scaled from the nearest measured size and may differ from Excel by a character near the edge of a column. Any other font raises LookupError.
  • Character mode is its own contract. A # run is always width characters, and a value is judged by its character count, where Excel would fit or overflow some texts by glyph width.
  • Expected outputs come from Excel 16 for Mac with the en-US locale. Month and day names are English; locale tags such as [$-409] are parsed but not applied. Windows Excel renders text with a different engine and may disagree on a borderline fit in Excel mode.
  • Error values are passed as ExcelError (ExcelError("#N/A")); a plain string such as "#N/A" is text.
  • Conditional formatting and cell styles beyond the format string are out of scope.

Every captured row passes; no known deviations remain. Fifteen color readings in the Excel captures are skipped because Excel's scripting interface misreports them; the sheet itself shows the color the package returns.

xlformat is one of three packages from the sheetmill project, with sheetpeek, which renders workbooks to HTML, Markdown and terminal output, and xlformula, which evaluates formulas. Each installs and works on its own. A sheetmill meta-package that installs all three is planned.

Development

With uv, which installs the exact versions pinned in uv.lock:

uv sync --group docs
uv run pytest                 # unit tests and every captured Excel row, about eight seconds
uv run pytest --tier=fast     # a per-format sample of the Excel captures
uv run ruff check . && uv run ruff format --check . && uv run mypy
uv run --group docs sphinx-build -n -W docs docs/_build/html
npx --yes pagefind@1.5.2 --site docs/_build/html   # search index for served copies

With pip 25.1 or later, which ignores uv.lock and installs the latest versions the dependency groups allow:

python -m venv .venv && source .venv/bin/activate
pip install -e . --group dev --group docs
pytest && ruff check . && ruff format --check . && mypy
sphinx-build -n -W docs docs/_build/html

CONTRIBUTING.md lists the checks a change must pass and how commits are written.

The documentation's search page uses Pagefind when the site is served over http or https, as on Read the Docs or with python -m http.server -d docs/_build/html. A copy opened straight from disk uses Sphinx's built-in search instead, because browsers do not let a file:// page fetch Pagefind's index.

The expected outputs under tests/captures/ were captured from Excel for Mac by the scripts in capture/: each suite is written as a workbook with openpyxl, opened in Excel through AppleScript, and read back as displayed text at each column width. Regenerating a suite needs Excel for Mac and is described in CAPTURING.md:

uv run python -m capture numeric_core

Most of the code is written with Claude Code, reviewed before each commit and tested against output captured from Excel; see CONTRIBUTING.md.

License

MIT.

Metadata

Release files for xlformat 0.2.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 xlformat 0.2.0
File Size Uploaded Provenance Actions
xlformat-0.2.0.tar.gz 3.0 MB With provenance Details for xlformat-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for xlformat 0.2.0
File Interpreter ABI Platform Actions
xlformat-0.2.0-py3-none-any.whl Python 3 none any Details for xlformat-0.2.0-py3-none-any.whl

Total release size: 3.1 MB

Release files / xlformat-0.2.0.tar.gz

Download URL xlformat-0.2.0.tar.gz
Size 3.0 MB
Tags Source
SHA-256 checksum
How to use checksums
99a60ba1b847067d8d7efa7e408adf046f012f4542ffc4b06d2d8aa42bc95ed4
BLAKE2b-256 checksum
How to use checksums
a392dc1b33c512be7b0b7fc5316d0826a475e4cace5b2cf41c6ad662ed53c7d4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / xlformat-0.2.0-py3-none-any.whl

Download URL xlformat-0.2.0-py3-none-any.whl
Size 51.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ebaea1139c2163720266faa333e345fd42d6dd32845cbb5d52db4b78fb42830f
BLAKE2b-256 checksum
How to use checksums
b102aa2f9dd29d622238acb98fcda5745df7c20ffb5e349c82928df25fea0a77
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Security for xlformat 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for xlformat-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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