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