diff options
Diffstat (limited to 'kernel/time/timeconv.c')
| -rw-r--r-- | kernel/time/timeconv.c | 128 | 
1 files changed, 70 insertions, 58 deletions
| diff --git a/kernel/time/timeconv.c b/kernel/time/timeconv.c index 62e3b46717a6..59b922c826e7 100644 --- a/kernel/time/timeconv.c +++ b/kernel/time/timeconv.c @@ -22,47 +22,16 @@  /*   * Converts the calendar time to broken-down time representation - * Based on code from glibc-2.6   *   * 2009-7-14:   *   Moved from glibc-2.6 to kernel by Zhaolei<zhaolei@cn.fujitsu.com> + * 2021-06-02: + *   Reimplemented by Cassio Neri <cassio.neri@gmail.com>   */  #include <linux/time.h>  #include <linux/module.h> - -/* - * Nonzero if YEAR is a leap year (every 4 years, - * except every 100th isn't, and every 400th is). - */ -static int __isleap(long year) -{ -	return (year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0); -} - -/* do a mathdiv for long type */ -static long math_div(long a, long b) -{ -	return a / b - (a % b < 0); -} - -/* How many leap years between y1 and y2, y1 must less or equal to y2 */ -static long leaps_between(long y1, long y2) -{ -	long leaps1 = math_div(y1 - 1, 4) - math_div(y1 - 1, 100) -		+ math_div(y1 - 1, 400); -	long leaps2 = math_div(y2 - 1, 4) - math_div(y2 - 1, 100) -		+ math_div(y2 - 1, 400); -	return leaps2 - leaps1; -} - -/* How many days come before each month (0-12). */ -static const unsigned short __mon_yday[2][13] = { -	/* Normal years. */ -	{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, -	/* Leap years. */ -	{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} -}; +#include <linux/kernel.h>  #define SECS_PER_HOUR	(60 * 60)  #define SECS_PER_DAY	(SECS_PER_HOUR * 24) @@ -77,9 +46,11 @@ static const unsigned short __mon_yday[2][13] = {   */  void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)  { -	long days, rem, y; +	u32 u32tmp, day_of_century, year_of_century, day_of_year, month, day; +	u64 u64tmp, udays, century, year; +	bool is_Jan_or_Feb, is_leap_year; +	long days, rem;  	int remainder; -	const unsigned short *ip;  	days = div_s64_rem(totalsecs, SECS_PER_DAY, &remainder);  	rem = remainder; @@ -103,27 +74,68 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)  	if (result->tm_wday < 0)  		result->tm_wday += 7; -	y = 1970; - -	while (days < 0 || days >= (__isleap(y) ? 366 : 365)) { -		/* Guess a corrected year, assuming 365 days per year. */ -		long yg = y + math_div(days, 365); - -		/* Adjust DAYS and Y to match the guessed year. */ -		days -= (yg - y) * 365 + leaps_between(y, yg); -		y = yg; -	} - -	result->tm_year = y - 1900; - -	result->tm_yday = days; - -	ip = __mon_yday[__isleap(y)]; -	for (y = 11; days < ip[y]; y--) -		continue; -	days -= ip[y]; - -	result->tm_mon = y; -	result->tm_mday = days + 1; +	/* +	 * The following algorithm is, basically, Proposition 6.3 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] "Euclidean Affine Functions and Applications to Calendar +	 * Algorithms". https://arxiv.org/abs/2102.06959 +	 * +	 * (*) The numbering of months follows tm more closely and thus, +	 * is slightly different from [1]. +	 */ + +	udays	= ((u64) days) + 2305843009213814918ULL; + +	u64tmp		= 4 * udays + 3; +	century		= div64_u64_rem(u64tmp, 146097, &u64tmp); +	day_of_century	= (u32) (u64tmp / 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 ? !(year_of_century % 4) : !(century % 4); + +	u32tmp		= 2141 * day_of_year + 132377; +	month		= u32tmp >> 16; +	day		= ((u16) u32tmp) / 2141; + +	/* +	 * Recall that January 1st is the 306-th day of the year in the +	 * computational (not Gregorian) calendar. +	 */ +	is_Jan_or_Feb	= day_of_year >= 306; + +	/* Convert to the Gregorian calendar and adjust to Unix time. */ +	year		= year + is_Jan_or_Feb - 6313183731940000ULL; +	month		= is_Jan_or_Feb ? month - 12 : month; +	day		= day + 1; +	day_of_year	+= is_Jan_or_Feb ? -306 : 31 + 28 + is_leap_year; + +	/* Convert to tm's format. */ +	result->tm_year = (long) (year - 1900); +	result->tm_mon  = (int) month; +	result->tm_mday = (int) day; +	result->tm_yday = (int) day_of_year;  }  EXPORT_SYMBOL(time64_to_tm); | 
