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