1 /*
2 Copyright (c) 1994 Cygnus Support.
3 All rights reserved.
4 
5 Redistribution and use in source and binary forms are permitted
6 provided that the above copyright notice and this paragraph are
7 duplicated in all such forms and that any documentation,
8 and/or other materials related to such
9 distribution and use acknowledge that the software was developed
10 at Cygnus Support, Inc.  Cygnus Support, Inc. may not be used to
11 endorse or promote products derived from this software without
12 specific prior written permission.
13 THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14 IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16  */
17 /*
18  * mktime.c
19  * Original Author:	G. Haley
20  *
21  * Converts the broken-down time, expressed as local time, in the structure
22  * pointed to by tim_p into a calendar time value. The original values of the
23  * tm_wday and tm_yday fields of the structure are ignored, and the original
24  * values of the other fields have no restrictions. On successful completion
25  * the fields of the structure are set to represent the specified calendar
26  * time. Returns the specified calendar time. If the calendar time can not be
27  * represented, returns the value (time_t) -1.
28  *
29  * Modifications:	Fixed tm_isdst usage - 27 August 2008 Craig Howland.
30  * 			Added timegm - 15 May 2021 R. Diez.
31  */
32 
33 /*
34 FUNCTION
35 <<mktime>>, <<timegm>>---convert time to arithmetic representation
36 
37 INDEX
38 	mktime
39 INDEX
40 	timegm
41 
42 SYNOPSIS
43 	#include <time.h>
44 	time_t mktime(struct tm *<[timp]>);
45 	time_t timegm(struct tm *<[timp]>);
46 
47 DESCRIPTION
48 <<mktime>> assumes the time at <[timp]> is a local time, and converts
49 its representation from the traditional representation defined by
50 <<struct tm>> into a representation suitable for arithmetic.
51 
52 <<localtime>> is the inverse of <<mktime>>.
53 
54 <<timegm>> is similar to <<mktime>>, but assumes that the time at
55 <[timp]> is Coordinated Universal Time (UTC).
56 
57 <<timegm>> could be emulated by setting the TZ environment variable to UTC,
58 calling <<mktime>> and restoring the value of TZ. However, other concurrent
59 threads could be affected by the temporary change to TZ.
60 
61 <<timegm>> is the inverse of <<gmtime>>.
62 
63 <<timegm>> is available if _BSD_SOURCE || _SVID_SOURCE || _DEFAULT_SOURCE.
64 
65 RETURNS
66 If the contents of the structure at <[timp]> do not form a valid
67 calendar time representation, the result is <<-1>>.  Otherwise, the
68 result is the time, converted to a <<time_t>> value.
69 
70 PORTABILITY
71 ANSI C requires <<mktime>>.
72 
73 <<timegm>> is a nonstandard GNU extension that is also present on the BSDs.
74 
75 <<mktime>> and <<timegm>> require no supporting OS subroutines.
76 */
77 
78 #define _DEFAULT_SOURCE
79 #include <stdlib.h>
80 #include <time.h>
81 #include "local.h"
82 
83 
84 #define _DAYS_IN_MONTH(x) ((x == 1) ? days_in_feb : __month_lengths[0][x])
85 
86 static const int16_t _DAYS_BEFORE_MONTH[12] =
87 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
88 
89 #define _DAYS_IN_YEAR(year) (isleap(year+YEAR_BASE) ? 366 : 365)
90 
91 static void
set_tm_wday(long days,struct tm * tim_p)92 set_tm_wday (long days, struct tm *tim_p)
93 {
94   if ((tim_p->tm_wday = (days + 4) % 7) < 0)
95     tim_p->tm_wday += 7;
96 }
97 
98 static void
validate_structure(struct tm * tim_p)99 validate_structure (struct tm *tim_p)
100 {
101   div_t res;
102   int days_in_feb = 28;
103 
104   /* calculate time & date to account for out of range values */
105   if (tim_p->tm_sec < 0 || tim_p->tm_sec > 59)
106     {
107       res = div (tim_p->tm_sec, 60);
108       tim_p->tm_min += res.quot;
109       if ((tim_p->tm_sec = res.rem) < 0)
110 	{
111 	  tim_p->tm_sec += 60;
112 	  --tim_p->tm_min;
113 	}
114     }
115 
116   if (tim_p->tm_min < 0 || tim_p->tm_min > 59)
117     {
118       res = div (tim_p->tm_min, 60);
119       tim_p->tm_hour += res.quot;
120       if ((tim_p->tm_min = res.rem) < 0)
121 	{
122 	  tim_p->tm_min += 60;
123 	  --tim_p->tm_hour;
124         }
125     }
126 
127   if (tim_p->tm_hour < 0 || tim_p->tm_hour > 23)
128     {
129       res = div (tim_p->tm_hour, 24);
130       tim_p->tm_mday += res.quot;
131       if ((tim_p->tm_hour = res.rem) < 0)
132 	{
133 	  tim_p->tm_hour += 24;
134 	  --tim_p->tm_mday;
135         }
136     }
137 
138   if (tim_p->tm_mon < 0 || tim_p->tm_mon > 11)
139     {
140       res = div (tim_p->tm_mon, 12);
141       tim_p->tm_year += res.quot;
142       if ((tim_p->tm_mon = res.rem) < 0)
143         {
144 	  tim_p->tm_mon += 12;
145 	  --tim_p->tm_year;
146         }
147     }
148 
149   if (isleap (tim_p->tm_year+YEAR_BASE))
150     days_in_feb = 29;
151 
152   if (tim_p->tm_mday <= 0)
153     {
154       while (tim_p->tm_mday <= 0)
155 	{
156 	  if (--tim_p->tm_mon == -1)
157 	    {
158 	      tim_p->tm_year--;
159 	      tim_p->tm_mon = 11;
160 	      days_in_feb =
161 		(isleap (tim_p->tm_year+YEAR_BASE) ?
162 		 29 : 28);
163 	    }
164 	  tim_p->tm_mday += _DAYS_IN_MONTH (tim_p->tm_mon);
165 	}
166     }
167   else
168     {
169       while (tim_p->tm_mday > _DAYS_IN_MONTH (tim_p->tm_mon))
170 	{
171 	  tim_p->tm_mday -= _DAYS_IN_MONTH (tim_p->tm_mon);
172 	  if (++tim_p->tm_mon == 12)
173 	    {
174 	      tim_p->tm_year++;
175 	      tim_p->tm_mon = 0;
176 	      days_in_feb =
177 		(isleap (tim_p->tm_year+YEAR_BASE) ?
178 		 29 : 28);
179 	    }
180 	}
181     }
182 }
183 
184 static time_t
mktime_utc(struct tm * tim_p,long * days_p)185 mktime_utc (struct tm *tim_p, long *days_p)
186 {
187   time_t tim = 0;
188   long days = 0;
189   int year;
190 
191   /* validate structure */
192   validate_structure (tim_p);
193 
194   /* compute hours, minutes, seconds */
195   tim += tim_p->tm_sec + (tim_p->tm_min * SECSPERMIN) +
196     (tim_p->tm_hour * SECSPERHOUR);
197 
198   /* compute days in year */
199   days += tim_p->tm_mday - 1;
200   days += _DAYS_BEFORE_MONTH[tim_p->tm_mon];
201   if (tim_p->tm_mon > 1 && isleap (tim_p->tm_year+YEAR_BASE))
202     days++;
203 
204   /* compute day of the year */
205   tim_p->tm_yday = days;
206 
207   if (tim_p->tm_year > 10000 || tim_p->tm_year < -10000)
208       return (time_t) -1;
209 
210   /* compute days in other years */
211   if ((year = tim_p->tm_year) > 70)
212     {
213       for (year = 70; year < tim_p->tm_year; year++)
214 	days += _DAYS_IN_YEAR (year);
215     }
216   else if (year < 70)
217     {
218       for (year = 69; year > tim_p->tm_year; year--)
219 	days -= _DAYS_IN_YEAR (year);
220       days -= _DAYS_IN_YEAR (year);
221     }
222 
223   /* compute total seconds */
224   tim += (time_t)days * SECSPERDAY;
225 
226   *days_p = days;
227   return tim;
228 }
229 
230 time_t
mktime(struct tm * tim_p)231 mktime (struct tm *tim_p)
232 {
233   long days;
234   time_t tim;
235   int year;
236   int isdst=0;
237   __tzinfo_type *tz;
238 
239   tim = mktime_utc (tim_p, &days);
240 
241   if (tim == (time_t) -1)
242     return tim;
243 
244   year = tim_p->tm_year;
245 
246   tz = __gettzinfo ();
247 
248   TZ_LOCK;
249 
250   _tzset_unlocked ();
251 
252   if (_daylight)
253     {
254       int tm_isdst;
255       int y = tim_p->tm_year + YEAR_BASE;
256       /* Convert user positive into 1 */
257       tm_isdst = tim_p->tm_isdst > 0  ?  1 : tim_p->tm_isdst;
258       isdst = tm_isdst;
259 
260       if (y == tz->__tzyear || __tzcalc_limits (y))
261 	{
262 	  /* calculate start of dst in dst local time and
263 	     start of std in both std local time and dst local time */
264           time_t startdst_dst = tz->__tzrule[0].change
265 	    - (time_t) tz->__tzrule[1].offset;
266 	  time_t startstd_dst = tz->__tzrule[1].change
267 	    - (time_t) tz->__tzrule[1].offset;
268 	  time_t startstd_std = tz->__tzrule[1].change
269 	    - (time_t) tz->__tzrule[0].offset;
270 	  /* if the time is in the overlap between dst and std local times */
271 	  if (tim >= startstd_std && tim < startstd_dst)
272 	    ; /* we let user decide or leave as -1 */
273           else
274 	    {
275 	      isdst = (tz->__tznorth
276 		       ? (tim >= startdst_dst && tim < startstd_std)
277 		       : (tim >= startdst_dst || tim < startstd_std));
278  	      /* if user committed and was wrong, perform correction, but not
279  	       * if the user has given a negative value (which
280  	       * asks mktime() to determine if DST is in effect or not) */
281  	      if (tm_isdst >= 0  &&  (isdst ^ tm_isdst) == 1)
282 		{
283 		  /* we either subtract or add the difference between
284 		     time zone offsets, depending on which way the user got it
285 		     wrong. The diff is typically one hour, or 3600 seconds,
286 		     and should fit in a 16-bit int, even though offset
287 		     is a long to accomodate 12 hours. */
288 		  int diff = (int) (tz->__tzrule[0].offset
289 				    - tz->__tzrule[1].offset);
290 		  if (!isdst)
291 		    diff = -diff;
292 		  tim_p->tm_sec += diff;
293 		  tim += diff;  /* we also need to correct our current time calculation */
294 		  int mday = tim_p->tm_mday;
295 		  validate_structure (tim_p);
296 		  mday = tim_p->tm_mday - mday;
297 		  /* roll over occurred */
298 		  if (mday) {
299 		    /* compensate for month roll overs */
300 		    if (mday > 1)
301 			  mday = -1;
302 		    else if (mday < -1)
303 			  mday = 1;
304 		    /* update days for wday calculation */
305 		    days += mday;
306 		    /* handle yday */
307 		    if ((tim_p->tm_yday += mday) < 0) {
308 			  --year;
309 			  tim_p->tm_yday = _DAYS_IN_YEAR(year) - 1;
310 		    } else {
311 			  mday = _DAYS_IN_YEAR(year);
312 			  if (tim_p->tm_yday > (mday - 1))
313 				tim_p->tm_yday -= mday;
314 		    }
315 		  }
316 		}
317 	    }
318 	}
319     }
320 
321   /* add appropriate offset to put time in gmt format */
322   if (isdst == 1)
323     tim += (time_t) tz->__tzrule[1].offset;
324   else /* otherwise assume std time */
325     tim += (time_t) tz->__tzrule[0].offset;
326 
327   TZ_UNLOCK;
328 
329   /* reset isdst flag to what we have calculated */
330   tim_p->tm_isdst = isdst;
331 
332   set_tm_wday(days, tim_p);
333 
334   return tim;
335 }
336 
337 time_t
timegm(struct tm * tim_p)338 timegm (struct tm *tim_p)
339 {
340   long days;
341   time_t tim = mktime_utc (tim_p, &days);
342 
343   if (tim == (time_t) -1)
344     return tim;
345 
346   /* The time is always UTC, so there is no daylight saving time. */
347   tim_p->tm_isdst = 0;
348 
349   set_tm_wday(days, tim_p);
350 
351   return tim;
352 }
353