fmtlib / fmt

A modern formatting library
https://fmt.dev
Other
20.78k stars 2.5k forks source link

Support chrono date formatting flags via hinnant/date #2202

Closed ecorm closed 3 years ago

ecorm commented 3 years ago

It would be useful to have the chrono date formatting flags supported. @HowardHinnant 's date library could be used to perform the Gregorian date calculations. __has_include or some other preprocessor magic could be used to detect the presence of <date/date.h> and then provide the implementation accordingly.

If using hinnant/date is not feasible, then his algorithms are provided here: http://howardhinnant.github.io/date_algorithms.html

ecorm commented 3 years ago

Just a note that hinnant/date pollutes the preprocessor namespace with short, unprefixed macros. See https://github.com/HowardHinnant/date/issues/135

That may be a showstopper in directly making use of that library to support chrono date formatting flags.

vitaut commented 3 years ago

Thanks for the suggestion. Could you elaborate what specifically you are looking for, maybe with an example?

ecorm commented 3 years ago

Here's a C++20 example that formats a std::chrono::timepoint into a ISO8601 date/time.

std::chrono::time_point t = std::chrono::system_clock::now();
std::format(std::locale::classic(), "{:%FT%TZ}", t); // Prints something like 2021-03-31T12:34:56.123456789Z

The chrono format specifiers are documented here: https://eel.is/c++draft/time.format

Those that are specific to the Gregorian calendar are (here summarized):

Specifier Replacement
%a The locale's abbreviated weekday name.
%A The locale's full weekday name.
%b The locale's abbreviated month name.
%B The locale's full month name.
%c The locale's date and time representation.
%C The year divided by 100 using floored division.
%d The day of month as a decimal number. %Od produces the locale's alternative representation.
%D Equivalent to %m/%d/%y.
%e The day of month as a decimal number. %Oe produces the locale's alternative representation.
%F Equivalent to %Y-%m-%d.
%g The last two decimal digits of the ISO week-based year.
%G The ISO week-based year as a decimal number.
%j Number of days (duration), or day of the year (time_point).
%m The month as a decimal number. %Om produces the locale's alternative representation.
%u The ISO weekday as a decimal number. Ou produces the locale's alternative representation.
%U The week number of the year as a decimal number. %OU produces the locale's alternative representation.
%V The ISO week-based week number as a decimal number. %OV produces the locale's alternative representation.
%w The weekday as a decimal number. %Ow produces the locale's alternative representation.
%W The week number of the year as a decimal number. OW produces the locale's alternative representation.
%x The locale's date representation. %Ex produces the locale's alternate date representation.
%X The locale's time representation. %EX produces the locale's alternate time representation.
%y The last two decimal digits of the year. %Oy produces the locale's alternative representation. %Ey produces the locale's alternative representation of offset from %EC (year only).
%Y The year as a decimal number. %EY produces the locale's alternative full year representation.
%z The offset from UTC in the ISO 8601:2004 format. %Ez and %Oz insert a : between the hours and minutes.
%Z The time zone abbreviation.
ecorm commented 3 years ago

The C++11 std::put_time could be leveraged to obtain the platform's locale-dependent names, but would involve std::basic_ostream and could be slow.

ecorm commented 3 years ago

Also, arbitrary time zones would not be feasible without hinnant/date's tz.h. The local time zone offset is probably doable without resorting to 3rd party libraries.

vitaut commented 3 years ago

Note that your example already works (godbolt):

#include <fmt/chrono.h>

int main() {
  std::chrono::time_point t = std::chrono::system_clock::now();
  auto s = fmt::format(std::locale::classic(), "{:%FT%TZ}", t);
  fmt::print("{}", s);
}

Output:

2021-04-01T17:19:54Z

Do you have an example that doesn't?

ecorm commented 3 years ago

It appears I misinterpreted the fmt library code and assumed my example wouldn't work. I apologize. I'm going to try all of the above listed format specifiers and report back on which ones don't work.

ecorm commented 3 years ago

They all work in Godbolt in C++11 mode, GCC 6.1. Sorry for the trouble.

When I was perusing the code, I saw report_no_date() on this line https://github.com/fmtlib/fmt/blob/7c43f8b896be886acd4e6e0ba61f71a8aa1ebc35/include/fmt/chrono.h#L702 and incorrectly assumed that calendar-related specifiers were not supported. I should have known better to test before raising the issue.