From 7cc5698094d6859b757d4d5a404ebaf0a852341e Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+yunusshaikh7@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:30:01 -0700 Subject: [PATCH] fix(libc): format years before 1900 in asctime on Windows asctime passed the guest tm to the host CRT. UCRT's asctime returns null and sets errno to EINVAL when tm_year is negative or tm_mday is past the end of the month (common_asctime_s in ucrt/time/asctime.cpp), so on Windows asctime of any time in the years 1000 to 1899, which gmtime converts, or of February 29 in a common year returned null. C17 7.27.3.1 defines the text for every year from 1000 to 9999 when each member is in its normal range (tm_mday 1 to 31), and glibc on the Linux build and FreeBSD 11's asctime (contrib/tzcode/stdtime/asctime.c) print it. Format the text with the C17 algorithm's format into a thread-local buffer that fits any member values. As in glibc and FreeBSD, a weekday or month outside its range is printed as ???, and the year is computed in 64 bits. --- core/libs/prx/libc/src/Time.cpp | 11 +++++++++-- core/libs/tests/GuestTimeConversion.cpp | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index b730a9066..be7c8cea4 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -17,6 +17,9 @@ namespace { +constexpr const char* WEEKDAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +constexpr const char* MONTH_NAMES[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +constexpr std::size_t ASCTIME_SIZE = sizeof("Sun Jan-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n"); constexpr std::int64_t SECONDS_PER_DAY = 86400; constexpr std::int64_t DAYS_FROM_MARCH_TO_YEAR_END = 306; @@ -308,8 +311,12 @@ size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char* } char* APS5_VABI asctime_nid_postfix(const GuestTm* timeptr) { - const std::tm host = toHostTm(*timeptr); - return std::asctime(&host); + static thread_local char result[ASCTIME_SIZE]; + const char* weekday = timeptr->tm_wday >= 0 && timeptr->tm_wday < 7 ? WEEKDAY_NAMES[timeptr->tm_wday] : "???"; + const char* month = timeptr->tm_mon >= 0 && timeptr->tm_mon < 12 ? MONTH_NAMES[timeptr->tm_mon] : "???"; + std::snprintf(result, sizeof(result), "%s %s%3d %.2d:%.2d:%.2d %lld\n", weekday, month, timeptr->tm_mday, + timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, 1900LL + timeptr->tm_year); + return result; } char* APS5_VABI ctime_nid_postfix(const int64_t* timer) { diff --git a/core/libs/tests/GuestTimeConversion.cpp b/core/libs/tests/GuestTimeConversion.cpp index e0027fb55..1989e7f63 100644 --- a/core/libs/tests/GuestTimeConversion.cpp +++ b/core/libs/tests/GuestTimeConversion.cpp @@ -19,6 +19,7 @@ GuestTm* APS5_VABI libc_localtime_nid_postfix(const std::int64_t*); GuestTm* APS5_VABI localtime_nid_postfix(const std::int64_t*); GuestTm* APS5_VABI gmtime_s_nid_postfix(const std::int64_t*, GuestTm*); GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t*, GuestTm*); +char* APS5_VABI asctime_nid_postfix(const GuestTm*); std::int64_t APS5_VABI mktime_nid_postfix(GuestTm*); std::size_t APS5_VABI strftime_nid_postfix(char*, std::size_t, const char*, const GuestTm*); void APS5_VABI tzset_nid_postfix(void); @@ -68,6 +69,17 @@ static void CheckConcurrent(Converter convert, BufferedConverter buffered, const Require(convert(&invalid) == nullptr, "Unrepresentable time was accepted"); } +static GuestTm UtcTime(std::int64_t timer) { + GuestTm utc{}; + Require(gmtime_s_nid_postfix(&timer, &utc) == &utc, "UTC conversion failed"); + return utc; +} + +static void CheckAsctime(const GuestTm& brokenDown, const char* expected) { + const char* text = asctime_nid_postfix(&brokenDown); + Require(text != nullptr && std::strcmp(text, expected) == 0, "asctime did not format the broken-down time"); +} + struct UtcCase { std::int64_t timer; int year; @@ -202,6 +214,14 @@ int main() { GuestTm scratch{}; Require(gmtime_s_nid_postfix(&underflow, &scratch) == nullptr, "Unrepresentable negative time was accepted"); + CheckAsctime(UtcTime(0), "Thu Jan 1 00:00:00 1970\n"); + CheckAsctime(UtcTime(-2208988801), "Sun Dec 31 23:59:59 1899\n"); + CheckAsctime(UtcTime(-30610224000), "Wed Jan 1 00:00:00 1000\n"); + GuestTm pastMonthEnd = UtcTime(983404800); + pastMonthEnd.tm_mon = 1; + pastMonthEnd.tm_mday = 29; + CheckAsctime(pastMonthEnd, "Thu Feb 29 00:00:00 2001\n"); + const std::int64_t epoch = 0; GuestTm local{}; std::memset(&local, 0xAA, sizeof(local));