[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 <mcb30@ipxe.org>
diff --git a/src/core/time.c b/src/core/time.c index 6d33f6c..f1ba22c 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 <assert.h> #include <time.h> /** @file @@ -116,26 +117,45 @@ * @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 3bf01dd..4ce8a2f 100644 --- a/src/tests/time_test.c +++ b/src/tests/time_test.c
@@ -138,6 +138,12 @@ 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 @@ 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 */