[PATCH v3 2/5] rtc: Add handling of pre-1970 dates in time conversion functions

Alexandre Mergnat
2025-04-11T12:36:36Z

Linux RTC subsystem's time conversion functions couldn't properly handle
dates before January 1, 1970 (negative time64_t values). This affected
offset calculations, causing incorrect time translations for RTCs with
pre-1970 base years like those using a 1900 epoch.

The original rtc_time64_to_tm() function produced incorrect dates for
pre-1970 inputs and rtc_valid_tm() rejected all years before 1970 as
invalid, even if they were within the hardware's capabilities. For
example, converting January 1, 1942 2:36:47 is equal to -883603393.
converting it back resulted in wildly incorrect values
=> January 2, 1942 1193025:5:3.

These issues made it impossible to correctly use RTCs with pre-1970 base
years, particularly affecting embedded systems using hardware like the
MT6357 RTC.

Modify rtc_time64_to_tm to implement special handling for negative
time values, properly calculating days and seconds for dates before
1970. It also removes the tm_year < 70 restriction in rtc_valid_tm to
allow pre-1970 dates to be validated correctly, ensuring accurate
conversion between hardware and system time across the full range of
RTC hardware capabilities.

Signed-off-by: Alexandre Mergnat <amergnat@baylibre.com>
---
drivers/rtc/lib.c | 38 ++++++++++++++++++++++++++++++++------
1 file changed, 32 insertions(+), 6 deletions(-)

diff --git a/drivers/rtc/lib.c b/drivers/rtc/lib.c
index fe361652727a3..2014a86499e02 100644
--- a/drivers/rtc/lib.c
+++ b/drivers/rtc/lib.c
@@ -46,7 +46,6 @@ EXPORT_SYMBOL(rtc_year_days);
* rtc_time64_to_tm - converts time64_t to rtc_time.
*
* @time: The number of seconds since 01-01-1970 00:00:00.
- * (Must be positive.)
* @tm: Pointer to the struct rtc_time.
*/
void rtc_time64_to_tm(time64_t time, struct rtc_time *tm)
@@ -59,11 +58,39 @@ void rtc_time64_to_tm(time64_t time, struct rtc_time *tm)
day_of_year, month, day;
bool is_Jan_or_Feb, is_leap_year;

- /* time must be positive */
- days = div_s64_rem(time, 86400, &secs);
+ bool is_negative = false;
+
+ /* Handle negative time values (dates before 1970-01-01) */
+ if (time < 0) {
+ /* Store that we had a negative value */
+ is_negative = true;
+
+ /* Convert to positive value for the algorithm, but
+ * we'll subtract one more day to handle the boundary correctly
+ */
+ time = -time;
+
+ /* Get days and seconds */
+ days = div_s64_rem(time, 86400, &secs);
+
+ /* If we have seconds, we need to adjust to the previous day */
+ if (secs > 0) {
+ days += 1;
+ secs = 86400 - secs;
+ }
+
+ /* Make days negative again */
+ days = -days;
+ } else {
+ /* Positive time value - normal case */
+ days = div_s64_rem(time, 86400, &secs);
+ }

/* day of the week, 1970-01-01 was a Thursday */
tm->tm_wday = (days + 4) % 7;
+ /* Ensure tm_wday is always positive */
+ if (tm->tm_wday < 0)
+ tm->tm_wday += 7;

/*
* The following algorithm is, basically, Proposition 6.3 of Neri
@@ -93,7 +120,7 @@ void rtc_time64_to_tm(time64_t time, struct rtc_time *tm)
* thus, is slightly different from [1].
*/

- udays = ((u32) days) + 719468;
+ udays = days + 719468;

u32tmp = 4 * udays + 3;
century = u32tmp / 146097;
@@ -146,8 +173,7 @@ EXPORT_SYMBOL(rtc_time64_to_tm);
*/
int rtc_valid_tm(struct rtc_time *tm)
{
- if (tm->tm_year < 70 ||
- tm->tm_year > (INT_MAX - 1900) ||
+ if (tm->tm_year > (INT_MAX - 1900) ||
((unsigned int)tm->tm_mon) >= 12 ||
tm->tm_mday < 1 ||
tm->tm_mday > rtc_month_days(tm->tm_mon,

--
2.25.1