Skip to content

Conversation

@dkuku
Copy link

@dkuku dkuku commented Dec 7, 2025

This algorithm is O1 and it is over 2x faster than the current one.
I implemented it in elixir and this is the same logic. In the elixir pr the benchmarks are against elixir version of the current algorithm used in erlang.
It also works for negative dates which was not supported by the current algorithm, so the specs may need update but I keep it unchanged for now.

@CLAassistant
Copy link

CLAassistant commented Dec 7, 2025

CLA assistant check
All committers have signed the CLA.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 7, 2025

CT Test Results

Tests are running... https://github.com/erlang/otp/actions/runs/20011636102

Results for commit c87d145

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

  • No CT logs found
  • No HTML docs found
  • No Windows Installer found

// Erlang/OTP Github Action Bot

@IngelaAndin IngelaAndin added the team:VM Assigned to OTP team VM label Dec 8, 2025
@Maria-12648430
Copy link
Contributor

Interesting... 😺

That said, I see that there are already tests for the affected functions in place, but IMO they're not too conclusive, they only test symmetry, and even that looks a little dubious to me. Could you provide some more tests (unit/common and maybe also property tests) that check if the computed results are actually correct? 🤔

@dkuku
Copy link
Author

dkuku commented Dec 8, 2025

Interesting... 😺

That said, I see that there are already tests for the affected functions in place, but IMO they're not too conclusive, they only test symmetry, and even that looks a little dubious to me. Could you provide some more tests (unit/common and maybe also property tests) that check if the computed results are actually correct? 🤔

I did not check the existing tests, my bad. updated

@Maria-12648430
Copy link
Contributor

I will take a closer look at the tests themselves tomorrow, but for now, note that you have to insert calls for the proptests in calendar_prop_SUITE.erl for them to be run.

0 = calendar:date_to_gregorian_days(0, 1, 1),
{0, 1, 1} = calendar:gregorian_days_to_date(0),

%% Test year 0 boundaries (year 0 is a leap year)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that got me thinking 🤔 Turns out there is no year 0 in the Gregorian calendar, year 1BC is immediately followed by year 1AD 🤯

And while the calendar module doc states that the Gregorian calendar of this module "is extended back to year 0", IMO this simply doesn't work out. There is no year 0 in the Gregorian calendar, period. Year 0, if you would instead assume the astronomical calendar, would map to year 1BC in the Gregorian calendar. In any case, this would mean that "year 0" (1BC) is not a leap year.

@IngelaAndin (and maybe also @bjorng), what are your thoughts about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that is_leap_year(0) results in true, which considering what I said above would also be wrong?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe calling the current implementation gregorian is invalid because is uses astronomical and this is how it works currently:

 % iex 
Erlang/OTP 26 [erts-14.2.5] [source] [64-bit] [smp:14:14] [ds:14:14:10] [async-threads:1] [jit]

Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :calendar.gregorian_days_to_date(0)
{0, 1, 1}
iex(2)> :calendar.gregorian_days_to_date(1)
{0, 1, 2}
iex(3)> :calendar.gregorian_days_to_date(58)
{0, 2, 28}
iex(4)> :calendar.gregorian_days_to_date(59)
{0, 2, 29}
iex(5)> 

Copy link
Contributor

@Maria-12648430 Maria-12648430 Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know... the current implementation is according to ISO 8601:200x, which tl;dr boils down to astronomical year numbering (which makes more sense/is more practical than the gregorian with its cumbersome 1BC/1AD transition), but calls it "gregorian extended to 0"; the "... extended to 0" part only occurs in the module doc, though, everywhere else it is simply called Gregorian. Now, fixing the documentation would be no big issue I think, but unfortunately the astronomical-vs-gregorian confusion goes down to the function names, too...

However, that is kinda outside the scope of this PR. As far as this PR goes, I'm all good =)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will leave this for discussion. it's an existing bug.
Maybe introducing an astronomical_days_to_date and deprecating the gregorian/1 would be a solution ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference this is called date_from_iso_days in elixir.
elixir-lang/elixir@904adda

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I consulted with @juhlig, and after some research we concluded that the documentation is correct and matches the implementation, and vice versa. A question that arose however is why there is a restriction for non-negative years. Admittedly, it is rare that you have to deal with such dates, but why restrict it at all?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous implementation just did not support it - it required some tweaks.
I think this limitation can be removed - the algorithm supports negative dates just fine.
It is not a popular usecase probably - but I think it should be enabled even to allow solving riddles in erlang.
I can add tests for negative dates also. But it may be better to have a separate pr with the changes ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for the OTP team to decide 🤷‍♀️ But seeing that there are many functions to change to consistely allow for negative years, plus their tests, plus documentation modifications, personally I'd opt for a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @bjorng probably has some insight and otherwise I think @rickard-green is our time expert.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team:VM Assigned to OTP team VM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants