Blog 2024 05 29 C++23: chrono related changes
Post
Cancel

C++23: chrono related changes

Let’s continue with what is changing in C++23. This time, let’s look at the three changes related to the chrono library. The first two are related to std::format and using locales, and the last one is about what requirements time_point imposes on a clock.

DR: Fixing locale handling in chrono formatters

P2372R3 is a fix of C++ based on the bug reported in LWG-3547. The problem is a bug in the specification of chrono formatters in the standard.

While std::format is locale-independent by default and gives you the possibility to change the locale via format specifiers, the new formatter specializations of C++20 for chrono types are localized by default and don’t let you change the locale via format specifiers.

There are three problems with this approach:

  • It goes against the design of std::format (and by the way the fmt implementation)
  • As chrono formatters are automatically localized, you cannot avoid locales, if you want to do so, you have to automatically format date and time manually
  • Some chrono formatters act as if they would provide a locale-independent specifier (%S) and a localized one as well (%OS) which is misleading.

So the original behaviour of std::format and std::chrono is the below:

1
2
3
4
5
std::locale::global(std::locale("ru_RU"));
using sec = std::chrono::duration<double>;
std::string s_std = std::format("{:%S}", sec(4.2)); // s3 == "04,200" (localized)
auto s_std2 = std::format("{:L%S}", sec(4.2)); // throws format_error
std::string s_fmt = fmt::format("{:%S}", sec(4.2));  // s == "04.200" (not localized)

And the fixed one is:

1
2
3
4
5
std::locale::global(std::locale("ru_RU"));
using sec = std::chrono::duration<double>;
auto s_std = std::format("{:%S}", sec(4.2)); // s == "04.200" (not localized)
auto s_std2 = std::format("{:L%S}", sec(4.2)); // s == "04,200" (localized)
std::string s_fmt = fmt::format("{:%S}", sec(4.2));  // s == "04.200" (not localized)

Notice that what was automatically localized, is not localized anymore and at the same time, you have the opportunity to manually localize chrono literals.

Clarify the handling of encodings in localized formatting of chrono types

P2419R2 is solving another problem with std::format and std::chrono. I already wrote about it in C++23: Encoding related changes, but I think it’s worth mentioning here too.

While P2372R3 solves the problem of whether chrono types should be localized or not, this proposal solves how to handle encodings, when chrono types have to be localized.

Let’s take the example from the paper.

1
2
std::locale::global(std::locale("Russian.1251"));
auto s = std::format("День недели: {:L}", std::chrono::Monday);

The problem is that before the acceptance of this paper (P2419R2), the standard didn’t specify what should happen if the literal encoding (in this case of std::chrono::Monday) is UTF-8 and it exists in the specified locale in a different encoding.

One option is to use the local encoding and the other is to use a UTF-8 encoding.

In this case, with the locale encoding of “Russian.1251”, we’d use CP1251 which is not valid UTF-8. Mixing the results in “День недели: \xcf\xed”, where “\xcf\xed” is in Russian.1251 and it’s not valid UTF-8. This is also called a “Mojibake” and is undesirable.

Mojibake (文字化け) is a term in Japanese that translates to “character transformation” or “character corruption” in English. It refers to the phenomenon where text that is encoded or decoded incorrectly results in a display of garbled or unreadable characters. Mojibake is often seen when there is a mismatch between the encoding used to store or transmit text and the encoding expected by the software or system trying to interpret that text.

With the acceptance of P2419R2, if the formatted text is in UTF-8 and the locale is among an implementation-defined set of locales, each replacement that depends on the locale is performed as if the replacement character sequence is converted to UTF-8.

Relaxing requirements for time_point<>::clock

P2212R2 recognizes the need of passing a non-Cpp17Clock-like clock to a std::chrono::time_point.

Let’s answer two questions here! What is a time_point and what requirements does a Cpp17Clock have?

std::chrono::time_point is a class template that represents a point in time. It takes a clock and a duration as template parameters and it acts as if it stored the time interval passed since the start of the clock’s epoch.

The requirements of a clock are listed here. Basically, these requirements fix that a clock’s API must have Clock::rep, Clock::period, Clock::time_point denoting types, must support Clock::now() member function and defines when Clock::is_steady should be true. is_steady should be true if there are two clocks with the same epoch and if the time returned by one is less or equal at one time_point then it should be less or equal at any time_point.

The authors of P2212R2 explain a couple of cases when the original requirements are too strict.

  • C++20 introduced a clock that is not really a clock, local_t. It’s a pseudo-clock to indicate that the time point represents local time in a not-yet-specified time zone.
  • Sometimes, you need a stateful clock that requires a non-static now() function.
  • Sometimes, you need to represent “time of day” as a distinct time_point without having the date specified.

But what is changing?

The standard doesn’t impose Cpp17Clock requirements or local_t on time_point. For threads on the other hand, now it explicitly says that template parameters with the name Clock should model those Cpp17Clock requirements instead of requiring simply is_clock_v to be true.

Conclusion

In this article, we had a look at C++23 changes related to the chrono library. We saw how the std::format becomes more consistent in handling durations and different locales. We also saw that time_point is relaxing its requirements on a clock.

Connect deeper

If you liked this article, please

This post is licensed under CC BY 4.0 by the author.