]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[libc] Allow out-of-range months in mktime() 1764/head
authorMichael Brown <mcb30@ipxe.org>
Thu, 30 Jul 2026 09:27:01 +0000 (10:27 +0100)
committerMichael Brown <mcb30@ipxe.org>
Thu, 30 Jul 2026 09:27:01 +0000 (10:27 +0100)
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>
src/core/time.c
src/tests/time_test.c

index 6d33f6caf036bc2362e1b798a506f692a6f331ca..f1ba22c93105e84553854f8950b6bce5c756a269 100644 (file)
@@ -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 @@ 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 ),
index 3bf01dd1d7e77c1786288d68060f99f041620015..4ce8a2f915c6c4d9e6e95f40ff3252c03dcf8fbe 100644 (file)
@@ -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 */