Formerly ../time/mktime.c.~12~
[kopensolaris-gnu/glibc.git] / time / mktime.c
1 /* mktime.c */
2
3 /* Copyright (C) 1993 Free Software Foundation, Inc.
4
5 This file is part of the GNU Accounting Utilities
6
7 The GNU Accounting Utilities are free software; you can redistribute
8 them and/or modify them under the terms of the GNU General Public
9 License as published by the Free Software Foundation; either version
10 2, or (at your option) any later version.
11
12 The GNU Accounting Utilities are distributed in the hope that they will
13 be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with the GNU Accounting Utilities; see the file COPYING.  If
19 not, write to the Free Software Foundation, 675 Mass Ave, Cambridge,
20 MA 02139, USA.  */
21
22 /* provided for those systems that don't have their own.
23  * 
24  * after testing this, the maximum number of iterations that I had on
25  * any number that I tried was 3!  Not bad.
26  *
27  * -----------------------THE-LOWDOWN-ON-MKTIME-----------------------------
28  *
29  * time_t mktime (timeptr)
30  * struct tm *timeptr;
31  *
32  * --- the struct tm from the time.h header file
33  *
34  *     struct tm {
35  *             int tm_sec;     seconds (0 - 59)
36  *             int tm_min;     minutes (0 - 59)
37  *             int tm_hour;    hours (0 - 23)
38  *             int tm_mday;    day of month (1 - 31)
39  *             int tm_mon;     month of year (0 - 11)
40  *             int tm_year;    year - 1900
41  *             int tm_wday;    day of week (Sunday = 0)
42  *             int tm_yday;    day of year (0 - 365)
43  *             int tm_isdst;   daylight savings time flag
44  *             long tm_gmtoff; offset from GMT in seconds
45  *             char *tm_zone;  abbreviation of timezone name
46  *     };
47  *
48  *     tm_isdst is nonzero if DST is in effect.
49  *
50  *     tm_gmtoff is the offset (in seconds) of the time represented from GMT,
51  *     with positive values indicating East of Greenwich.
52  *
53  * --- description
54  *
55  * mktime converts a struct tm (broken-down local time) into a time_t
56  * -- it it is the opposite of localtime(3).  It is possible to put
57  * the following values out of range and have mktime compensate:
58  * tm_sec, tm_min, tm_hour, tm_mday, tm_year.  The other values in the
59  * structure are ignored.
60  *
61  * The manual claims that a negative value for tm_isdst makes mktime
62  * determine whether daylight savings time was in effect for the
63  * specified time, but through observation this does not seem to be
64  * the case.  mktime always returns the correct DST flag.
65  *
66  * The manual page ALSO claims that if a calendar time cannot be
67  * represented, it will return -1.  Not so.  I've tried the entire
68  * range of time_t (longs) on several systems and never had -1
69  * returned, save for Dec 31, 1969, 23:59:59... */
70
71 #include "config.h"
72
73 #ifndef HAVE_MKTIME
74
75 #include <stdio.h>
76 #include <time.h>
77 #include <sys/types.h>
78
79 /* #define DEBUG */    /* define this to have a standalone shell to test
80                         * this implementation of mktime
81                         */
82
83 static char rcsid[] = "$Id$";
84
85 static int times_through_search; /* this library routine should never
86                                     hang -- make sure we always return
87                                     when we're searching for a value */
88
89 #ifdef DEBUG
90
91 int debugging_enabled = 0;
92
93 /* print the values in a tm structure */
94 void
95 printtm (struct tm *it)
96 {
97   printf ("%d/%d/%d %d:%d:%d (%s) yday:%d f:%d o:%ld",
98           it->tm_mon,
99           it->tm_mday,
100           it->tm_year,
101           it->tm_hour,
102           it->tm_min,
103           it->tm_sec,
104           it->tm_zone,
105           it->tm_yday,
106           it->tm_isdst,
107           it->tm_gmtoff);
108 }
109 #endif
110
111
112 /* return 1 if the YR is a leap year
113  *
114  * actually, it's a Steve Tibbets album
115  */
116 static
117 int
118 leap_year (int yr)
119 {
120   int year = yr + 1900;
121   
122   if ((year % 400) == 0)
123     return 1;
124   else if (((year % 4) == 0) && ((year % 100) != 0))
125     return 1;
126   else
127     return 0;
128 }
129
130
131 /* not pretty, but it's fast */
132
133 static
134 time_t
135 olddist_tm (struct tm *t1, struct tm *t2)
136 {
137   time_t distance = 0;          /* linear distance in seconds */
138   int diff_flag = 0;
139
140 #define doit(x,secs); \
141   if (t1->x != t2->x) diff_flag = 1; \
142   distance -= (t2->x - t1->x) * secs;
143   
144   doit (tm_year, 31536000);
145   doit (tm_yday, 86400);
146   doit (tm_hour, 3600);
147   doit (tm_min, 60);
148   doit (tm_sec, 1);
149   
150 #undef doit
151   
152   /* we need this DIFF_FLAG business because it is forseeable that
153    * the distance may be zero when, in actuality, the two structures
154    * are different.  This is usually the case when the dates are
155    * 366 days apart and one of the years is a leap year */
156
157   if ((distance == 0) && diff_flag)
158     distance += 86400;
159
160   return distance;
161 }
162       
163
164 static
165 time_t
166 dist_tm (struct tm *t1, struct tm *t2)
167 {
168   time_t distance = 0;
169   unsigned long v1, v2;
170   int diff_flag = 0;
171
172   v1 = v2 = 0;
173
174 #define doit(x,secs); \
175   v1 += t1->x * secs; \
176   v2 += t2->x * secs; \
177   if (!diff_flag) \
178   { \
179     if (t1->x < t2->x) \
180       { \
181         diff_flag = -1; \
182       } \
183     else if (t1->x > t2->x) \
184       { \
185         diff_flag = 1; \
186       } \
187   }
188   
189   doit (tm_year, 31536000);     /* okay, not all years have 365 days */
190   doit (tm_mon, 2592000);       /* okay, not all months have 30 days */
191   doit (tm_mday, 86400);
192   doit (tm_hour, 3600);
193   doit (tm_min, 60);
194   doit (tm_sec, 1);
195   
196 #undef doit
197   
198   /* we should also make sure that the sign of DISTANCE is correct --
199    * if DIFF_FLAG is positive, the distance should be positive and
200    * vice versa. */
201   
202   distance = (v1 > v2) ? (v1 - v2) : (v2 - v1);
203   if (diff_flag < 0)
204     distance = -distance;
205
206   if (times_through_search > 20) /* arbitrary # of calls, but makes
207                                     sure we never hang if there's a
208                                     problem with this algorithm */
209     {
210       distance = diff_flag;
211     }
212
213   /* we need this DIFF_FLAG business because it is forseeable that
214    * the distance may be zero when, in actuality, the two structures
215    * are different.  This is usually the case when the dates are
216    * 366 days apart and one of the years is a leap year */
217
218   if ((distance == 0) && diff_flag) distance = 86400 * diff_flag;
219
220   return distance;
221 }
222       
223
224 /* modified b-search -- make intelligent guesses as to where the time
225  * might lie along the timeline, assuming that our target time lies a
226  * linear distance (w/o considering time jumps of a particular region)
227  *
228  * assume that time does not fluctuate at all along the timeline --
229  * e.g., assume that a day will always take 86400 seconds, etc. -- and
230  * come up with a hypothetical value for the time_t representation of
231  * the struct tm TARGET, in relation to the guess variable -- it should
232  * be pretty close! */
233
234 static
235 time_t
236 search (struct tm *target)
237 {
238   struct tm *guess_tm;
239   time_t guess = 0;
240   time_t distance;
241
242   times_through_search = 0;
243
244   for (;;)
245     {
246       guess_tm = localtime (&guess);
247       
248 #ifdef DEBUG
249       if (debugging_enabled)
250         {
251           printf ("guess %d == ", guess);
252           printtm (guess_tm);
253           printf ("\n");
254         }
255 #endif
256       
257       /* are we on the money? */
258       distance = dist_tm (target, guess_tm);
259       
260       if (distance == 0)
261         {
262           /* yes, we got it!  Get out of here! */
263           return guess;
264         }
265
266       guess += distance;
267
268       times_through_search++;
269     }
270 }
271
272 /* since this function will call localtime many times (and the user might
273  * be passing their struct tm * right from localtime, let's make a copy
274  * for ourselves and run the search on the copy.
275  *
276  * also, we have to normalize the timeptr because it's possible to call mktime
277  * with values that are out of range for a specific item (like 30th Feb)...
278  */
279 #ifdef DEBUG
280 time_t
281 my_mktime (struct tm *timeptr)
282 #else
283 time_t
284 mktime (struct tm *timeptr)
285 #endif
286 {
287   struct tm private_mktime_struct_tm; /* yes, users can get a ptr to this */
288   struct tm *me;
289   time_t result;
290
291   int month_data[2][12] = {
292     { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
293     { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
294   };
295
296   me = &private_mktime_struct_tm;
297   
298   memcpy ((void *) me, (void *) timeptr, sizeof (struct tm));
299
300 #define normalize(foo,x,y,bar); \
301   while (me->foo < x) \
302     { \
303       me->bar--; \
304       me->foo = (y - (x - me->foo)); \
305     } \
306   while (me->foo > y) \
307     { \
308       me->bar++; \
309       me->foo = (x + (me->foo - y)); \
310     }
311   
312   normalize (tm_sec,0,59,tm_min);
313   normalize (tm_min,0,59,tm_hour);
314   normalize (tm_hour,0,23,tm_mday);
315   
316   /* do month first, so day range can be found */
317   normalize (tm_mon,0,11,tm_year);
318   normalize (tm_mday,1,month_data[leap_year (me->tm_year)][me->tm_mon],tm_mon);
319
320   /* do month again, because day may have pushed it out of range */
321   normalize (tm_mon,0,11,tm_year);
322
323   /* do day again, because month may have changed the range */
324   normalize (tm_mday,1,month_data[leap_year (me->tm_year)][me->tm_mon],tm_mon);
325   
326 #ifdef DEBUG
327   if (debugging_enabled)
328     {
329       printf ("After normalizing: ");
330       printtm (me);
331       printf ("\n\n");
332     }
333 #endif
334
335   result = search (me);
336
337   return result;
338 }
339
340
341 #ifdef DEBUG
342 void
343 main (int argc, char *argv[])
344 {
345   int time;
346   int result_time;
347   struct tm *tmptr;
348   
349   if (argc == 1)
350     {
351       long q;
352       
353       printf ("starting long test...\n");
354
355       for (q = 10000000; q < 1000000000; q++)
356         {
357           struct tm *tm = localtime (&q);
358           if ((q % 10000) == 0) { printf ("%ld\n", q); fflush (stdout); }
359           if (q != my_mktime (tm))
360             { printf ("failed for %ld\n", q); fflush (stdout); }
361         }
362       
363       printf ("test finished\n");
364
365       exit (0);
366     }
367   
368   if (argc != 2)
369     {
370       printf ("wrong # of args\n");
371       exit (0);
372     }
373   
374   debugging_enabled = 1;        /* we want to see the info */
375
376   ++argv;
377   time = atoi (*argv);
378   
379   printf ("Time: %d %s\n", time, ctime (&time));
380
381   tmptr = localtime (&time);
382   printf ("localtime returns: ");
383   printtm (tmptr);
384   printf ("\n");
385 #ifdef HAVE_MKTIME
386   printf ("system mktime: %d\n\n", mktime (tmptr));
387 #endif
388   printf ("  my mktime(): %d\n\n", my_mktime (tmptr));
389
390   tmptr->tm_sec -= 20;
391   tmptr->tm_min -= 20;
392   tmptr->tm_hour -= 20;
393   tmptr->tm_mday -= 20;
394   tmptr->tm_mon -= 20;
395   tmptr->tm_year -= 20;
396   tmptr->tm_gmtoff -= 20000;    /* this has no effect! */
397   tmptr->tm_zone = NULL;        /* nor does this! */
398   tmptr->tm_isdst = -1;
399
400   printf ("changed ranges: ");
401   printtm (tmptr);
402   printf ("\n\n");
403
404   result_time = my_mktime (tmptr);
405   printf ("\n  mine: %d\n", result_time);
406 #ifdef HAVE_MKTIME
407   printf ("system: %d\n", mktime (tmptr));
408 #endif
409 }
410 #endif /* DEBUG */
411
412 #endif /* HAVE_MKTIME */
413