1/*	$NetBSD: timegm.c,v 1.3 2005/05/11 01:01:56 lukem Exp $	*/
2/*	from	?	*/
3
4#include "tnftp.h"
5
6/*
7 * UTC version of mktime(3)
8 */
9
10/*
11 * This code is not portable, but works on most Unix-like systems.
12 * If the local timezone has no summer time, using mktime(3) function
13 * and adjusting offset would be usable (adjusting leap seconds
14 * is still required, though), but the assumption is not always true.
15 *
16 * Anyway, no portable and correct implementation of UTC to time_t
17 * conversion exists....
18 */
19
20static time_t
21sub_mkgmt(struct tm *tm)
22{
23	int y, nleapdays;
24	time_t t;
25	/* days before the month */
26	static const unsigned short moff[12] = {
27		0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
28	};
29
30	/*
31	 * XXX: This code assumes the given time to be normalized.
32	 * Normalizing here is impossible in case the given time is a leap
33	 * second but the local time library is ignorant of leap seconds.
34	 */
35
36	/* minimal sanity checking not to access outside of the array */
37	if ((unsigned) tm->tm_mon >= 12)
38		return (time_t) -1;
39	if (tm->tm_year < EPOCH_YEAR - TM_YEAR_BASE)
40		return (time_t) -1;
41
42	y = tm->tm_year + TM_YEAR_BASE - (tm->tm_mon < 2);
43	nleapdays = y / 4 - y / 100 + y / 400 -
44	    ((EPOCH_YEAR-1) / 4 - (EPOCH_YEAR-1) / 100 + (EPOCH_YEAR-1) / 400);
45	t = ((((time_t) (tm->tm_year - (EPOCH_YEAR - TM_YEAR_BASE)) * 365 +
46			moff[tm->tm_mon] + tm->tm_mday - 1 + nleapdays) * 24 +
47		tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec;
48
49	return (t < 0 ? (time_t) -1 : t);
50}
51
52time_t
53timegm(struct tm *tm)
54{
55	time_t t, t2;
56	struct tm *tm2;
57	int sec;
58
59	/* Do the first guess. */
60	if ((t = sub_mkgmt(tm)) == (time_t) -1)
61		return (time_t) -1;
62
63	/* save value in case *tm is overwritten by gmtime() */
64	sec = tm->tm_sec;
65
66	tm2 = gmtime(&t);
67	if ((t2 = sub_mkgmt(tm2)) == (time_t) -1)
68		return (time_t) -1;
69
70	if (t2 < t || tm2->tm_sec != sec) {
71		/*
72		 * Adjust for leap seconds.
73		 *
74		 *     real time_t time
75		 *           |
76		 *          tm
77		 *         /	... (a) first sub_mkgmt() conversion
78		 *       t
79		 *       |
80		 *      tm2
81		 *     /	... (b) second sub_mkgmt() conversion
82		 *   t2
83		 *			--->time
84		 */
85		/*
86		 * Do the second guess, assuming (a) and (b) are almost equal.
87		 */
88		t += t - t2;
89		tm2 = gmtime(&t);
90
91		/*
92		 * Either (a) or (b), may include one or two extra
93		 * leap seconds.  Try t, t + 2, t - 2, t + 1, and t - 1.
94		 */
95		if (tm2->tm_sec == sec
96		    || (t += 2, tm2 = gmtime(&t), tm2->tm_sec == sec)
97		    || (t -= 4, tm2 = gmtime(&t), tm2->tm_sec == sec)
98		    || (t += 3, tm2 = gmtime(&t), tm2->tm_sec == sec)
99		    || (t -= 2, tm2 = gmtime(&t), tm2->tm_sec == sec))
100			;	/* found */
101		else {
102			/*
103			 * Not found.
104			 */
105			if (sec >= 60)
106				/*
107				 * The given time is a leap second
108				 * (sec 60 or 61), but the time library
109				 * is ignorant of the leap second.
110				 */
111				;	/* treat sec 60 as 59,
112					   sec 61 as 0 of the next minute */
113			else
114				/* The given time may not be normalized. */
115				t++;	/* restore t */
116		}
117	}
118
119	return (t < 0 ? (time_t) -1 : t);
120}
121