merge with 1.9.2i
[gnulib.git] / lib / mktime.c
1 /* Copyright (C) 1993, 1994 Free Software Foundation, Inc.
2    Contributed by Noel Cragg (noel@cs.oberlin.edu).
3
4 This file is part of the GNU C Library.
5
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public License as
8 published by the Free Software Foundation; either version 2 of the
9 License, or (at your option) any later version.
10
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public
17 License along with the GNU C Library; see the file COPYING.LIB.  If
18 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
19 Cambridge, MA 02139, USA.  */
20
21 /* Define this to have a standalone program to test this implementation of
22    mktime.  */
23 /* #define DEBUG */
24
25 #ifdef HAVE_CONFIG_H
26 #if defined (CONFIG_BROKETS)
27 /* We use <config.h> instead of "config.h" so that a compilation
28    using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
29    (which it would do because it found this file in $srcdir).  */
30 #include <config.h>
31 #else
32 #include "config.h"
33 #endif
34 #endif
35
36 #include <sys/types.h>          /* Some systems define `time_t' here.  */
37 #include <time.h>
38
39
40 #ifndef __isleap
41 /* Nonzero if YEAR is a leap year (every 4 years,
42    except every 100th isn't, and every 400th is).  */
43 #define __isleap(year)  \
44   ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
45 #endif
46
47 #ifndef __P
48 #if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
49 #define __P(args) args
50 #else
51 #define __P(args) ()
52 #endif  /* GCC.  */
53 #endif  /* Not __P.  */
54
55 /* How many days are in each month.  */
56 const unsigned short int __mon_lengths[2][12] =
57   {
58     /* Normal years.  */
59     { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
60     /* Leap years.  */
61     { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
62   };
63
64
65 static int times_through_search; /* This library routine should never
66                                     hang -- make sure we always return
67                                     when we're searching for a value */
68
69 /* After testing this, the maximum number of iterations that I had on
70    any number that I tried was 3!  Not bad.
71
72    mktime converts a `struct tm' (broken-down local time) into a `time_t';
73    it is the opposite of localtime.  It is possible to put the following
74    values out of range and have mktime compensate: tm_sec, tm_min, tm_hour,
75    tm_mday, tm_year.  The other values in the structure are ignored.  */
76
77 #ifdef DEBUG
78
79 #include <stdio.h>
80 #include <ctype.h>
81
82 int debugging_enabled = 0;
83
84 /* Print the values in a `struct tm'. */
85 static void
86 printtm (it)
87      struct tm *it;
88 {
89   printf ("%d/%d/%d %d:%d:%d (%s) yday:%d f:%d o:%ld",
90           it->tm_mon,
91           it->tm_mday,
92           it->tm_year,
93           it->tm_hour,
94           it->tm_min,
95           it->tm_sec,
96           it->tm_zone,
97           it->tm_yday,
98           it->tm_isdst,
99           it->tm_gmtoff);
100 }
101 #endif
102
103
104 static time_t
105 dist_tm (t1, t2)
106      struct tm *t1;
107      struct tm *t2;
108 {
109   time_t distance = 0;
110   unsigned long int v1, v2;
111   int diff_flag = 0;
112
113   v1 = v2 = 0;
114
115 #define doit(x, secs)                                                         \
116   v1 += t1->x * secs;                                                         \
117   v2 += t2->x * secs;                                                         \
118   if (!diff_flag)                                                             \
119     {                                                                         \
120       if (t1->x < t2->x)                                                      \
121         diff_flag = -1;                                                       \
122       else if (t1->x > t2->x)                                                 \
123         diff_flag = 1;                                                        \
124     }
125   
126   doit (tm_year, 31536000);     /* Okay, not all years have 365 days. */
127   doit (tm_mon, 2592000);       /* Okay, not all months have 30 days. */
128   doit (tm_mday, 86400);
129   doit (tm_hour, 3600);
130   doit (tm_min, 60);
131   doit (tm_sec, 1);
132   
133 #undef doit
134   
135   /* We should also make sure that the sign of DISTANCE is correct -- if
136      DIFF_FLAG is positive, the distance should be positive and vice versa. */
137   
138   distance = (v1 > v2) ? (v1 - v2) : (v2 - v1);
139   if (diff_flag < 0)
140     distance = -distance;
141
142   if (times_through_search > 20) /* Arbitrary # of calls, but makes sure we
143                                     never hang if there's a problem with
144                                     this algorithm.  */
145     {
146       distance = diff_flag;
147     }
148
149   /* We need this DIFF_FLAG business because it is forseeable that the
150      distance may be zero when, in actuality, the two structures are
151      different.  This is usually the case when the dates are 366 days apart
152      and one of the years is a leap year.  */
153
154   if (distance == 0 && diff_flag)
155     distance = 86400 * diff_flag;
156
157   return distance;
158 }
159       
160
161 /* Modified b-search -- make intelligent guesses as to where the time might
162    lie along the timeline, assuming that our target time lies a linear
163    distance (w/o considering time jumps of a particular region).
164
165    Assume that time does not fluctuate at all along the timeline -- e.g.,
166    assume that a day will always take 86400 seconds, etc. -- and come up
167    with a hypothetical value for the time_t representation of the struct tm
168    TARGET, in relation to the guess variable -- it should be pretty close! */
169
170 static time_t
171 search (target, producer)
172      struct tm *target;
173      struct tm *(*producer) __P ((const time_t *));
174 {
175   struct tm *guess_tm;
176   time_t guess = 0;
177   time_t distance = 0;
178
179   times_through_search = 0;
180
181   do
182     {
183       guess += distance;
184
185       times_through_search++;     
186       
187       guess_tm = (*producer) (&guess);
188       
189 #ifdef DEBUG
190       if (debugging_enabled)
191         {
192           printf ("guess %d == ", (int) guess);
193           printtm (guess_tm);
194           puts ("");
195         }
196 #endif
197       
198       /* Are we on the money? */
199       distance = dist_tm (target, guess_tm);
200       
201     } while (distance != 0);
202
203   return guess;
204 }
205
206 /* Since this function will call localtime many times (and the user might
207    be passing their `struct tm *' right from localtime, let's make a copy
208    for ourselves and run the search on the copy.
209
210    Also, we have to normalize *TIMEPTR because it's possible to call mktime
211    with values that are out of range for a specific item (like Feb 30th). */
212 time_t
213 _mktime_internal (timeptr, producer)
214      struct tm *timeptr;
215      struct tm *(*producer) __P ((const time_t *));
216 {
217   struct tm private_mktime_struct_tm; /* Yes, users can get a ptr to this. */
218   struct tm *me;
219   time_t result;
220
221   me = &private_mktime_struct_tm;
222   
223   *me = *timeptr;
224
225 #define normalize(foo,x,y,bar); \
226   while (me->foo < x) \
227     { \
228       me->bar--; \
229       me->foo = (y - (x - me->foo)); \
230     } \
231   while (me->foo > y) \
232     { \
233       me->bar++; \
234       me->foo = (x + (me->foo - y)); \
235     }
236   
237   normalize (tm_sec, 0, 59, tm_min);
238   normalize (tm_min, 0, 59, tm_hour);
239   normalize (tm_hour, 0, 23, tm_mday);
240   
241   /* Do the month first, so day range can be found. */
242   normalize (tm_mon, 0, 11, tm_year);
243   normalize (tm_mday, 1,
244              __mon_lengths[__isleap (me->tm_year)][me->tm_mon],
245              tm_mon);
246
247   /* Do the month again, because the day may have pushed it out of range. */
248   normalize (tm_mon, 0, 11, tm_year);
249
250   /* Do the day again, because the month may have changed the range. */
251   normalize (tm_mday, 1,
252              __mon_lengths[__isleap (me->tm_year)][me->tm_mon],
253              tm_mon);
254   
255 #ifdef DEBUG
256   if (debugging_enabled)
257     {
258       printf ("After normalizing: ");
259       printtm (me);
260       puts ("\n");
261     }
262 #endif
263
264   result = search (me, producer);
265
266   *timeptr = *me;
267
268   return result;
269 }
270
271 time_t
272 mktime (timeptr)
273      struct tm *timeptr;
274 {
275   return _mktime_internal (timeptr, localtime);
276 }
277 \f
278 #ifdef DEBUG
279 void
280 main (argc, argv)
281      int argc;
282      char *argv[];
283 {
284   int time;
285   int result_time;
286   struct tm *tmptr;
287   
288   if (argc == 1)
289     {
290       long q;
291       
292       printf ("starting long test...\n");
293
294       for (q = 10000000; q < 1000000000; q++)
295         {
296           struct tm *tm = localtime (&q);
297           if ((q % 10000) == 0) { printf ("%ld\n", q); fflush (stdout); }
298           if (q != mktime (tm))
299             { printf ("failed for %ld\n", q); fflush (stdout); }
300         }
301       
302       printf ("test finished\n");
303
304       exit (0);
305     }
306   
307   if (argc != 2)
308     {
309       printf ("wrong # of args\n");
310       exit (0);
311     }
312   
313   debugging_enabled = 1;        /* We want to see the info */
314
315   ++argv;
316   time = atoi (*argv);
317   
318   printf ("Time: %d %s\n", time, ctime ((time_t *) &time));
319
320   tmptr = localtime ((time_t *) &time);
321   printf ("localtime returns: ");
322   printtm (tmptr);
323   printf ("\n");
324   printf ("mktime: %d\n\n", (int) mktime (tmptr));
325
326   tmptr->tm_sec -= 20;
327   tmptr->tm_min -= 20;
328   tmptr->tm_hour -= 20;
329   tmptr->tm_mday -= 20;
330   tmptr->tm_mon -= 20;
331   tmptr->tm_year -= 20;
332   tmptr->tm_gmtoff -= 20000;    /* This has no effect! */
333   tmptr->tm_zone = NULL;        /* Nor does this! */
334   tmptr->tm_isdst = -1;
335
336   printf ("changed ranges: ");
337   printtm (tmptr);
338   printf ("\n\n");
339
340   result_time = mktime (tmptr);
341   printf ("\nmktime: %d\n", result_time);
342 }
343 #endif /* DEBUG */