From d73b5d42e9a2f5669a68905231cd2065ddb5e398 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 23 May 2025 20:41:32 +0900 Subject: [PATCH] Rewrite format_common_log --- src/util.cc | 149 ++++++++++++++++++++++++++++++++--------------- src/util.h | 31 ++++------ src/util_test.cc | 69 ++++++++++++---------- 3 files changed, 153 insertions(+), 96 deletions(-) diff --git a/src/util.cc b/src/util.cc index 4a7ddb4a..a2c051c1 100644 --- a/src/util.cc +++ b/src/util.cc @@ -239,53 +239,6 @@ char *http_date(char *res, time_t t) { return p; } -std::string common_log_date(time_t t) { - // 03/Jul/2014:00:19:38 +0900 - std::string res(26, 0); - common_log_date(&res[0], t); - return res; -} - -char *common_log_date(char *res, time_t t) { - struct tm tms; - - if (localtime_r(&t, &tms) == nullptr) { - return res; - } - - auto p = res; - - p = cpydig2(tms.tm_mday, p); - *p++ = '/'; - p = std::ranges::copy(MONTH[tms.tm_mon], p).out; - *p++ = '/'; - p = cpydig4(tms.tm_year + 1900, p); - *p++ = ':'; - p = cpydig2(tms.tm_hour, p); - *p++ = ':'; - p = cpydig2(tms.tm_min, p); - *p++ = ':'; - p = cpydig2(tms.tm_sec, p); - *p++ = ' '; - -#ifdef HAVE_STRUCT_TM_TM_GMTOFF - auto gmtoff = tms.tm_gmtoff; -#else // !HAVE_STRUCT_TM_TM_GMTOFF - auto gmtoff = nghttp2_timegm(&tms) - t; -#endif // !HAVE_STRUCT_TM_TM_GMTOFF - if (gmtoff >= 0) { - *p++ = '+'; - } else { - *p++ = '-'; - gmtoff = -gmtoff; - } - - p = cpydig2(gmtoff / 3600, p); - p = cpydig2((gmtoff % 3600) / 60, p); - - return p; -} - std::string format_iso8601(const std::chrono::system_clock::time_point &tp) { // 2014-11-15T12:58:24.741Z // 2014-11-15T12:58:24.741+09:00 @@ -411,6 +364,56 @@ StringRef format_iso8601_basic(char *out, return {out, p}; } + +StringRef format_common_log(char *out, + const std::chrono::system_clock::time_point &tp) { + return format_common_log(out, tp, get_current_time_zone()); +} + +StringRef format_common_log(char *out, + const std::chrono::system_clock::time_point &tp, + const std::chrono::time_zone *tz) { + auto t = std::chrono::floor(tp); + auto zt = std::chrono::zoned_time{tz, t}; + auto lt = zt.get_local_time(); + auto days = std::chrono::floor(lt); + auto ymd = std::chrono::year_month_day{days}; + + auto p = out; + + p = cpydig2(static_cast(ymd.day()), p); + *p++ = '/'; + p = std::ranges::copy(MONTH[static_cast(ymd.month()) - 1], p).out; + *p++ = '/'; + p = cpydig4(static_cast(ymd.year()), p); + *p++ = ':'; + + auto hms = std::chrono::hh_mm_ss{lt - days}; + + p = cpydig2(hms.hours().count(), p); + *p++ = ':'; + p = cpydig2(hms.minutes().count(), p); + *p++ = ':'; + p = cpydig2(hms.seconds().count(), p); + *p++ = ' '; + + auto sys_info = zt.get_info(); + auto gmtoff = + std::chrono::floor(sys_info.offset).count(); + if (gmtoff >= 0) { + *p++ = '+'; + } else { + *p++ = '-'; + gmtoff = -gmtoff; + } + + p = cpydig2(gmtoff / 60, p); + p = cpydig2(gmtoff % 60, p); + + *p = '\0'; + + return {out, p}; +} #else // !defined(HAVE_STD_CHRONO_TIME_ZONE) namespace { char *iso8601_date(char *out, const std::chrono::system_clock::time_point &tp) { @@ -522,6 +525,58 @@ format_iso8601_basic(char *out, *p = '\0'; return {out, p}; } + +namespace { +char *common_log_date(char *out, + const std::chrono::system_clock::time_point &tp) { + time_t t = + std::chrono::floor(tp.time_since_epoch()).count(); + struct tm tms; + + if (localtime_r(&t, &tms) == nullptr) { + return out; + } + + auto p = out; + + p = cpydig2(tms.tm_mday, p); + *p++ = '/'; + p = std::ranges::copy(MONTH[tms.tm_mon], p).out; + *p++ = '/'; + p = cpydig4(tms.tm_year + 1900, p); + *p++ = ':'; + p = cpydig2(tms.tm_hour, p); + *p++ = ':'; + p = cpydig2(tms.tm_min, p); + *p++ = ':'; + p = cpydig2(tms.tm_sec, p); + *p++ = ' '; + +# ifdef HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = tms.tm_gmtoff; +# else // !HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = nghttp2_timegm(&tms) - t; +# endif // !HAVE_STRUCT_TM_TM_GMTOFF + if (gmtoff >= 0) { + *p++ = '+'; + } else { + *p++ = '-'; + gmtoff = -gmtoff; + } + + p = cpydig2(gmtoff / 3600, p); + p = cpydig2((gmtoff % 3600) / 60, p); + + return p; +} +} // namespace + +StringRef format_common_log(char *out, + const std::chrono::system_clock::time_point &tp) { + auto p = common_log_date(out, tp); + *p = '\0'; + return {out, p}; +} #endif // !defined(HAVE_STD_CHRONO_TIME_ZONE) time_t parse_http_date(const StringRef &s) { diff --git a/src/util.h b/src/util.h index 1d83b559..cbadcd5b 100644 --- a/src/util.h +++ b/src/util.h @@ -499,14 +499,6 @@ std::string http_date(time_t t); // long. This function returns the one beyond the last position. char *http_date(char *res, time_t t); -// Returns given time |t| from epoch in Common Log format (e.g., -// 03/Jul/2014:00:19:38 +0900) -std::string common_log_date(time_t t); -// Writes given time |t| from epoch in Common Log format into the -// buffer pointed by |res|. The buffer must be at least 26 bytes -// long. This function returns the one beyond the last position. -char *common_log_date(char *res, time_t t); - time_t parse_http_date(const StringRef &s); // Parses time formatted as "MMM DD HH:MM:SS YYYY [GMT]" (e.g., Feb 3 @@ -917,17 +909,18 @@ std::vector split_str(const StringRef &s, char delim, size_t n); // Writes given time |tp| in Common Log format (e.g., // 03/Jul/2014:00:19:38 +0900) in buffer pointed by |out|. The buffer -// must be at least 27 bytes, including terminal NULL byte. Expected -// type of |tp| is std::chrono::time_point. This function returns -// StringRef wrapping the buffer pointed by |out|, and this string is -// terminated by NULL. -template StringRef format_common_log(char *out, const T &tp) { - auto t = - std::chrono::duration_cast(tp.time_since_epoch()); - auto p = common_log_date(out, t.count()); - *p = '\0'; - return StringRef{out, p}; -} +// must be at least 27 bytes, including terminal NULL byte. This +// function returns StringRef wrapping the buffer pointed by |out|, +// and this string is terminated by NULL. +StringRef format_common_log(char *out, + const std::chrono::system_clock::time_point &tp); + +#ifdef HAVE_STD_CHRONO_TIME_ZONE +// Works like above but with a given time zone. +StringRef format_common_log(char *out, + const std::chrono::system_clock::time_point &tp, + const std::chrono::time_zone *tz); +#endif // defined(HAVE_STD_CHRONO_TIME_ZONE) // Returns given time |tp| in ISO 8601 format (e.g., // 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00). diff --git a/src/util_test.cc b/src/util_test.cc index f15ca3f2..2f653191 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -655,70 +655,78 @@ void test_util_parse_http_date(void) { } void test_util_localtime_date(void) { - auto tz = getenv("TZ"); - if (tz) { - tz = strdup(tz); - } -#ifdef __linux__ - setenv("TZ", "NZST-12:00:00:00", 1); -#else // !__linux__ - setenv("TZ", ":Pacific/Auckland", 1); -#endif // !__linux__ - tzset(); - - assert_stdstring_equal("02/Oct/2001:00:34:56 +1200", - util::common_log_date(1001939696)); - - std::array common_buf; - - assert_stdsv_equal("02/Oct/2001:00:34:56 +1200"sv, - util::format_common_log( - common_buf.data(), std::chrono::system_clock::time_point( - std::chrono::seconds(1001939696)))); - - std::array iso8601_buf; + std::array buf; #ifdef HAVE_STD_CHRONO_TIME_ZONE assert_stdsv_equal( "2001-10-02T00:34:56.123+12:00"sv, - util::format_iso8601(iso8601_buf.data(), + util::format_iso8601(buf.data(), std::chrono::system_clock::time_point( std::chrono::milliseconds(1001939696123LL)), std::chrono::locate_zone("Pacific/Auckland"sv))); assert_stdsv_equal( "20011002T003456.123+1200"sv, - util::format_iso8601_basic(iso8601_buf.data(), + util::format_iso8601_basic(buf.data(), std::chrono::system_clock::time_point( std::chrono::milliseconds(1001939696123LL)), std::chrono::locate_zone("Pacific/Auckland"sv))); + assert_stdsv_equal( + "02/Oct/2001:00:34:56 +1200"sv, + util::format_common_log( + buf.data(), + std::chrono::system_clock::time_point(std::chrono::seconds(1001939696)), + std::chrono::locate_zone("Pacific/Auckland"sv))); + assert_stdsv_equal( "2001-10-01T12:34:56.123Z"sv, - util::format_iso8601(iso8601_buf.data(), + util::format_iso8601(buf.data(), std::chrono::system_clock::time_point( std::chrono::milliseconds(1001939696123LL)), std::chrono::locate_zone("GMT"sv))); assert_stdsv_equal( "20011001T123456.123Z"sv, - util::format_iso8601_basic(iso8601_buf.data(), + util::format_iso8601_basic(buf.data(), std::chrono::system_clock::time_point( std::chrono::milliseconds(1001939696123LL)), std::chrono::locate_zone("GMT"sv))); -#else // !defined(HAVE_STD_CHRONO_TIME_ZONE) + + assert_stdsv_equal( + "01/Oct/2001:12:34:56 +0000"sv, + util::format_common_log( + buf.data(), + std::chrono::system_clock::time_point(std::chrono::seconds(1001939696)), + std::chrono::locate_zone("GMT"sv))); +#else // !defined(HAVE_STD_CHRONO_TIME_ZONE) + auto tz = getenv("TZ"); + if (tz) { + tz = strdup(tz); + } +# ifdef __linux__ + setenv("TZ", "NZST-12:00:00:00", 1); +# else // !__linux__ + setenv("TZ", ":Pacific/Auckland", 1); +# endif // !__linux__ + tzset(); + assert_stdsv_equal( "2001-10-02T00:34:56.123+12:00"sv, - util::format_iso8601(iso8601_buf.data(), + util::format_iso8601(buf.data(), std::chrono::system_clock::time_point( std::chrono::milliseconds(1001939696123LL)))); assert_stdsv_equal( "20011002T003456.123+1200"sv, - util::format_iso8601_basic(iso8601_buf.data(), + util::format_iso8601_basic(buf.data(), std::chrono::system_clock::time_point( std::chrono::milliseconds(1001939696123LL)))); -#endif // !defined(HAVE_STD_CHRONO_TIME_ZONE) + + assert_stdsv_equal( + "02/Oct/2001:00:34:56 +1200"sv, + util::format_common_log(buf.data(), std::chrono::system_clock::time_point( + std::chrono::seconds(1001939696)))); if (tz) { setenv("TZ", tz, 1); @@ -727,6 +735,7 @@ void test_util_localtime_date(void) { unsetenv("TZ"); } tzset(); +#endif // !defined(HAVE_STD_CHRONO_TIME_ZONE) } void test_util_get_uint64(void) {