xref: /netbsd-src/sys/dev/clock_subr.c (revision 6cf6fe02a981b55727c49c3d37b0d8191a98c0ee)
1 /*	$NetBSD: clock_subr.c,v 1.22 2014/09/07 11:50:23 martin Exp $	*/
2 
3 /*
4  * Copyright (c) 1988 University of Utah.
5  * Copyright (c) 1982, 1990, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * the Systems Programming Group of the University of Utah Computer
10  * Science Department.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  * from: Utah $Hdr: clock.c 1.18 91/01/21$
37  *
38  *	@(#)clock.c	8.2 (Berkeley) 1/12/94
39  */
40 
41 /*
42  * Generic routines to convert between a POSIX date
43  * (seconds since 1/1/1970) and yr/mo/day/hr/min/sec
44  * Derived from arch/hp300/hp300/clock.c
45  */
46 
47 #if HAVE_NBTOOL_CONFIG_H
48 #include "nbtool_config.h"
49 #endif /* HAVE_NBTOOL_CONFIG_H */
50 
51 #ifdef _KERNEL
52 #include <sys/cdefs.h>
53 __KERNEL_RCSID(0, "$NetBSD: clock_subr.c,v 1.22 2014/09/07 11:50:23 martin Exp $");
54 
55 #include <sys/param.h>
56 #include <sys/systm.h>
57 #include <sys/errno.h>
58 #else /* ! _KERNEL */
59 #include <string.h>
60 #include <time.h>
61 #include <errno.h>
62 #endif /* ! _KERNEL */
63 
64 #include <dev/clock_subr.h>
65 
66 static inline int leapyear(uint64_t year);
67 #define FEBRUARY	2
68 #define	days_in_year(a) 	(leapyear(a) ? 366 : 365)
69 #define	days_in_month(a) 	(month_days[(a) - 1])
70 
71 /* for easier alignment:
72  * time from the epoch to 2000 (there were 7 leap years): */
73 #define	DAYSTO2000	(365*30+7)
74 
75 /* 4 year intervals include 1 leap year */
76 #define	DAYS4YEARS	(365*4+1)
77 
78 /* 100 year intervals include 24 leap years */
79 #define	DAYS100YEARS	(365*100+24)
80 
81 /* 400 year intervals include 97 leap years */
82 #define	DAYS400YEARS	(365*400+97)
83 
84 static const int month_days[12] = {
85 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
86 };
87 
88 /*
89  * This inline avoids some unnecessary modulo operations
90  * as compared with the usual macro:
91  *   ( ((year % 4) == 0 &&
92  *      (year % 100) != 0) ||
93  *     ((year % 400) == 0) )
94  * It is otherwise equivalent.
95  */
96 static inline int
97 leapyear(uint64_t year)
98 {
99 	int rv = 0;
100 
101 	if (year < 1969)
102 		return EINVAL;
103 
104 	if ((year & 3) == 0) {
105 		rv = 1;
106 		if ((year % 100) == 0) {
107 			rv = 0;
108 			if ((year % 400) == 0)
109 				rv = 1;
110 		}
111 	}
112 	return rv;
113 }
114 
115 time_t
116 clock_ymdhms_to_secs(struct clock_ymdhms *dt)
117 {
118 	uint64_t secs, i, year, days;
119 
120 	year = dt->dt_year;
121 
122 	/*
123 	 * Compute days since start of time
124 	 * First from years, then from months.
125 	 */
126 	if (year < POSIX_BASE_YEAR)
127 		return -1;
128 	days = 0;
129 	if (leapyear(year) && dt->dt_mon > FEBRUARY)
130 		days++;
131 
132 	if (year < 2000) {
133 		/* simple way for early years */
134 		for (i = POSIX_BASE_YEAR; i < year; i++)
135 			days += days_in_year(i);
136 	} else {
137 		/* years are properly aligned */
138 		days += DAYSTO2000;
139 		year -= 2000;
140 
141 		i = year / 400;
142 		days += i * DAYS400YEARS;
143 		year -= i * 400;
144 
145 		i = year / 100;
146 		days += i * DAYS100YEARS;
147 		year -= i * 100;
148 
149 		i = year / 4;
150 		days += i * DAYS4YEARS;
151 		year -= i * 4;
152 
153 		for (i = dt->dt_year-year; i < dt->dt_year; i++)
154 			days += days_in_year(i);
155 	}
156 
157 
158 	/* Months */
159 	for (i = 1; i < dt->dt_mon; i++)
160 	  	days += days_in_month(i);
161 	days += (dt->dt_day - 1);
162 
163 	/* Add hours, minutes, seconds. */
164 	secs = (((uint64_t)days
165 	    * 24 + dt->dt_hour)
166 	    * 60 + dt->dt_min)
167 	    * 60 + dt->dt_sec;
168 
169 	if ((time_t)secs < 0 || secs > __type_max(time_t))
170 		return -1;
171 	return secs;
172 }
173 
174 int
175 clock_secs_to_ymdhms(time_t secs, struct clock_ymdhms *dt)
176 {
177 	int leap;
178 	uint64_t i;
179 	time_t days;
180 	time_t rsec;	/* remainder seconds */
181 
182 	if (secs < 0)
183 		return EINVAL;
184 
185 	days = secs / SECDAY;
186 	rsec = secs % SECDAY;
187 
188 	/* Day of week (Note: 1/1/1970 was a Thursday) */
189 	dt->dt_wday = (days + 4) % 7;
190 
191 	if (days >= DAYSTO2000) {
192 		days -= DAYSTO2000;
193 		dt->dt_year = 2000;
194 
195 		i = days / DAYS400YEARS;
196 		days -= i*DAYS400YEARS;
197 		dt->dt_year += i*400;
198 
199 		i = days / DAYS100YEARS;
200 		days -= i*DAYS100YEARS;
201 		dt->dt_year += i*100;
202 
203 		i = days / DAYS4YEARS;
204 		days -= i*DAYS4YEARS;
205 		dt->dt_year += i*4;
206 
207 		for (i = dt->dt_year; days >= days_in_year(i); i++)
208 			days -= days_in_year(i);
209 		dt->dt_year = i;
210 	} else {
211 		/* Subtract out whole years, counting them in i. */
212 		for (i = POSIX_BASE_YEAR; days >= days_in_year(i); i++)
213 			days -= days_in_year(i);
214 		dt->dt_year = i;
215 	}
216 
217 	/* Subtract out whole months, counting them in i. */
218 	for (leap = 0, i = 1; days >= days_in_month(i)+leap; i++) {
219 		days -= days_in_month(i)+leap;
220 		if (i == 1 && leapyear(dt->dt_year))
221 			leap = 1;
222 		else
223 			leap = 0;
224 	}
225 	dt->dt_mon = i;
226 
227 	/* Days are what is left over (+1) from all that. */
228 	dt->dt_day = days + 1;
229 
230 	/* Hours, minutes, seconds are easy */
231 	dt->dt_hour = rsec / 3600;
232 	rsec = rsec % 3600;
233 	dt->dt_min  = rsec / 60;
234 	rsec = rsec % 60;
235 	dt->dt_sec  = rsec;
236 
237 	return 0;
238 }
239