22
22
23
23
/*
24
24
* Converts the calendar time to broken-down time representation
25
- * Based on code from glibc-2.6
26
25
*
27
26
* 2009-7-14:
28
27
* Moved from glibc-2.6 to kernel by Zhaolei<[email protected] >
28
+ * 2021-06-02:
29
+ * Reimplemented by Cassio Neri <[email protected] >
29
30
*/
30
31
31
32
#include <linux/time.h>
32
33
#include <linux/module.h>
33
-
34
- /*
35
- * Nonzero if YEAR is a leap year (every 4 years,
36
- * except every 100th isn't, and every 400th is).
37
- */
38
- static int __isleap (long year )
39
- {
40
- return (year ) % 4 == 0 && ((year ) % 100 != 0 || (year ) % 400 == 0 );
41
- }
42
-
43
- /* do a mathdiv for long type */
44
- static long math_div (long a , long b )
45
- {
46
- return a / b - (a % b < 0 );
47
- }
48
-
49
- /* How many leap years between y1 and y2, y1 must less or equal to y2 */
50
- static long leaps_between (long y1 , long y2 )
51
- {
52
- long leaps1 = math_div (y1 - 1 , 4 ) - math_div (y1 - 1 , 100 )
53
- + math_div (y1 - 1 , 400 );
54
- long leaps2 = math_div (y2 - 1 , 4 ) - math_div (y2 - 1 , 100 )
55
- + math_div (y2 - 1 , 400 );
56
- return leaps2 - leaps1 ;
57
- }
58
-
59
- /* How many days come before each month (0-12). */
60
- static const unsigned short __mon_yday [2 ][13 ] = {
61
- /* Normal years. */
62
- {0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 , 365 },
63
- /* Leap years. */
64
- {0 , 31 , 60 , 91 , 121 , 152 , 182 , 213 , 244 , 274 , 305 , 335 , 366 }
65
- };
34
+ #include <linux/kernel.h>
66
35
67
36
#define SECS_PER_HOUR (60 * 60)
68
37
#define SECS_PER_DAY (SECS_PER_HOUR * 24)
@@ -77,9 +46,11 @@ static const unsigned short __mon_yday[2][13] = {
77
46
*/
78
47
void time64_to_tm (time64_t totalsecs , int offset , struct tm * result )
79
48
{
80
- long days , rem , y ;
49
+ u32 u32tmp , day_of_century , year_of_century , day_of_year , month , day ;
50
+ u64 u64tmp , udays , century , year ;
51
+ bool is_Jan_or_Feb , is_leap_year ;
52
+ long days , rem ;
81
53
int remainder ;
82
- const unsigned short * ip ;
83
54
84
55
days = div_s64_rem (totalsecs , SECS_PER_DAY , & remainder );
85
56
rem = remainder ;
@@ -103,27 +74,68 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
103
74
if (result -> tm_wday < 0 )
104
75
result -> tm_wday += 7 ;
105
76
106
- y = 1970 ;
107
-
108
- while (days < 0 || days >= (__isleap (y ) ? 366 : 365 )) {
109
- /* Guess a corrected year, assuming 365 days per year. */
110
- long yg = y + math_div (days , 365 );
111
-
112
- /* Adjust DAYS and Y to match the guessed year. */
113
- days -= (yg - y ) * 365 + leaps_between (y , yg );
114
- y = yg ;
115
- }
116
-
117
- result -> tm_year = y - 1900 ;
118
-
119
- result -> tm_yday = days ;
120
-
121
- ip = __mon_yday [__isleap (y )];
122
- for (y = 11 ; days < ip [y ]; y -- )
123
- continue ;
124
- days -= ip [y ];
125
-
126
- result -> tm_mon = y ;
127
- result -> tm_mday = days + 1 ;
77
+ /*
78
+ * The following algorithm is, basically, Proposition 6.3 of Neri
79
+ * and Schneider [1]. In a few words: it works on the computational
80
+ * (fictitious) calendar where the year starts in March, month = 2
81
+ * (*), and finishes in February, month = 13. This calendar is
82
+ * mathematically convenient because the day of the year does not
83
+ * depend on whether the year is leap or not. For instance:
84
+ *
85
+ * March 1st 0-th day of the year;
86
+ * ...
87
+ * April 1st 31-st day of the year;
88
+ * ...
89
+ * January 1st 306-th day of the year; (Important!)
90
+ * ...
91
+ * February 28th 364-th day of the year;
92
+ * February 29th 365-th day of the year (if it exists).
93
+ *
94
+ * After having worked out the date in the computational calendar
95
+ * (using just arithmetics) it's easy to convert it to the
96
+ * corresponding date in the Gregorian calendar.
97
+ *
98
+ * [1] "Euclidean Affine Functions and Applications to Calendar
99
+ * Algorithms". https://arxiv.org/abs/2102.06959
100
+ *
101
+ * (*) The numbering of months follows tm more closely and thus,
102
+ * is slightly different from [1].
103
+ */
104
+
105
+ udays = ((u64 ) days ) + 2305843009213814918ULL ;
106
+
107
+ u64tmp = 4 * udays + 3 ;
108
+ century = div64_u64_rem (u64tmp , 146097 , & u64tmp );
109
+ day_of_century = (u32 ) (u64tmp / 4 );
110
+
111
+ u32tmp = 4 * day_of_century + 3 ;
112
+ u64tmp = 2939745ULL * u32tmp ;
113
+ year_of_century = upper_32_bits (u64tmp );
114
+ day_of_year = lower_32_bits (u64tmp ) / 2939745 / 4 ;
115
+
116
+ year = 100 * century + year_of_century ;
117
+ is_leap_year = year_of_century ? !(year_of_century % 4 ) : !(century % 4 );
118
+
119
+ u32tmp = 2141 * day_of_year + 132377 ;
120
+ month = u32tmp >> 16 ;
121
+ day = ((u16 ) u32tmp ) / 2141 ;
122
+
123
+ /*
124
+ * Recall that January 1st is the 306-th day of the year in the
125
+ * computational (not Gregorian) calendar.
126
+ */
127
+ is_Jan_or_Feb = day_of_year >= 306 ;
128
+
129
+ /* Convert to the Gregorian calendar and adjust to Unix time. */
130
+ year = year + is_Jan_or_Feb - 6313183731940000ULL ;
131
+ month = is_Jan_or_Feb ? month - 12 : month ;
132
+ day = day + 1 ;
133
+ day_of_year += is_Jan_or_Feb ? -306 : 31 + 28 + is_leap_year ;
134
+
135
+ /* Convert to tm's format. */
136
+ result -> tm_year = (long ) (year - 1900 );
137
+ result -> tm_mon = (int ) month ;
138
+ result -> tm_mday = (int ) day ;
139
+ result -> tm_yday = (int ) day_of_year ;
128
140
}
129
141
EXPORT_SYMBOL (time64_to_tm );
0 commit comments