diff options
Diffstat (limited to 'drivers/rtc/lib.c')
| -rw-r--r-- | drivers/rtc/lib.c | 163 |
1 files changed, 113 insertions, 50 deletions
diff --git a/drivers/rtc/lib.c b/drivers/rtc/lib.c index ef160da84220..f7051592a6e3 100644 --- a/drivers/rtc/lib.c +++ b/drivers/rtc/lib.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * rtc and date/time utility functions * @@ -6,10 +7,8 @@ * * based on arch/arm/common/rtctime.c and other bits * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. -*/ + * Author: Cassio Neri <cassio.neri@gmail.com> (rtc_time64_to_tm) + */ #include <linux/export.h> #include <linux/rtc.h> @@ -25,8 +24,6 @@ static const unsigned short rtc_ydays[2][13] = { { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } }; -#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400) - /* * The number of days in the month. */ @@ -41,47 +38,111 @@ EXPORT_SYMBOL(rtc_month_days); */ int rtc_year_days(unsigned int day, unsigned int month, unsigned int year) { - return rtc_ydays[is_leap_year(year)][month] + day-1; + return rtc_ydays[is_leap_year(year)][month] + day - 1; } EXPORT_SYMBOL(rtc_year_days); - -/* - * rtc_time64_to_tm - Converts time64_t to rtc_time. - * Convert seconds since 01-01-1970 00:00:00 to Gregorian date. +/** + * rtc_time64_to_tm - converts time64_t to rtc_time. + * + * @time: The number of seconds since 01-01-1970 00:00:00. + * Works for values since at least 1900 + * @tm: Pointer to the struct rtc_time. */ void rtc_time64_to_tm(time64_t time, struct rtc_time *tm) { - unsigned int month, year, secs; - int days; - - /* time must be positive */ - days = div_s64_rem(time, 86400, &secs); - - /* day of the week, 1970-01-01 was a Thursday */ - tm->tm_wday = (days + 4) % 7; - - year = 1970 + days / 365; - days -= (year - 1970) * 365 - + LEAPS_THRU_END_OF(year - 1) - - LEAPS_THRU_END_OF(1970 - 1); - while (days < 0) { - year -= 1; - days += 365 + is_leap_year(year); - } - tm->tm_year = year - 1900; - tm->tm_yday = days + 1; - - for (month = 0; month < 11; month++) { - int newdays; - - newdays = days - rtc_month_days(month, year); - if (newdays < 0) - break; - days = newdays; - } - tm->tm_mon = month; - tm->tm_mday = days + 1; + int secs; + + u64 u64tmp; + u32 u32tmp, udays, century, day_of_century, year_of_century, year, + day_of_year, month, day; + bool is_Jan_or_Feb, is_leap_year; + + /* + * The time represented by `time` is given in seconds since 1970-01-01 + * (UTC). As the division done below might misbehave for negative + * values, we convert it to seconds since 0000-03-01 and then assume it + * will be non-negative. + * Below we do 4 * udays + 3 which should fit into a 32 bit unsigned + * variable. So the latest date this algorithm works for is 1073741823 + * days after 0000-03-01 which is in the year 2939805. + */ + time += (u64)719468 * 86400; + + udays = div_s64_rem(time, 86400, &secs); + + /* + * day of the week, 0000-03-01 was a Wednesday (in the proleptic + * Gregorian calendar) + */ + tm->tm_wday = (udays + 3) % 7; + + /* + * The following algorithm is, basically, Figure 12 of Neri + * and Schneider [1]. In a few words: it works on the computational + * (fictitious) calendar where the year starts in March, month = 2 + * (*), and finishes in February, month = 13. This calendar is + * mathematically convenient because the day of the year does not + * depend on whether the year is leap or not. For instance: + * + * March 1st 0-th day of the year; + * ... + * April 1st 31-st day of the year; + * ... + * January 1st 306-th day of the year; (Important!) + * ... + * February 28th 364-th day of the year; + * February 29th 365-th day of the year (if it exists). + * + * After having worked out the date in the computational calendar + * (using just arithmetics) it's easy to convert it to the + * corresponding date in the Gregorian calendar. + * + * [1] Neri C, Schneider L. Euclidean affine functions and their + * application to calendar algorithms. Softw Pract Exper. + * 2023;53(4):937-970. doi: 10.1002/spe.3172 + * https://doi.org/10.1002/spe.3172 + * + * (*) The numbering of months follows rtc_time more closely and + * thus, is slightly different from [1]. + */ + + u32tmp = 4 * udays + 3; + century = u32tmp / 146097; + day_of_century = u32tmp % 146097 / 4; + + u32tmp = 4 * day_of_century + 3; + u64tmp = 2939745ULL * u32tmp; + year_of_century = upper_32_bits(u64tmp); + day_of_year = lower_32_bits(u64tmp) / 2939745 / 4; + + year = 100 * century + year_of_century; + is_leap_year = year_of_century != 0 ? + year_of_century % 4 == 0 : century % 4 == 0; + + u32tmp = 2141 * day_of_year + 132377; + month = u32tmp >> 16; + day = ((u16) u32tmp) / 2141; + + /* + * Recall that January 01 is the 306-th day of the year in the + * computational (not Gregorian) calendar. + */ + is_Jan_or_Feb = day_of_year >= 306; + + /* Converts to the Gregorian calendar. */ + year = year + is_Jan_or_Feb; + month = is_Jan_or_Feb ? month - 12 : month; + day = day + 1; + + day_of_year = is_Jan_or_Feb ? + day_of_year - 306 : day_of_year + 31 + 28 + is_leap_year; + + /* Converts to rtc_time's format. */ + tm->tm_year = (int) (year - 1900); + tm->tm_mon = (int) month; + tm->tm_mday = (int) day; + tm->tm_yday = (int) day_of_year + 1; tm->tm_hour = secs / 3600; secs -= tm->tm_hour * 3600; @@ -97,13 +158,15 @@ EXPORT_SYMBOL(rtc_time64_to_tm); */ int rtc_valid_tm(struct rtc_time *tm) { - if (tm->tm_year < 70 - || ((unsigned)tm->tm_mon) >= 12 - || tm->tm_mday < 1 - || tm->tm_mday > rtc_month_days(tm->tm_mon, tm->tm_year + 1900) - || ((unsigned)tm->tm_hour) >= 24 - || ((unsigned)tm->tm_min) >= 60 - || ((unsigned)tm->tm_sec) >= 60) + if (tm->tm_year < 70 || + 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, + ((unsigned int)tm->tm_year + 1900)) || + ((unsigned int)tm->tm_hour) >= 24 || + ((unsigned int)tm->tm_min) >= 60 || + ((unsigned int)tm->tm_sec) >= 60) return -EINVAL; return 0; @@ -116,8 +179,8 @@ EXPORT_SYMBOL(rtc_valid_tm); */ time64_t rtc_tm_to_time64(struct rtc_time *tm) { - return mktime64(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); + return mktime64(((unsigned int)tm->tm_year + 1900), tm->tm_mon + 1, + tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } EXPORT_SYMBOL(rtc_tm_to_time64); |
