diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index daa5b8b334d..eb6057a6ffb 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -385,6 +385,21 @@ BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30"); } + +BOOST_AUTO_TEST_CASE(util_FormatRFC1123DateTime) +{ + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(std::numeric_limits::max()), ""); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(253402300800), ""); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(253402300799), "Fri, 31 Dec 9999 23:59:59 GMT"); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(253402214400), "Fri, 31 Dec 9999 00:00:00 GMT"); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(1717429609), "Mon, 03 Jun 2024 15:46:49 GMT"); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(0), "Thu, 01 Jan 1970 00:00:00 GMT"); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(-1), "Wed, 31 Dec 1969 23:59:59 GMT"); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(-1717429609), "Sat, 31 Jul 1915 08:13:11 GMT"); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(-62167219200), "Sat, 01 Jan 0000 00:00:00 GMT"); + BOOST_CHECK_EQUAL(FormatRFC1123DateTime(-62167219201), ""); +} + BOOST_AUTO_TEST_CASE(util_FormatMoney) { BOOST_CHECK_EQUAL(FormatMoney(0), "0.00"); diff --git a/src/util/time.cpp b/src/util/time.cpp index 68af84a8fcc..9e0715e5250 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,9 @@ #include #include +static constexpr std::array weekdays{"Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed"}; // 1970-01-01 was a Thursday. +static constexpr std::array months{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + void UninterruptibleSleep(const std::chrono::microseconds& n) { std::this_thread::sleep_for(n); } static std::atomic g_mock_time{}; //!< For testing @@ -117,6 +121,24 @@ std::optional ParseISO8601DateTime(std::string_view str) return int64_t{TicksSinceEpoch(tp)}; } +std::string FormatRFC1123DateTime(int64_t time) +{ + if (time < -62167219200 || 253402300799 < time) { + // 4-digit year, so only support years 0 to 9999 + return ""; + } + const std::chrono::sys_seconds secs{std::chrono::seconds{time}}; + const auto days{std::chrono::floor(secs)}; + const auto w{days.time_since_epoch().count() % 7}; // will be in the range [-6, 6] + std::string_view weekday{weekdays.at(w >= 0 ? w : w + 7)}; + const std::chrono::year_month_day ymd{days}; + std::string_view month{months.at(unsigned{ymd.month()} - 1)}; + const std::chrono::hh_mm_ss hms{secs - days}; + // examples: Mon, 27 Jul 2009 12:28:53 GMT + // Fri, 31 May 2024 19:18:04 GMT + return strprintf("%03s, %02u %03s %04i %02i:%02i:%02i GMT", weekday, unsigned{ymd.day()}, month, signed{ymd.year()}, hms.hours().count(), hms.minutes().count(), hms.seconds().count()); +} + struct timeval MillisToTimeval(int64_t nTimeout) { struct timeval timeout; diff --git a/src/util/time.h b/src/util/time.h index 655db4475a5..0f98726d224 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -136,6 +136,12 @@ std::string FormatISO8601DateTime(int64_t nTime); std::string FormatISO8601Date(int64_t nTime); std::optional ParseISO8601DateTime(std::string_view str); +/** + * RFC1123 formatting https://www.rfc-editor.org/rfc/rfc1123#section-5.2.14 + * Used in HTTP/1.1 responses + */ +std::string FormatRFC1123DateTime(int64_t nTime); + /** * Convert milliseconds to a struct timeval for e.g. select. */