From 222edb8edd44876f7dce6319c78aa3152c58adb5 Mon Sep 17 00:00:00 2001 From: Olly Betts Date: Mon, 1 Jul 2024 17:18:13 +1200 Subject: [PATCH] Workaround mktime() if it rejects years < 1970 Microsoft's mktime() rejects years before 1970, unlike most (all?) other implementations. We can workaround this by off-setting the year, calling mktime(), then adjusting the result. --- src/img.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/img.c b/src/img.c index e4126744..3dfad393 100644 --- a/src/img.c +++ b/src/img.c @@ -234,6 +234,42 @@ mktime_with_tz(struct tm * tm, const char * tz) #endif tzset(); r = mktime(tm); + if (r == (time_t)-1 && tm->tm_year < 70 && + (sizeof(time_t) > 4 || tm->tm_year >= 1)) { + /* Microsoft's mktime() treats years before 1970 as an error, unlike + * most other implementations. + * + * We workaround this to support older years by calling mktime() for a + * date offset such that it's after 1970 (but before 3000 which is the + * highest year Microsoft's mktime() handles, and also before 2038 for + * 32-bit time_t), and that the leap year pattern matches. For 32-bit + * time_t we just need to add a multiple of 4, but for 64-bit time_t + * we need to add a multiple of 400. + * + * We require the year to be >= 1901 for 32-bit time_t since the + * oldest representable date in signed 32-bit time_t after this + * so there's no point retrying anything older: + * + * Fri 13 Dec 1901 20:45:52 UTC + * + * For larger time_t we support any year which fits in an int. This + * is somewhat dubious before the adoption of the Gregorian calendar + * but it matches what most mktime() implementations seem to do. + */ + int y = tm->tm_year; + int y_offset = sizeof(time_t) > 4 ? + ((-1 - y) / 400 + 1) * 400 : + (76 - y) & ~3; + tm->tm_year = y + y_offset; + r = mktime(tm); + tm->tm_year = y; + if (r != (time_t)-1) { + // The two magic numbers are the average number of seconds in a + // year for a 400 year cycle and for a 4 year cycle (one which + // includes a leap year). + r -= y_offset * (time_t)(sizeof(time_t) > 4 ? 31556952 : 31557600); + } + } if (old_tz) { #ifdef _MSC_VER _putenv_s("TZ", old_tz); -- 2.11.4.GIT