All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[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 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 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 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 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 0.37.4 → 0.38.0 (kept in sync with the Elixir package)
    • chrono 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 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 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 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 0.37.2 → 0.37.3

[0.4.6] - 2026-03-02

Updated

  • Updated Rust dependencies:

[0.4.5] - 2026-01-20

Updated

  • Updated Rust dependencies

[0.4.3] - 2025-12-05

Updated

  • Updated Rust dependencies:

[0.4.2] - 2025-11-24

Updated

  • Updated Rust dependencies in native spreadsheet module:

[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:
  • 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