From: Michael Brown Date: Thu, 30 Jul 2026 09:27:01 +0000 (+0100) Subject: [libc] Allow out-of-range months in mktime() X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=fa779b1bce2331d286bfc8f70276fdad72ae9fa5;p=thirdparty%2Fipxe.git [libc] Allow out-of-range months in mktime() POSIX specifies that the values of members of the broken-down time structure are "not restricted to the ranges", and defines the way in which out-of-range values are to be handled. For most fields, the arithmetic is already purely linear and so out-of-range values are handled automatically. Out-of-range months are an exception: these are used as array indices and so must be normalised before use. Restructure mktime() to make it more immediately visible when values are being read from and written back to the broken-down time structure, add the required normalisation for the month number, and add test cases to cover out-of-range months. Signed-off-by: Michael Brown --- diff --git a/src/core/time.c b/src/core/time.c index 6d33f6caf..f1ba22c93 100644 --- a/src/core/time.c +++ b/src/core/time.c @@ -24,6 +24,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); FILE_SECBOOT ( PERMITTED ); +#include #include /** @file @@ -116,26 +117,45 @@ static const uint16_t days_to_month_start[] = * @ret time Seconds since the Epoch */ time_t mktime ( struct tm *tm ) { + int tm_year = tm->tm_year; + int tm_mon = tm->tm_mon; + int tm_mday = tm->tm_mday; + int tm_hour = tm->tm_hour; + int tm_min = tm->tm_min; + int tm_sec = tm->tm_sec; + int tm_yday; + int tm_wday; int days_since_epoch; int seconds_since_day; time_t seconds; + /* Normalize month for use as an array index */ + tm_year += ( tm_mon / 12 ); + tm_mon = ( tm_mon % 12 ); + if ( tm_mon < 0 ) { + tm_year--; + tm_mon += 12; + } + assert ( tm_mon >= 0 ); + assert ( tm_mon < 12 ); + /* Calculate day of year */ - tm->tm_yday = ( ( tm->tm_mday - 1 ) + - days_to_month_start[ tm->tm_mon ] ); - if ( ( tm->tm_mon >= 2 ) && is_leap_year ( tm->tm_year ) ) - tm->tm_yday++; + tm_yday = ( ( tm_mday - 1 ) + days_to_month_start[tm_mon] ); + if ( ( tm_mon >= 2 ) && is_leap_year ( tm_year ) ) + tm_yday++; + tm->tm_yday = tm_yday; /* Calculate day of week */ - tm->tm_wday = day_of_week ( tm->tm_year, tm->tm_mon, tm->tm_mday ); + tm_wday = day_of_week ( tm_year, tm_mon, tm_mday ); + tm->tm_wday = tm_wday; /* Calculate seconds since the Epoch */ - days_since_epoch = ( tm->tm_yday + ( 365 * tm->tm_year ) - 25567 + - leap_years_to_end ( tm->tm_year - 1 ) ); + days_since_epoch = ( tm_yday + ( 365 * tm_year ) - 25567 + + leap_years_to_end ( tm_year - 1 ) ); seconds_since_day = - ( ( ( ( tm->tm_hour * 60 ) + tm->tm_min ) * 60 ) + tm->tm_sec ); - seconds = ( ( ( ( time_t ) days_since_epoch ) * ( ( time_t ) 86400 ) ) + - seconds_since_day ); + ( ( ( ( tm_hour * 60 ) + tm_min ) * 60 ) + tm_sec ); + seconds = ( ( ( ( time_t ) days_since_epoch ) * ( ( time_t ) 86400 ) ) + + seconds_since_day ); DBGC ( &weekdays, "TIME %04d-%02d-%02d %02d:%02d:%02d => %lld (%s, " "day %d)\n", ( tm->tm_year + 1900 ), ( tm->tm_mon + 1 ), diff --git a/src/tests/time_test.c b/src/tests/time_test.c index 3bf01dd1d..4ce8a2f91 100644 --- a/src/tests/time_test.c +++ b/src/tests/time_test.c @@ -138,6 +138,12 @@ MKTIME_TEST ( mktime_29, 31, 20, 12, 24, 1, 146, 6, 54, 0, 2403087631ULL ); MKTIME_TEST ( mktime_30, 49, 7, 18, 16, 10, 271, 6, 319, 0, 6370596469ULL ); MKTIME_TEST ( mktime_31, 31, 55, 2, 25, 5, 141, 2, 175, 0, 2255741731ULL ); +/* Out-of-range month values */ +MKTIME_TEST ( mktime_32, 32, 55, 2, 25, 17, 140, 2, 175, 0, 2255741732ULL ); +MKTIME_TEST ( mktime_33, 33, 55, 2, 25, 65, 136, 2, 175, 0, 2255741733ULL ); +MKTIME_TEST ( mktime_34, 34, 55, 2, 25, -7, 142, 2, 175, 0, 2255741734ULL ); +MKTIME_TEST ( mktime_35, 35, 55, 2, 25, -55, 146, 2, 175, 0, 2255741735ULL ); + /** * Perform date and time self-tests * @@ -178,6 +184,10 @@ static void time_test_exec ( void ) { mktime_ok ( &mktime_29 ); mktime_ok ( &mktime_30 ); mktime_ok ( &mktime_31 ); + mktime_ok ( &mktime_32 ); + mktime_ok ( &mktime_33 ); + mktime_ok ( &mktime_34 ); + mktime_ok ( &mktime_35 ); } /** Date and time self-test */