diff --git a/src/catch2/catch_tostring.hpp b/src/catch2/catch_tostring.hpp index 67a7c1d2..5f84196f 100644 --- a/src/catch2/catch_tostring.hpp +++ b/src/catch2/catch_tostring.hpp @@ -634,7 +634,11 @@ struct ratio_string { #ifdef _MSC_VER std::tm timeInfo = {}; - gmtime_s(&timeInfo, &converted); + const auto err = gmtime_s(&timeInfo, &converted); + if ( err ) { + return "gmtime from provided timepoint has failed. This " + "happens e.g. with pre-1970 dates using Microsoft libc"; + } #else std::tm* timeInfo = std::gmtime(&converted); #endif diff --git a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp index e50e4c5e..e1904606 100644 --- a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp @@ -7,10 +7,13 @@ // SPDX-License-Identifier: BSL-1.0 #include +#include #include #include #include +#include + enum class EnumClass3 { Value1, Value2, Value3, Value4 }; struct UsesSentinel { @@ -95,3 +98,23 @@ TEMPLATE_TEST_CASE( "Stringifying char arrays with statically known sizes", TestType no_null_terminator[3] = { 'a', 'b', 'c' }; CHECK( ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s ); } + +TEST_CASE( "#2944 - Stringifying dates before 1970 should not crash", "[.approvals]" ) { + using Catch::Matchers::Equals; + using Days = std::chrono::duration>; + using SysDays = std::chrono::time_point; + Catch::StringMaker sm; + + // Check simple date first + const SysDays post1970{ Days{ 1 } }; + auto converted_post = sm.convert( post1970 ); + REQUIRE( converted_post == "1970-01-02T00:00:00Z" ); + + const SysDays pre1970{ Days{ -1 } }; + auto converted_pre = sm.convert( pre1970 ); + REQUIRE_THAT( + converted_pre, + Equals( "1969-12-31T00:00:00Z" ) || + Equals( "gmtime from provided timepoint has failed. This " + "happens e.g. with pre-1970 dates using Microsoft libc" ) ); +}