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