igorgbr
Hi everyone! I just published bizdays, a library for business day calculations in Elixir, shipping with the Brazilian ANBIMA calendar.
Why? In the Brazilian financial market almost everything is counted in business days: fixed income accrual, CDI-indexed investments, settlement dates, and the 252-business-day year convention. The market standard calendar is published by ANBIMA (the Brazilian financial markets association). Python and R have had bizdays by Wilson Freitas for years, and this library brings the same idea to Elixir.
What it does (v0.1.0):
- Built-in ANBIMA calendar for 2001–2099
business_day?,count,add,following,preceding,modified_following,modified_preceding,range,holidayscount/3uses the market convention(from, to]: start excluded, end included- Custom calendars with your own holidays, weekend days and date boundaries
- Pure functions over an immutable struct, zero runtime dependencies
cal = Bizdays.anbima()
Bizdays.business_day?(cal, ~D[2026-02-16])
# => false (Carnival)
Bizdays.add(cal, ~D[2026-02-13], 1)
# => ~D[2026-02-18]
Bizdays.count(cal, ~D[2026-02-13], ~D[2026-02-24])
# => 5
How holidays are built: there is no data file. Holidays are calculated from rules (fixed dates, plus Carnival, Good Friday and Corpus Christi relative to Easter, plus November 20 from 2024, when it became a national holiday) and stored in a MapSet at compile time. When the law changes, it is one rule with a starting year.
How it is tested: the test suite compares all 1,263 dates against a snapshot of ANBIMA’s official holiday spreadsheet (2001–2099). Business day counts were also cross-checked against python-bizdays over thousands of random intervals.
Out of scope for now: the B3 (stock exchange) trading calendar, state and municipal holidays, and CDI/Selic accrual helpers on top of the calendar. Ideas and feedback on these are very welcome, especially from anyone doing financial calculations in Elixir.
Hex: bizdays | Hex
Docs: bizdays v0.1.0 — Documentation
Trending in Announcing
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #ai
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixirconf-us
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #security
- #metaprogramming










Showing Posts 1 to 4- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
tubedude
Igor, thanks for your work on this
I’ve written a finance library and stopped short of implementing the AMBIMA calendar.
I reached out to @kip , maintainer of tempo for tips on how to work with calendars and he wrote a recipe for AMBIMA.
I suggest you take a look. As working with calendars is far from obvious.
igorgbr
Thanks, Roberto, and thanks to @kip for writing that recipe! I hadn’t seen it, and Tempo’s set algebra over intervals is a really nice way to express it.
I read it carefully and noticed two differences that are worth knowing if someone is choosing between the two:
1. Interval convention. The recipe counts
[from, to)(start included, end excluded).Bizdays.count/3uses(from, to](start excluded, end included), which is how business days are usually counted between a settlement date and a maturity. When both dates are business days, which is the normal case, the two give the same number. They differ when exactly one endpoint falls on a non-business day. For example, from Saturday 2026-02-14 to Wednesday 2026-02-18 (Carnival in between),(from, to]gives 1 and[from, to)gives 0.2. Holiday source. The recipe loads an
.icsfile converted from ANBIMA’s spreadsheet with a Python script. bizdays has no data file: holidays are generated from rules at compile time, and the test suite checks all 1,263 dates against the official spreadsheet. So there is nothing to download or convert, and no dependencies, at the cost of doing much less than Tempo.I see them as complementary. Tempo is the general toolkit, and bizdays is a small, focused piece for people who only need the ANBIMA calendar and the 252 convention. For interop,
Bizdays.holidays(calendar, year)returns the dates, so they can be fed into Tempo.I’m curious about your finance library: what would you have needed from a calendar to keep going? CDI/Selic accrual on top of the calendar is the next thing I’m considering, and I’d like it to fit real use cases.
tubedude
Igor,
I really can’t tell you which convention is correct.
As for the source, it comes down to whether you want updates at compile time or runtime. If there is a new holiday, one approach requires a new version of the library, while the other requires a new reference file (the latter also requires runtime parsing and safety checks, which are already solved in a compiled solution).
The Finance library was my first project in Elixir and it essentially ported what I had used in Ruby in the past: a set of functions implementing Excel’s XIRR formula. When I expanded the reach of Finance, I got into some functions that required fractional years. There are many different ways to calculate these:
30/actual,30/360, etc.bizdays/252is the Brazilian convention. I didn’t delve into calendar parsing or holiday compiling like you did, and stopped there.But there is an open door. Finance exposes a
DayCountbehaviour that simply takes two dates and expects a float.bizdayscould solve that as:For actual CDI accrual, you would need a historical calendar of rates and the dates they changed, allowing you to compound the value across the various rate windows in the period. Kinda like this.
igorgbr
Roberto, this is really useful!
Good point on compile time vs runtime. I went with compile time on purpose: ANBIMA’s national calendar changes rarely, and when it does (like November 20 in 2024) it is announced well in advance, so a new patch release felt safer than parsing a file at runtime.
I tried your snippet as-is with bizdays 0.1.0 and Finance 1.8.0, and it works. For a -1000 / +1100 pair from 2025-01-02 to 2026-01-02 (exactly 252 business days),
xirr(flows, basis: Bizdays252)returns{:ok, 0.1}, matching1.1^(252/n) - 1. One behaviour worth documenting: the ANBIMA calendar covers 2001–2099, so a cash flow outside that range raises anArgumentErrorfrom bizdays throughxirrinstead of returning{:error, _}.I’ll add a “Using with Finance” section to the bizdays docs with your example. If you think it makes sense, a link from Finance’s
DayCountdocs would help people looking for BUS/252 find it. Happy to open a PR for that if you’d like.On CDI: agreed, that needs the rate history (the daily CDI series from the Central Bank), which is runtime data. So it probably belongs in a separate package built on top of bizdays, not inside it. Thanks for the pointer.