92a9a147926e8c9d27fbe2827a5e8132315dbcd1
[kopensolaris-gnu/glibc.git] / time / mktime.c
1 /* Copyright (C) 1993 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 DEBUG */    /* Define this to have a standalone shell to test
22                         * this implementation of mktime
23                         */
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
48 /* How many days are in each month.  */
49 const unsigned short int __mon_lengths[2][12] =
50   {
51     /* Normal years.  */
52     { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
53     /* Leap years.  */
54     { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
55   };
56
57
58 static int times_through_search; /* This library routine should never
59                                     hang -- make sure we always return
60                                     when we're searching for a value */
61
62 /* After testing this, the maximum number of iterations that I had on
63    any number that I tried was 3!  Not bad.
64
65    mktime converts a `struct tm' (broken-down local time) into a `time_t';
66    it is the opposite of localtime.  It is possible to put the following
67    values out of range and have mktime compensate: tm_sec, tm_min, tm_hour,
68    tm_mday, tm_year.  The other values in the structure are ignored.  */
69
70 #ifdef DEBUG
71
72 /* Print the values in a `struct tm'. */
73 static void
74 printtm (it)
75      struct tm *it;
76 {
77   printf ("%d/%d/%d %d:%d:%d (%s) yday:%d f:%d o:%ld",
78           it->tm_mon,
79           it->tm_mday,
80           it->tm_year,
81           it->tm_hour,
82           it->tm_min,
83           it->tm_sec,
84           it->tm_zone,
85           it->tm_yday,
86           it->tm_isdst,
87           it->tm_gmtoff);
88 }
89 #endif
90
91
92 static time_t
93 dist_tm (t1, t2)
94      struct tm *t1;
95      struct tm *t2;
96 {
97   time_t distance = 0;
98   unsigned long int v1, v2;
99   int diff_flag = 0;
100
101   v1 = v2 = 0;
102
103 #define doit(x, secs)                                                         \
104   v1 += t1->x * secs;                                                         \
105   v2 += t2->x * secs;                                                         \
106   if (!diff_flag)                                                             \
107     {                                                                         \
108       if (t1->x < t2->x)                                                      \
109         diff_flag = -1;                                                       \
110       else if (t1->x > t2->x)                                                 \
111         diff_flag = 1;                                                        \
112     }
113   
114   doit (tm_year, 31536000);     /* Okay, not all years have 365 days. */
115   doit (tm_mon, 2592000);       /* Okay, not all months have 30 days. */
116   doit (tm_mday, 86400);
117   doit (tm_hour, 3600);
118   doit (tm_min, 60);
119   doit (tm_sec, 1);
120   
121 #undef doit
122   
123   /* We should also make sure that the sign of DISTANCE is correct --
124    * if DIFF_FLAG is positive, the distance should be positive and
125    * vice versa. */
126   
127   distance = (v1 > v2) ? (v1 - v2) : (v2 - v1);
128   if (diff_flag < 0)
129     distance = -distance;
130
131   if (times_through_search > 20) /* Arbitrary # of calls, but makes
132                                     sure we never hang if there's a
133                                     problem with this algorithm */
134     {
135       distance = diff_flag;
136     }
137
138   /* We need this DIFF_FLAG business because it is forseeable that
139    * the distance may be zero when, in actuality, the two structures
140    * are different.  This is usually the case when the dates are
141    * 366 days apart and one of the years is a leap year. */
142
143   if ((distance == 0) && diff_flag)
144     distance = 86400 * diff_flag;
145
146   return distance;
147 }
148       
149
150 /* Modified b-search -- make intelligent guesses as to where the time
151  * might lie along the timeline, assuming that our target time lies a
152  * linear distance (w/o considering time jumps of a particular region).
153  *
154  * Assume that time does not fluctuate at all along the timeline --
155  * e.g., assume that a day will always take 86400 seconds, etc. -- and
156  * come up with a hypothetical value for the time_t representation of
157  * the struct tm TARGET, in relation to the guess variable -- it should
158  * be pretty close! */
159
160 static time_t
161 search (target)
162      struct tm *target;
163 {
164   struct tm *guess_tm;
165   time_t guess = 0;
166   time_t distance = 0;
167
168   times_through_search = 0;
169
170   do
171     {
172       guess += distance;
173
174       times_through_search++;     
175       
176       guess_tm = localtime (&guess);
177       
178 #ifdef DEBUG
179       printf ("guess %d == ", guess);
180       printtm (guess_tm);
181       puts ("");
182 #endif
183       
184       /* Are we on the money? */
185       distance = dist_tm (target, guess_tm);
186       
187     } while (distance != 0);
188
189   return guess;
190 }
191
192 /* Since this function will call localtime many times (and the user might
193  * be passing their `struct tm *' right from localtime, let's make a copy
194  * for ourselves and run the search on the copy.
195  *
196  * Also, we have to normalize the timeptr because it's possible to call mktime
197  * with values that are out of range for a specific item (like 30th Feb). */
198 time_t
199 mktime (timeptr)
200      struct tm *timeptr;
201 {
202   struct tm private_mktime_struct_tm; /* Yes, users can get a ptr to this. */
203   struct tm *me;
204   time_t result;
205
206   me = &private_mktime_struct_tm;
207   
208   *me = *timeptr;
209
210 #define normalize(foo,x,y,bar); \
211   while (me->foo < x) \
212     { \
213       me->bar--; \
214       me->foo = (y - (x - me->foo)); \
215     } \
216   while (me->foo > y) \
217     { \
218       me->bar++; \
219       me->foo = (x + (me->foo - y)); \
220     }
221   
222   normalize (tm_sec, 0, 59, tm_min);
223   normalize (tm_min, 0, 59, tm_hour);
224   normalize (tm_hour, 0, 23, tm_mday);
225   
226   /* Do the month first, so day range can be found. */
227   normalize (tm_mon, 0, 11, tm_year);
228   normalize (tm_mday, 1,
229              __mon_lengths[__isleap (me->tm_year)][me->tm_mon],
230              tm_mon);
231
232   /* Do the month again, because the day may have pushed it out of range. */
233   normalize (tm_mon, 0, 11, tm_year);
234
235   /* Do the day again, because the month may have changed the range. */
236   normalize (tm_mday, 1,
237              __mon_lengths[__isleap (me->tm_year)][me->tm_mon],
238              tm_mon);
239   
240 #ifdef DEBUG
241   printf ("After normalizing: ");
242   printtm (me);
243   puts ("\n");
244 #endif
245
246   result = search (me);
247
248   *timeptr = *me;
249
250   return result;
251 }
252 \f
253 #ifdef DEBUG
254 void
255 main (argc, argv)
256      int argc;
257      char *argv[];
258 {
259   int time;
260   int result_time;
261   struct tm *tmptr;
262   
263   if (argc == 1)
264     {
265       long q;
266       
267       printf ("starting long test...\n");
268
269       for (q = 10000000; q < 1000000000; q++)
270         {
271           struct tm *tm = localtime (&q);
272           if ((q % 10000) == 0) { printf ("%ld\n", q); fflush (stdout); }
273           if (q != mktime (tm))
274             { printf ("failed for %ld\n", q); fflush (stdout); }
275         }
276       
277       printf ("test finished\n");
278
279       exit (0);
280     }
281   
282   if (argc != 2)
283     {
284       printf ("wrong # of args\n");
285       exit (0);
286     }
287   
288   ++argv;
289   time = atoi (*argv);
290   
291   printf ("Time: %d %s\n", time, ctime (&time));
292
293   tmptr = localtime (&time);
294   printf ("localtime returns: ");
295   printtm (tmptr);
296   printf ("\n");
297   printf ("mktime: %d\n\n", mktime (tmptr));
298
299   tmptr->tm_sec -= 20;
300   tmptr->tm_min -= 20;
301   tmptr->tm_hour -= 20;
302   tmptr->tm_mday -= 20;
303   tmptr->tm_mon -= 20;
304   tmptr->tm_year -= 20;
305   tmptr->tm_gmtoff -= 20000;    /* This has no effect! */
306   tmptr->tm_zone = NULL;        /* Nor does this! */
307   tmptr->tm_isdst = -1;
308
309   printf ("changed ranges: ");
310   printtm (tmptr);
311   printf ("\n\n");
312
313   result_time = mktime (tmptr);
314   printf ("\nmktime: %d\n", result_time);
315 }
316 #endif /* DEBUG */