Packages
Fast, memory-efficient Elixir library for parsing spreadsheet files (.xls, .xlsx, .xlsm, .xlsb, .ods) powered by Rust and Calamine. Features precompiled NIFs, smart type handling, and support for hidden sheets.
Current section
Files
Jump to
Current section
Files
spreadsheet
CHANGELOG.md
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.6.4] - 2026-10-14
No breaking changes: the public API, supported Elixir requirement (`~> 1.18`)
and all runtime `mix.exs` constraints are unchanged. Dependency updates only.
### Added
- Legacy `.xls` files declaring code page 10007 (Mac Cyrillic) or 21010
(UTF-16LE) now open; previously calamine failed with "Codepage … not found"
(`codepage` 0.1.2 → 0.1.3).
### Updated
- Updated Rust dependencies:
- `encoding_rs` 0.8.35 → 0.8.42: rewritten ASCII fast path, UTF-8 validation
via `simdutf8`, and a fix for a boundary bug in streaming legacy decoders
(calamine uses the non-streaming API, so results are unchanged). Pulls in
four new crates: `simdutf8`, `scopeguard`, `core_detect` and
`multiversion_no_op`.
- `flate2` 1.1.9 → 1.1.10: a stream truncated at end-of-file is now an error
instead of a silent short read, plus an infinite-loop fix in gzip header
writing. Drops `miniz_oxide` and `adler2` from the lockfile.
- flate2 1.1.10 moved zlib-rs runtime CPU feature detection (SIMD) behind a
new default feature, which `zip` disables. `flate2` is now listed directly
in `Cargo.toml` with `runtime_detection` enabled, so decompression keeps
using SIMD. The backend is still zlib-rs.
- Transitive: `cc`, `cfg-if`, `crc32fast`, `equivalent`, `find-msvc-tools`,
`indexmap`, `inventory`, `js-sys`, `libc`, `syn`, `unicode-ident`,
`wasm-bindgen` (+ macro crates), `zlib-rs`, `zopfli`.
- `rustler` stays on 0.38.0: crate 0.39.0 is out, but the hex package is
still 0.38.0 and the two must match.
- Verified parsing is unaffected: every fixture in `test/files` (`.ods`,
`.xls`, `.xlsx` and both hidden-sheet variants) was parsed before and
after the update via `sheet_names/2`, `sheet_names_from_binary/2`,
`parse/2` and `parse_from_binary/2`, producing identical results.
- Tested against `rustler_precompiled` 0.10.0, which the existing `~> 0.8`
constraint already allows (adds NIF 2.18 / OTP 29 support; the options this
library passes and the checksum file format are unchanged).
- Tested against `decimal` 3.2.0.
- Updated Elixir dev/test dependencies: `ex_doc` 0.40.4, `igniter` 0.8.4, and
their transitives (`ex_ast`, `finch`, `hpax`, `makeup`, `mint`, `req`,
`sourceror`, `spitfire`). Dropped the stale `inflex` and `toml` lock
entries, which no environment resolved.
### Removed
- Dropped the unused `dialyxir` dev/test dependency.
### Internal
- CI actions updated to their current major versions.
## [0.6.3] - 2026-08-25
No breaking changes: the public API, supported Elixir requirement (`~> 1.18`)
and all runtime Elixir dependencies are unchanged. Dependency updates only.
### Updated
- Updated transitive Rust dependencies (`android_system_properties`, `cc`,
`crc32fast`, `fast-float2`, `find-msvc-tools`, `futures-core`,
`futures-task`, `futures-util`, `js-sys`, `log`, `syn`, `wasm-bindgen`,
`wasm-bindgen-macro`, `wasm-bindgen-macro-support`, `wasm-bindgen-shared`,
`zlib-rs`). The direct Rust dependencies (`calamine` 0.36.1, `rustler`
0.38.0, `chrono` 0.4.45, `serde` 1.0.229) were already at their latest
releases and are unchanged.
- Verified parsing is unaffected: every fixture in `test/files` (`.ods`,
`.xls`, `.xlsx` and both hidden-sheet variants) was parsed before and
after the update via `sheet_names/2`, `sheet_names_from_binary/2`,
`parse/2` and `parse_from_binary/2`, producing identical results.
- Updated Elixir dev/test dependencies (no runtime deps changed):
`earmark_parser` 1.4.45 → 1.4.46, `req` 0.7.1 → 0.7.3, `spitfire` 0.3.13
→ 0.4.0. Dropped the stale `castore` lock entry, which no environment
resolved.
## [0.6.2] - 2026-07-30
No breaking changes: the public API, supported Elixir requirement (`~> 1.18`)
and all runtime Elixir dependencies are unchanged. Dependency updates only.
### Updated
- Updated Rust dependencies:
- [calamine](https://github.com/tafia/calamine/blob/master/Changelog.md) 0.36.0 → 0.36.1
- Bug fixes only: fixed a 0.36.0 regression that broke Strict OOXML
(ISO/IEC 29500) `_rels/.rels` links, fixed XLSB parsing of unhandled
workbook-global records (previously could silently drop a worksheet or
panic), and XLS string cells now preserve empty strings instead of
discarding them.
- [serde](https://github.com/serde-rs/serde/releases) 1.0.228 → 1.0.229
- Updated transitive Rust dependencies (`cc`, `futures-core`, `futures-task`,
`futures-util`, `libc`, `proc-macro2`, `quote`, `syn`)
- Updated Elixir dev/test dependencies (no runtime deps changed):
`igniter` 0.8.2 → 0.8.3, `ex_ast` 0.12.10 → 0.13.1, `glob_ex` 0.1.11 →
0.1.12, `req` 0.6.3 → 0.7.1
## [0.6.1] - 2026-07-17
No breaking changes: the public API, supported Elixir requirement (`~> 1.18`)
and all runtime Elixir dependencies are unchanged.
### Changed
- CI now tests every supported Elixir version (1.18, 1.19 and 1.20) against
every Erlang/OTP version each of them supports (26 through 29), using the
latest patch releases (1.18.4 / 1.19.5 / 1.20.2, OTP 26.2 / 27.3 / 28.5 /
29.0).
### Updated
- Updated Rust dependencies:
- [calamine](https://github.com/tafia/calamine/blob/master/Changelog.md) 0.35.0 → 0.36.0
- No API changes affecting this library (verified: only
`open_workbook_auto*`, `sheets_metadata` and `worksheet_range` are used).
- Bug fixes: shared-formula cell-name replacement for defined names, custom
number-format detection for the `*` (fill/repeat) operator, better XLS
error messages for truncated files.
- Behaviour note: XLSX text cells now trim leading/trailing ASCII
whitespace unless `xml:space="preserve"` is set, matching Excel and the
ECMA-376 spec. Files written by Excel, LibreOffice, openpyxl etc. set
this attribute when whitespace is intended, so their output is
unaffected; only spec-violating writers are normalised.
- Updated transitive Rust dependencies (`atoi_simd`, `cc`, `memchr`,
`quick-xml`, `syn`, `wasm-bindgen`, `zip`, `zlib-rs`, …)
- Updated Elixir dev/test dependencies (transitive only, no runtime deps
changed): `ex_ast` 0.12.0 → 0.12.10, `hpax` 1.0.3 → 1.0.4, `makeup` 1.2.1 →
1.2.2, `mint` 1.9.0 → 1.9.3, `req` 0.6.2 → 0.6.3
## [0.6.0] - 2026-06-24
### Fixed
- **Breaking:** `.ods` dates are now normalised to `NaiveDateTime` (date-only
values become midnight), matching the `.xlsx`/`.xls` output. Previously they
leaked back as raw ISO 8601 strings, so the type of a date cell depended on the
source format.
- Duration-formatted cells (e.g. `[h]:mm:ss`) are no longer misread as bogus
dates. Calamine surfaces them as `DateTime`/`TimeDelta` values; they are now
returned as ISO 8601 duration strings (e.g. `"PT1H30M0S"`).
### Changed
- Internal cleanup: deduplicated the path/binary NIF bodies behind generic Rust
helpers, and the all-sheets/format-error handling in the Elixir wrapper. No
user-facing behaviour change.
- Removed the inert `doctest Spreadsheet` declaration (the `@doc` examples are
illustrative and were never executed as doctests).
### Updated
- Updated Elixir dependencies:
- [rustler](https://github.com/rusterlium/rustler/blob/master/CHANGELOG.md) 0.37.3 → 0.38.0
- Added `Term::size` support on Erlang 29 (`nif_version_2_18`), direct UTF-8
atom support (`nif_version_2_17`), `NewBinary::from_iter`, and
`deserialize_seq` for binary deserialization
- Deprecated the previous atom-creation signatures (alongside the new UTF-8
variants) and removed some deprecated codegen features. No code changes
were required — this library uses only `rustler::nif`, `NifTaggedEnum`, and
`rustler::init!`, none of which are affected. Not breaking for callers.
- `credo` 1.7.18 → 1.7.19 (dev/test)
- `decimal` 3.1.0 → 3.1.1
- `ex_doc` 0.40.2 → 0.40.3 (dev)
- `igniter` 0.8.0 → 0.8.2 (dev/test)
- Updated Rust dependencies:
- [rustler](https://github.com/rusterlium/rustler/blob/master/CHANGELOG.md) 0.37.4 → 0.38.0 (kept in sync with the Elixir package)
- [chrono](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) 0.4.44 → 0.4.45
- Updated transitive Rust dependencies (`cc`, `js-sys`, `log`, `memchr`,
`quote`, `shlex`, `syn`, `wasm-bindgen`, `zlib-rs`, …)
## [0.5.0] - 2026-05-15
### Added
- `Spreadsheet.Calamine.parse_all_from_path/2` and `parse_all_from_binary/2` —
open the workbook once and return every sheet, replacing the previous
open-per-sheet loop in `Spreadsheet.parse/2`.
### Changed
- **Breaking:** Excel formula errors (`#REF!`, `#DIV/0!`, `#N/A`, …) are now
returned as `{:error, reason}` tuples instead of bare strings, so they are
distinguishable from text cells.
- All NIFs are now scheduled on `DirtyCpu` to avoid stalling the BEAM scheduler
when parsing large workbooks.
### Fixed
- Datetimes that calamine cannot convert no longer leak the literal string
`"Invalid DateTime"` into the result — they become `nil`.
- Sub-second precision in `Data::DateTime` cells is preserved (was previously
truncated to whole seconds by the format string).
- Hidden-sheet filtering now uses sheet metadata in both branches, removing a
latent inconsistency between `hidden: true` and `hidden: false`.
### Updated
- Updated Elixir dependencies: `erlex` 0.2.8 → 0.2.9, `ex_ast` 0.11.2 → 0.12.0
- Updated transitive Rust dependencies (`cc`, `hashbrown`, `js-sys`, `libc`, `quick-xml`, `wasm-bindgen`)
## [0.4.10] - 2026-05-12
- Relax `rustler_precompiled` package version
## [0.4.9] - 2026-05-12
### Added
- Allow `decimal` package version 2.0 or 3.0 (`~> 2.0 or ~> 3.0`)
### Updated
- Updated `rustler_precompiled` 0.8.1 → 0.9.0
- Updated Rust dependencies:
- [calamine](https://github.com/tafia/calamine/blob/master/CHANGELOG.md) 0.34.0 → 0.35.0
- Added `has_1904_epoch()` method on XLS/XLSX/XLSB structs to detect Excel date epoch
- Made `RowDeserializer` public for custom `DeserializeSeed` implementations
- Performance: optimised `<v>` cell value parsing and cell reference parsing
- Bug fixes: redundant `<v>` in XLSX inlineStr cells, XML namespace prefixes in relation ids, rich text `<r>` after plain `<t>`, XLS date formats for formula values, XLSX shared formula expansion corrupting function names (e.g. `LOG10`), inlineStr CDATA handling, BIFF5 formula parse panic, panic on unsupported XLS formula functions
- [rustler](https://github.com/rusterlium/rustler/blob/master/CHANGELOG.md) 0.37.3 → 0.37.4
- Fixed OTP 22/23 heap overflow by gating `ErlNifResourceTypeInit` members/dyncall on `nif_version_2_16`
## [0.4.7] - 2026-03-18
### Updated
- Updated Rust dependencies:
- [calamine](https://github.com/tafia/calamine/blob/master/CHANGELOG.md) 0.33.0 → 0.34.0
- Performance improvements: cell reader buffer reuse, optimised XLS sector chain reads, cached path/attr lookups, minimised allocations
- Bug fixes: capitalized workbook/book stream handling in XLS, BIFF5 record parsing, empty `<v/>` values in XLSX, millisecond rounding
- [rustler](https://github.com/rusterlium/rustler/blob/master/CHANGELOG.md) 0.37.2 → 0.37.3
## [0.4.6] - 2026-03-02
### Updated
- Updated Rust dependencies:
- [calamine](https://github.com/tafia/calamine/blob/master/CHANGELOG.md) 0.32.0 → 0.33.0
- [chrono](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) 0.4.43 → 0.4.44
## [0.4.5] - 2026-01-20
### Updated
- Updated Rust dependencies
## [0.4.3] - 2025-12-05
### Updated
- Updated Rust dependencies:
- [rustler](https://github.com/rusterlium/rustler/blob/master/CHANGELOG.md) 0.37.0 → 0.37.2
## [0.4.2] - 2025-11-24
### Updated
- Updated Rust dependencies in native spreadsheet module:
- [calamine](https://github.com/tafia/calamine/blob/master/CHANGELOG.md) 0.31.0 → 0.32.0
## [0.4.1] - 2025-11-17
### Changed
- Dependency updates
## [0.4.0] - 2025-10-29
### Added
- **NEW**: `parse/2` now supports parsing all sheets at once when called without the `:sheet` option
- Returns a list of `{sheet_name, sheet_data}` tuples in sheet order
- Supports `:hidden` option to exclude hidden sheets
- Works with both file paths and binary content via `:format` option
- Example: `Spreadsheet.parse("workbook.xlsx")` returns `{:ok, [{"Sheet1", [...]}, {"Sheet2", [...]}]}`
### Changed
- **BREAKING**: Unified API interface for `sheet_names/2` and `parse/2` functions
- `sheet_names/2` now accepts a `:format` option (`:filename` or `:binary`) instead of having separate `_from_binary` variant
- `parse/2` now uses a `:sheet` option to specify which sheet to parse, instead of a separate parameter
- Default format is `:filename` for backwards compatibility with existing code that doesn't pass options
- Examples:
- `Spreadsheet.sheet_names(content, format: :binary)` replaces `Spreadsheet.sheet_names_from_binary(content)`
- `Spreadsheet.parse(content, sheet: "Sheet1", format: :binary)` replaces `Spreadsheet.parse_from_binary(content, "Sheet1")`
- `Spreadsheet.parse("file.xlsx", sheet: "Sheet1")` replaces `Spreadsheet.parse("file.xlsx", "Sheet1")`
### Deprecated
- `sheet_names_from_binary/2` - Use `sheet_names/2` with `format: :binary` instead
- `parse_from_binary/2` - Use `parse/2` with `sheet:` and `format: :binary` options instead
### Fixed
- Improved error messages for invalid format options
## [0.3.0] - 2025-10-13
### Updated
- Updated Rust dependencies in native spreadsheet module:
- [calamine](https://github.com/tafia/calamine/blob/master/CHANGELOG.md) 0.30.0 → 0.31.0
- [rustler](https://github.com/rusterlium/rustler/blob/master/CHANGELOG.md) 0.36.2 → 0.37.0
- [chrono](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) 0.4.41 → 0.4.42
- [serde](https://github.com/serde-rs/serde/releases) 1.0.219 → 1.0.228
- Changed calamine feature flag from `dates` to `chrono`
## [0.2.4] - 2025-09-03
### Updated
- Updated calamine dependency to version 0.30.0
## [0.2.3] - 2025-01-08
### Updated
- Updated calamine dependency to version 0.29.0
## [0.2.2] - 2025-07-23
### Added
- Support for filtering hidden sheets in `sheet_names/2` and `sheet_names_from_binary/2` functions