xref: /freebsd/sys/compat/linuxkpi/common/src/linux_timesub.c (revision 5fad251f05634e3220d36d51f624d77b41bc01c1)
1*5fad251fSJean-Sébastien Pédron /*
2*5fad251fSJean-Sébastien Pédron  * This file is in the public domain, so clarified as of
3*5fad251fSJean-Sébastien Pédron  * 1996-06-05 by Arthur David Olson.
4*5fad251fSJean-Sébastien Pédron  */
5*5fad251fSJean-Sébastien Pédron 
6*5fad251fSJean-Sébastien Pédron /*
7*5fad251fSJean-Sébastien Pédron  * The whole implementation of `timesub()` and its dependencies are copied from
8*5fad251fSJean-Sébastien Pédron  * `contrib/tzcode/localtime.c` as of FreeBSD commit
9*5fad251fSJean-Sébastien Pédron  * 28f617de7d9b9c708eacb3c2c13e5287e1b7354d.
10*5fad251fSJean-Sébastien Pédron  */
11*5fad251fSJean-Sébastien Pédron 
12*5fad251fSJean-Sébastien Pédron #include <linux/time.h>
13*5fad251fSJean-Sébastien Pédron 
14*5fad251fSJean-Sébastien Pédron enum {
15*5fad251fSJean-Sébastien Pédron   SECSPERMIN = 60,
16*5fad251fSJean-Sébastien Pédron   MINSPERHOUR = 60,
17*5fad251fSJean-Sébastien Pédron   SECSPERHOUR = SECSPERMIN * MINSPERHOUR,
18*5fad251fSJean-Sébastien Pédron   HOURSPERDAY = 24,
19*5fad251fSJean-Sébastien Pédron   DAYSPERWEEK = 7,
20*5fad251fSJean-Sébastien Pédron   DAYSPERNYEAR = 365,
21*5fad251fSJean-Sébastien Pédron   DAYSPERLYEAR = DAYSPERNYEAR + 1,
22*5fad251fSJean-Sébastien Pédron   MONSPERYEAR = 12,
23*5fad251fSJean-Sébastien Pédron   YEARSPERREPEAT = 400	/* years before a Gregorian repeat */
24*5fad251fSJean-Sébastien Pédron };
25*5fad251fSJean-Sébastien Pédron 
26*5fad251fSJean-Sébastien Pédron enum {
27*5fad251fSJean-Sébastien Pédron   TM_SUNDAY,
28*5fad251fSJean-Sébastien Pédron   TM_MONDAY,
29*5fad251fSJean-Sébastien Pédron   TM_TUESDAY,
30*5fad251fSJean-Sébastien Pédron   TM_WEDNESDAY,
31*5fad251fSJean-Sébastien Pédron   TM_THURSDAY,
32*5fad251fSJean-Sébastien Pédron   TM_FRIDAY,
33*5fad251fSJean-Sébastien Pédron   TM_SATURDAY
34*5fad251fSJean-Sébastien Pédron };
35*5fad251fSJean-Sébastien Pédron 
36*5fad251fSJean-Sébastien Pédron enum {
37*5fad251fSJean-Sébastien Pédron   TM_JANUARY,
38*5fad251fSJean-Sébastien Pédron   TM_FEBRUARY,
39*5fad251fSJean-Sébastien Pédron   TM_MARCH,
40*5fad251fSJean-Sébastien Pédron   TM_APRIL,
41*5fad251fSJean-Sébastien Pédron   TM_MAY,
42*5fad251fSJean-Sébastien Pédron   TM_JUNE,
43*5fad251fSJean-Sébastien Pédron   TM_JULY,
44*5fad251fSJean-Sébastien Pédron   TM_AUGUST,
45*5fad251fSJean-Sébastien Pédron   TM_SEPTEMBER,
46*5fad251fSJean-Sébastien Pédron   TM_OCTOBER,
47*5fad251fSJean-Sébastien Pédron   TM_NOVEMBER,
48*5fad251fSJean-Sébastien Pédron   TM_DECEMBER
49*5fad251fSJean-Sébastien Pédron };
50*5fad251fSJean-Sébastien Pédron 
51*5fad251fSJean-Sébastien Pédron enum {
52*5fad251fSJean-Sébastien Pédron   TM_YEAR_BASE = 1900,
53*5fad251fSJean-Sébastien Pédron   TM_WDAY_BASE = TM_MONDAY,
54*5fad251fSJean-Sébastien Pédron   EPOCH_YEAR = 1970,
55*5fad251fSJean-Sébastien Pédron   EPOCH_WDAY = TM_THURSDAY
56*5fad251fSJean-Sébastien Pédron };
57*5fad251fSJean-Sébastien Pédron 
58*5fad251fSJean-Sébastien Pédron #define SECSPERDAY	(SECSPERHOUR * HOURSPERDAY)
59*5fad251fSJean-Sébastien Pédron #define DAYSPERREPEAT	(400 * 365 + 100 - 4 + 1)
60*5fad251fSJean-Sébastien Pédron 
61*5fad251fSJean-Sébastien Pédron #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
62*5fad251fSJean-Sébastien Pédron 
63*5fad251fSJean-Sébastien Pédron #define TYPE_SIGNED(type) (((type) -1) < 0)
64*5fad251fSJean-Sébastien Pédron 
65*5fad251fSJean-Sébastien Pédron static const int	mon_lengths[2][MONSPERYEAR] = {
66*5fad251fSJean-Sébastien Pédron 	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
67*5fad251fSJean-Sébastien Pédron 	{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
68*5fad251fSJean-Sébastien Pédron };
69*5fad251fSJean-Sébastien Pédron 
70*5fad251fSJean-Sébastien Pédron static const int	year_lengths[2] = {
71*5fad251fSJean-Sébastien Pédron 	DAYSPERNYEAR, DAYSPERLYEAR
72*5fad251fSJean-Sébastien Pédron };
73*5fad251fSJean-Sébastien Pédron 
74*5fad251fSJean-Sébastien Pédron static time_t
leaps_thru_end_of_nonneg(time_t y)75*5fad251fSJean-Sébastien Pédron leaps_thru_end_of_nonneg(time_t y)
76*5fad251fSJean-Sébastien Pédron {
77*5fad251fSJean-Sébastien Pédron   return y / 4 - y / 100 + y / 400;
78*5fad251fSJean-Sébastien Pédron }
79*5fad251fSJean-Sébastien Pédron 
80*5fad251fSJean-Sébastien Pédron static time_t
leaps_thru_end_of(time_t y)81*5fad251fSJean-Sébastien Pédron leaps_thru_end_of(time_t y)
82*5fad251fSJean-Sébastien Pédron {
83*5fad251fSJean-Sébastien Pédron   return (y < 0
84*5fad251fSJean-Sébastien Pédron 	  ? -1 - leaps_thru_end_of_nonneg(-1 - y)
85*5fad251fSJean-Sébastien Pédron 	  : leaps_thru_end_of_nonneg(y));
86*5fad251fSJean-Sébastien Pédron }
87*5fad251fSJean-Sébastien Pédron 
88*5fad251fSJean-Sébastien Pédron static struct tm *
timesub(const time64_t * timep,int offset,struct tm * tmp)89*5fad251fSJean-Sébastien Pédron timesub(const time64_t *timep, int offset, struct tm *tmp)
90*5fad251fSJean-Sébastien Pédron {
91*5fad251fSJean-Sébastien Pédron 	time_t			tdays;
92*5fad251fSJean-Sébastien Pédron 	const int *		ip;
93*5fad251fSJean-Sébastien Pédron 	int corr;
94*5fad251fSJean-Sébastien Pédron 	int idays, rem, dayoff, dayrem;
95*5fad251fSJean-Sébastien Pédron 	time_t y;
96*5fad251fSJean-Sébastien Pédron 
97*5fad251fSJean-Sébastien Pédron 	/* If less than SECSPERMIN, the number of seconds since the
98*5fad251fSJean-Sébastien Pédron 	   most recent positive leap second; otherwise, do not add 1
99*5fad251fSJean-Sébastien Pédron 	   to localtime tm_sec because of leap seconds.  */
100*5fad251fSJean-Sébastien Pédron 	time_t secs_since_posleap = SECSPERMIN;
101*5fad251fSJean-Sébastien Pédron 
102*5fad251fSJean-Sébastien Pédron 	corr = 0;
103*5fad251fSJean-Sébastien Pédron 
104*5fad251fSJean-Sébastien Pédron 	/* Calculate the year, avoiding integer overflow even if
105*5fad251fSJean-Sébastien Pédron 	   time_t is unsigned.  */
106*5fad251fSJean-Sébastien Pédron 	tdays = *timep / SECSPERDAY;
107*5fad251fSJean-Sébastien Pédron 	rem = *timep % SECSPERDAY;
108*5fad251fSJean-Sébastien Pédron 	rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY;
109*5fad251fSJean-Sébastien Pédron 	dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3;
110*5fad251fSJean-Sébastien Pédron 	rem %= SECSPERDAY;
111*5fad251fSJean-Sébastien Pédron 	/* y = (EPOCH_YEAR
112*5fad251fSJean-Sébastien Pédron 		+ floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT),
113*5fad251fSJean-Sébastien Pédron 	   sans overflow.  But calculate against 1570 (EPOCH_YEAR -
114*5fad251fSJean-Sébastien Pédron 	   YEARSPERREPEAT) instead of against 1970 so that things work
115*5fad251fSJean-Sébastien Pédron 	   for localtime values before 1970 when time_t is unsigned.  */
116*5fad251fSJean-Sébastien Pédron 	dayrem = tdays % DAYSPERREPEAT;
117*5fad251fSJean-Sébastien Pédron 	dayrem += dayoff % DAYSPERREPEAT;
118*5fad251fSJean-Sébastien Pédron 	y = (EPOCH_YEAR - YEARSPERREPEAT
119*5fad251fSJean-Sébastien Pédron 	     + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT
120*5fad251fSJean-Sébastien Pédron 		 - ((dayrem % DAYSPERREPEAT) < 0)
121*5fad251fSJean-Sébastien Pédron 		 + tdays / DAYSPERREPEAT)
122*5fad251fSJean-Sébastien Pédron 		* YEARSPERREPEAT));
123*5fad251fSJean-Sébastien Pédron 	/* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow.  */
124*5fad251fSJean-Sébastien Pédron 	idays = tdays % DAYSPERREPEAT;
125*5fad251fSJean-Sébastien Pédron 	idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT;
126*5fad251fSJean-Sébastien Pédron 	idays %= DAYSPERREPEAT;
127*5fad251fSJean-Sébastien Pédron 	/* Increase Y and decrease IDAYS until IDAYS is in range for Y.  */
128*5fad251fSJean-Sébastien Pédron 	while (year_lengths[isleap(y)] <= idays) {
129*5fad251fSJean-Sébastien Pédron 		int tdelta = idays / DAYSPERLYEAR;
130*5fad251fSJean-Sébastien Pédron 		int_fast32_t ydelta = tdelta + !tdelta;
131*5fad251fSJean-Sébastien Pédron 		time_t newy = y + ydelta;
132*5fad251fSJean-Sébastien Pédron 		register int	leapdays;
133*5fad251fSJean-Sébastien Pédron 		leapdays = leaps_thru_end_of(newy - 1) -
134*5fad251fSJean-Sébastien Pédron 			leaps_thru_end_of(y - 1);
135*5fad251fSJean-Sébastien Pédron 		idays -= ydelta * DAYSPERNYEAR;
136*5fad251fSJean-Sébastien Pédron 		idays -= leapdays;
137*5fad251fSJean-Sébastien Pédron 		y = newy;
138*5fad251fSJean-Sébastien Pédron 	}
139*5fad251fSJean-Sébastien Pédron 
140*5fad251fSJean-Sébastien Pédron 	if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) {
141*5fad251fSJean-Sébastien Pédron 	  int signed_y = y;
142*5fad251fSJean-Sébastien Pédron 	  tmp->tm_year = signed_y - TM_YEAR_BASE;
143*5fad251fSJean-Sébastien Pédron 	} else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y)
144*5fad251fSJean-Sébastien Pédron 		   && y - TM_YEAR_BASE <= INT_MAX)
145*5fad251fSJean-Sébastien Pédron 	  tmp->tm_year = y - TM_YEAR_BASE;
146*5fad251fSJean-Sébastien Pédron 	else {
147*5fad251fSJean-Sébastien Pédron 	  return NULL;
148*5fad251fSJean-Sébastien Pédron 	}
149*5fad251fSJean-Sébastien Pédron 	tmp->tm_yday = idays;
150*5fad251fSJean-Sébastien Pédron 	/*
151*5fad251fSJean-Sébastien Pédron 	** The "extra" mods below avoid overflow problems.
152*5fad251fSJean-Sébastien Pédron 	*/
153*5fad251fSJean-Sébastien Pédron 	tmp->tm_wday = (TM_WDAY_BASE
154*5fad251fSJean-Sébastien Pédron 			+ ((tmp->tm_year % DAYSPERWEEK)
155*5fad251fSJean-Sébastien Pédron 			   * (DAYSPERNYEAR % DAYSPERWEEK))
156*5fad251fSJean-Sébastien Pédron 			+ leaps_thru_end_of(y - 1)
157*5fad251fSJean-Sébastien Pédron 			- leaps_thru_end_of(TM_YEAR_BASE - 1)
158*5fad251fSJean-Sébastien Pédron 			+ idays);
159*5fad251fSJean-Sébastien Pédron 	tmp->tm_wday %= DAYSPERWEEK;
160*5fad251fSJean-Sébastien Pédron 	if (tmp->tm_wday < 0)
161*5fad251fSJean-Sébastien Pédron 		tmp->tm_wday += DAYSPERWEEK;
162*5fad251fSJean-Sébastien Pédron 	tmp->tm_hour = rem / SECSPERHOUR;
163*5fad251fSJean-Sébastien Pédron 	rem %= SECSPERHOUR;
164*5fad251fSJean-Sébastien Pédron 	tmp->tm_min = rem / SECSPERMIN;
165*5fad251fSJean-Sébastien Pédron 	tmp->tm_sec = rem % SECSPERMIN;
166*5fad251fSJean-Sébastien Pédron 
167*5fad251fSJean-Sébastien Pédron 	/* Use "... ??:??:60" at the end of the localtime minute containing
168*5fad251fSJean-Sébastien Pédron 	   the second just before the positive leap second.  */
169*5fad251fSJean-Sébastien Pédron 	tmp->tm_sec += secs_since_posleap <= tmp->tm_sec;
170*5fad251fSJean-Sébastien Pédron 
171*5fad251fSJean-Sébastien Pédron 	ip = mon_lengths[isleap(y)];
172*5fad251fSJean-Sébastien Pédron 	for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
173*5fad251fSJean-Sébastien Pédron 		idays -= ip[tmp->tm_mon];
174*5fad251fSJean-Sébastien Pédron 	tmp->tm_mday = idays + 1;
175*5fad251fSJean-Sébastien Pédron 	tmp->tm_isdst = 0;
176*5fad251fSJean-Sébastien Pédron 	return tmp;
177*5fad251fSJean-Sébastien Pédron }
178*5fad251fSJean-Sébastien Pédron 
179*5fad251fSJean-Sébastien Pédron void
linuxkpi_time64_to_tm(time64_t totalsecs,int offset,struct tm * result)180*5fad251fSJean-Sébastien Pédron linuxkpi_time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
181*5fad251fSJean-Sébastien Pédron {
182*5fad251fSJean-Sébastien Pédron 	timesub(&totalsecs, offset, result);
183*5fad251fSJean-Sébastien Pédron }
184