Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

rtc: Make rtc_time64_to_tm() support dates before 1970

Conversion of dates before 1970 is still relevant today because these
dates are reused on some hardwares to store dates bigger than the
maximal date that is representable in the device's native format.
This prominently and very soon affects the hardware covered by the
rtc-mt6397 driver that can only natively store dates in the interval
1900-01-01 up to 2027-12-31. So to store the date 2028-01-01 00:00:00
to such a device, rtc_time64_to_tm() must do the right thing for
time=-2208988800.

Signed-off-by: Alexandre Mergnat <amergnat@baylibre.com>
Reviewed-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Link: https://lore.kernel.org/r/20250428-enable-rtc-v4-1-2b2f7e3f9349@baylibre.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>

authored by

Alexandre Mergnat and committed by
Alexandre Belloni
7df4cfef 1e15a680

+19 -5
+19 -5
drivers/rtc/lib.c
··· 46 46 * rtc_time64_to_tm - converts time64_t to rtc_time. 47 47 * 48 48 * @time: The number of seconds since 01-01-1970 00:00:00. 49 - * (Must be positive.) 49 + * Works for values since at least 1900 50 50 * @tm: Pointer to the struct rtc_time. 51 51 */ 52 52 void rtc_time64_to_tm(time64_t time, struct rtc_time *tm) 53 53 { 54 - unsigned int secs; 55 - int days; 54 + int days, secs; 56 55 57 56 u64 u64tmp; 58 57 u32 u32tmp, udays, century, day_of_century, year_of_century, year, 59 58 day_of_year, month, day; 60 59 bool is_Jan_or_Feb, is_leap_year; 61 60 62 - /* time must be positive */ 61 + /* 62 + * Get days and seconds while preserving the sign to 63 + * handle negative time values (dates before 1970-01-01) 64 + */ 63 65 days = div_s64_rem(time, 86400, &secs); 66 + 67 + /* 68 + * We need 0 <= secs < 86400 which isn't given for negative 69 + * values of time. Fixup accordingly. 70 + */ 71 + if (secs < 0) { 72 + days -= 1; 73 + secs += 86400; 74 + } 64 75 65 76 /* day of the week, 1970-01-01 was a Thursday */ 66 77 tm->tm_wday = (days + 4) % 7; 78 + /* Ensure tm_wday is always positive */ 79 + if (tm->tm_wday < 0) 80 + tm->tm_wday += 7; 67 81 68 82 /* 69 83 * The following algorithm is, basically, Proposition 6.3 of Neri ··· 107 93 * thus, is slightly different from [1]. 108 94 */ 109 95 110 - udays = ((u32) days) + 719468; 96 + udays = days + 719468; 111 97 112 98 u32tmp = 4 * udays + 3; 113 99 century = u32tmp / 146097;