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