Skip to content

utils: replace boost::date_time usage with c++20 std::chrono#30462

Closed
theuni wants to merge 2 commits intobitcoin:masterfrom
theuni:nuke-date-time
Closed

utils: replace boost::date_time usage with c++20 std::chrono#30462
theuni wants to merge 2 commits intobitcoin:masterfrom
theuni:nuke-date-time

Conversation

@theuni
Copy link
Member

@theuni theuni commented Jul 16, 2024

This would be a very straightforward and uncontroversial change if not for the sad state of std lib implementations which implement this functionality.

As of now, std::chrono::parse and friends (c++20 additions) are only available as of gcc 14 and (unreleased) clang 19.

Formatting in chrono is quite useful, so I don't think it makes sense to wait years until we require those compilers.

Instead of waiting around, this PR takes the sledgehammer approach of adding a fully-conformant implementation of the new c++20 chrono features (from the author of std::chrono himself). More info available here: https://howardhinnant.github.io/date/date.html

A convenience header adds the chrono_parse namespace, which uses the std lib if possible, and date.h otherwise.

This allows us to get rid of our last use of boost::date_time, and we can begin using more modern c++20-isms as well.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jul 16, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK hebasto

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #30664 (build: Remove Autotools-based build system by hebasto)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@theuni
Copy link
Member Author

theuni commented Jul 16, 2024

This was noticed while working on #30434, which requires us to install boost::date_time. Though vendoring date.h here may seem heavy, keep in mind that the status quo is a forward-incompatible mess of boost headers :)

@hebasto
Copy link
Member

hebasto commented Jul 16, 2024

This allows us to get rid of our last use of boost::date_time

Concept ACK on that.

theuni added 2 commits July 16, 2024 19:09
Taken from commit 8a932116795f4046ba8ed79ce33717c7331a1490

See documentation here: https://howardhinnant.github.io/date/date.html

This is meant to be a full implementation of the c++20 additions to
std::chrono.

This functionality is only available in libstd++ >= 14, and libc++ >= 19,
meaning that it will be years before we can require availability in the std
namespace.

Use of this lib will allow us to drop our current dependency on
boost::date_time, as well as to begin using (at least) formatting with
std::chrono.
…teTime

Because the functions in date.h are intended to line up exactly with the c++20
implementation in the std namespace, attempt to use std::chrono if a conforming
version is found.

While testing, I noticed that libstdc++ breaks for very old timestamps, so
guard against that and add an additional test.
@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Debug: https://github.com/bitcoin/bitcoin/runs/27526907840

Hints

Make sure to run all tests locally, according to the documentation.

The failure may happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.


// Guard against broken implementations which can't handle very old time
// values. Speficically, libstdc++ (as of v14) is broken at least on x86_64
// with times before 1677-09-21T00:12:44Z .
Copy link
Member

Choose a reason for hiding this comment

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

Can you add more details, whether this is a crash/UB, or just wrong behavior? Is there a bug?

Copy link
Member Author

Choose a reason for hiding this comment

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

Specifically the comparison against the epoch (0) fails. So, if the timestamp is old enough, this should fail but doesn't:

assert tp < epoch

You can play around here: https://godbolt.org/z/G8r837oGf

I'll add more specific info. I should probably submit a bug report upstream as well.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, so it is UB/crash, according to ubsan:

/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/chrono.h:227:38: runtime error: signed integer overflow: -9223372037 * 1000000000 cannot be represented in type 'long int'

Copy link
Member

@maflcko maflcko Jul 16, 2024

Choose a reason for hiding this comment

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

@maflcko
Copy link
Member

maflcko commented Jul 16, 2024

Formatting in chrono is quite useful, so I don't think it makes sense to wait years until we require those compilers.

Can you give some examples where it would be useful? Right now it is only used in a single place in the wallet.

This was noticed while working on #30434, which requires us to install boost::date_time. Though vendoring date.h here may seem heavy, keep in mind that the status quo is a forward-incompatible mess of boost headers :)

Is the boost header footprint larger than this header? Also, by adding this header the locale linter alerts, so I wonder if the new stuff changes the behavior for users, or if the linter just alerts about unused code. Finally, ParseISO8601DateTime can be done in much less than 8k lines of C++ code, so I wonder if just the required parts can be extracted?

@theuni
Copy link
Member Author

theuni commented Jul 16, 2024

Formatting in chrono is quite useful, so I don't think it makes sense to wait years until we require those compilers.

Can you give some examples where it would be useful? Right now it is only used in a single place in the wallet.

I was assuming that it'd be useful in other places where it's simply cumbersome to deal with these conversions like logging. Or as a simplification for FormatISO8601DateTime in util/time.cpp. But sure, if we're not going to end up using any more of this formatting in the future, I agree it's not worth bothering with for this single function.

Is the boost header footprint larger than this header?

Heh. I modified #30434 to only install the headers required by boost::date_time (and its dependencies, recursively). The result:

cory@nowhere:19:48:40:~/dev/bitcoin (cmake-boost-depends)
 $ find depends/x86_64-pc-linux-gnu/include/boost/ -type f -name "*.hpp" | wc -l
4268

I won't even bother adding up the lines of code, but I'm quite sure it's less than 8k :)

Also, by adding this header the locale linter alerts, so I wonder if the new stuff changes the behavior for users, or if the linter just alerts about unused code.

Hmm, will have a look at this.

Finally, ParseISO8601DateTime can be done in much less than 8k lines of C++ code, so I wonder if just the required parts can be extracted?

I started down this rabbit hole intending to simply rewrite this function, but ended up afraid of introducing bugs due to the locale footguns. Maybe I'm over-complicating it?

I think if you look at this as: "use c++20 functionality with a bridge for compilers which don't support it yet", this PR makes perfect sense. But given the size of that bridge, I agree it's maybe not worth it.

I suppose I was also curious if anyone else was waiting on this functionality for other code.

@maflcko
Copy link
Member

maflcko commented Jul 16, 2024

I started down this rabbit hole intending to simply rewrite this function, but ended up afraid of introducing bugs due to the locale footguns. Maybe I'm over-complicating it?

Happy to try myself, but https://en.cppreference.com/w/cpp/chrono/year_month_day (which can convert to system clock) is available, so using Split + ToIntegral + year_month_day, then adding the hours, minutes and seconds should do everything in about 20 lines of code?

@maflcko
Copy link
Member

maflcko commented Jul 25, 2024

Maybe I'm over-complicating it?

And if there were any issues that I am missing, commit fa72dcb would be wrong as well, no?

@fanquake fanquake marked this pull request as draft July 25, 2024 09:38
@maflcko
Copy link
Member

maflcko commented Aug 29, 2024

@theuni Are you still working on this? My suggestion from #30462 (comment) should be easy to implement, unless I am missing something. Happy to pick that up, if this pull is up-for-grabs.

@theuni
Copy link
Member Author

theuni commented Aug 29, 2024

@maflcko All yours :)

@maflcko
Copy link
Member

maflcko commented Nov 28, 2024

should do everything in about 20 lines of code?

Ok, it is only 18 lines of code, see #31391

@bitcoin bitcoin locked and limited conversation to collaborators Nov 28, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants