Added code to assure that TM_ISDST is -1, 0, or 1 in the
[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 by
3    Michael E. Calwas (calwas@ttd.teradyne.com) and
4    Wade Hampton (tasi029@tmn.com).
5
6 This file is part of the GNU C Library.
7
8 The GNU C Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
12
13 The GNU C Library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public
19 License along with the GNU C Library; see the file COPYING.LIB.  If
20 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
21 Cambridge, MA 02139, USA.  */
22
23 /* Define this to have a standalone program to test this implementation of
24    mktime.  */
25 /* #define DEBUG */
26
27 #ifdef HAVE_CONFIG_H
28 #if defined (CONFIG_BROKETS)
29 /* We use <config.h> instead of "config.h" so that a compilation
30    using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
31    (which it would do because it found this file in $srcdir).  */
32 #include <config.h>
33 #else
34 #include "config.h"
35 #endif
36 #endif
37
38 #include <sys/types.h>          /* Some systems define `time_t' here.  */
39 #include <time.h>
40
41
42 #ifndef __isleap
43 /* Nonzero if YEAR is a leap year (every 4 years,
44    except every 100th isn't, and every 400th is).  */
45 #define __isleap(year)  \
46   ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
47 #endif
48
49 #ifndef __P
50 #if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
51 #define __P(args) args
52 #else
53 #define __P(args) ()
54 #endif  /* GCC.  */
55 #endif  /* Not __P.  */
56
57 /* How many days are in each month.  */
58 const unsigned short int __mon_lengths[2][12] =
59   {
60     /* Normal years.  */
61     { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
62     /* Leap years.  */
63     { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
64   };
65
66
67 static int times_through_search; /* This library routine should never
68                                     hang -- make sure we always return
69                                     when we're searching for a value */
70
71
72 #ifdef DEBUG
73
74 #include <stdio.h>
75 #include <ctype.h>
76
77 int debugging_enabled = 0;
78
79 /* Print the values in a `struct tm'. */
80 static void
81 printtm (it)
82      struct tm *it;
83 {
84   printf ("%02d/%02d/%04d %02d:%02d:%02d (%s) yday:%03d dst:%d gmtoffset:%ld",
85           it->tm_mon + 1,
86           it->tm_mday,
87           it->tm_year + 1900,
88           it->tm_hour,
89           it->tm_min,
90           it->tm_sec,
91           it->tm_zone,
92           it->tm_yday,
93           it->tm_isdst,
94           it->tm_gmtoff);
95 }
96 #endif
97
98
99 static time_t
100 dist_tm (t1, t2)
101      struct tm *t1;
102      struct tm *t2;
103 {
104   time_t distance = 0;
105   unsigned long int v1, v2;
106   int diff_flag = 0;
107
108   v1 = v2 = 0;
109
110 #define doit(x, secs)                                                         \
111   v1 += t1->x * secs;                                                         \
112   v2 += t2->x * secs;                                                         \
113   if (!diff_flag)                                                             \
114     {                                                                         \
115       if (t1->x < t2->x)                                                      \
116         diff_flag = -1;                                                       \
117       else if (t1->x > t2->x)                                                 \
118         diff_flag = 1;                                                        \
119     }
120   
121   doit (tm_year, 31536000);     /* Okay, not all years have 365 days. */
122   doit (tm_mon, 2592000);       /* Okay, not all months have 30 days. */
123   doit (tm_mday, 86400);
124   doit (tm_hour, 3600);
125   doit (tm_min, 60);
126   doit (tm_sec, 1);
127   
128 #undef doit
129   
130   /* We should also make sure that the sign of DISTANCE is correct -- if
131      DIFF_FLAG is positive, the distance should be positive and vice versa. */
132   
133   distance = (v1 > v2) ? (v1 - v2) : (v2 - v1);
134   if (diff_flag < 0)
135     distance = -distance;
136
137   if (times_through_search > 20) /* Arbitrary # of calls, but makes sure we
138                                     never hang if there's a problem with
139                                     this algorithm.  */
140     {
141       distance = diff_flag;
142     }
143
144   /* We need this DIFF_FLAG business because it is forseeable that the
145      distance may be zero when, in actuality, the two structures are
146      different.  This is usually the case when the dates are 366 days apart
147      and one of the years is a leap year.  */
148
149   if (distance == 0 && diff_flag)
150     distance = 86400 * diff_flag;
151
152   return distance;
153 }
154       
155
156 /* MKTIME converts the values in a struct tm to a time_t.  The values
157    in tm_wday and tm_yday are ignored; other values can be put outside
158    of legal ranges since they will be normalized.  This routine takes
159    care of that normalization. */
160
161 void
162 do_normalization (tmptr)
163      struct tm *tmptr;
164 {
165
166 #define normalize(foo,x,y,bar); \
167   while (tmptr->foo < x) \
168     { \
169       tmptr->bar--; \
170       tmptr->foo = (y - (x - tmptr->foo) + 1); \
171     } \
172   while (tmptr->foo > y) \
173     { \
174       tmptr->foo = (x + (tmptr->foo - y) - 1); \
175       tmptr->bar++; \
176     }
177   
178   normalize (tm_sec, 0, 59, tm_min);
179   normalize (tm_min, 0, 59, tm_hour);
180   normalize (tm_hour, 0, 23, tm_mday);
181   
182   /* Do the month first, so day range can be found. */
183   normalize (tm_mon, 0, 11, tm_year);
184
185   /* Since the day range modifies the month, we should be careful how
186      we reference the array of month lengths -- it is possible that
187      the month will go negative, hence the modulo...
188
189      Also, tm_year is the year - 1900, so we have to 1900 to have it
190      work correctly. */
191
192   normalize (tm_mday, 1,
193              __mon_lengths[__isleap (tmptr->tm_year + 1900)]
194                           [((tmptr->tm_mon < 0)
195                             ? (12 + (tmptr->tm_mon % 12))
196                             : (tmptr->tm_mon % 12)) ],
197              tm_mon);
198
199   /* Do the month again, because the day may have pushed it out of range. */
200   normalize (tm_mon, 0, 11, tm_year);
201
202   /* Do the day again, because the month may have changed the range. */
203   normalize (tm_mday, 1,
204              __mon_lengths[__isleap (tmptr->tm_year + 1900)]
205                           [((tmptr->tm_mon < 0)
206                             ? (12 + (tmptr->tm_mon % 12))
207                             : (tmptr->tm_mon % 12)) ],
208              tm_mon);
209   
210 #ifdef DEBUG
211   if (debugging_enabled)
212     {
213       printf ("   After normalizing:\n     ");
214       printtm (tmptr);
215       putchar ('\n');
216     }
217 #endif
218
219 }
220
221
222 /* Here's where the work gets done. */
223
224 #define BAD_STRUCT_TM ((time_t) -1)
225
226 time_t
227 _mktime_internal (timeptr, producer)
228      struct tm *timeptr;
229      struct tm *(*producer) __P ((const time_t *));
230 {
231   struct tm our_tm;             /* our working space */
232   struct tm *me = &our_tm;      /* a pointer to the above */
233   time_t result;                /* the value we return */
234
235   *me = *timeptr;               /* copy the struct tm that was passed
236                                    in by the caller */
237
238
239   /***************************/
240   /* Normalize the structure */
241   /***************************/
242
243   /* This routine assumes that the value of TM_ISDST is -1, 0, or 1.
244      If the user didn't pass it in that way, fix it. */
245
246   if (me->tm_isdst > 0)
247     me->tm_isdst = 1;
248   else if (me->tm_isdst < 0)
249     me->tm_isdst = -1;
250
251   do_normalization (me);
252
253   /* Get out of here if it's not possible to represent this struct.
254      If any of the values in the normalized struct tm are negative,
255      our algorithms won't work.  Luckily, we only need to check the
256      year at this point; normalization guarantees that all values will
257      be in correct ranges EXCEPT the year. */
258
259   if (me->tm_year < 0)
260     return BAD_STRUCT_TM;
261
262   /*************************************************/
263   /* Find the appropriate time_t for the structure */
264   /*************************************************/
265
266   /* Modified b-search -- make intelligent guesses as to where the
267      time might lie along the timeline, assuming that our target time
268      lies a linear distance (w/o considering time jumps of a
269      particular region).
270
271      Assume that time does not fluctuate at all along the timeline --
272      e.g., assume that a day will always take 86400 seconds, etc. --
273      and come up with a hypothetical value for the time_t
274      representation of the struct tm TARGET, in relation to the guess
275      variable -- it should be pretty close!
276
277      After testing this, the maximum number of iterations that I had
278      on any number that I tried was 3!  Not bad.
279
280      The reason this is not a subroutine is that we will modify some
281      fields in the struct tm (yday and mday).  I've never felt good
282      about side-effects when writing structured code... */
283
284   {
285     struct tm *guess_tm;
286     time_t guess = 0;
287     time_t distance = 0;
288     time_t last_distance = 0;
289
290     times_through_search = 0;
291
292     do
293       {
294         guess += distance;
295
296         times_through_search++;     
297       
298         guess_tm = (*producer) (&guess);
299       
300 #ifdef DEBUG
301         if (debugging_enabled)
302           {
303             printf ("   Guessing time_t == %d\n     ", (int) guess);
304             printtm (guess_tm);
305             putchar ('\n');
306           }
307 #endif
308       
309         /* How far is our guess from the desired struct tm? */
310         distance = dist_tm (me, guess_tm);
311       
312         /* Handle periods of time where a period of time is skipped.
313            For example, 2:15 3 April 1994 does not exist, because DST
314            is in effect.  The distance function will alternately
315            return values of 3600 and -3600, because it doesn't know
316            that the requested time doesn't exist.  In these situations
317            (even if the skip is not exactly an hour) the distances
318            returned will be the same, but alternating in sign.  We
319            want the later time, so check to see that the distance is
320            oscillating and we've chosen the correct of the two
321            possibilities.
322
323            Useful: 3 Apr 94 765356300, 30 Oct 94 783496000 */
324
325         if ((distance == -last_distance) && (distance < last_distance))
326           {
327             /* If the caller specified that the DST flag was off, it's
328                not possible to represent this time. */
329             if (me->tm_isdst == 0)
330               {
331 #ifdef DEBUG
332             printf ("   Distance is oscillating -- dst flag nixes struct!\n");
333 #endif
334                 return BAD_STRUCT_TM;
335               }
336
337 #ifdef DEBUG
338             printf ("   Distance is oscillating -- chose the later time.\n");
339 #endif
340             distance = 0;
341           }
342
343         if ((distance == 0) && (me->tm_isdst != -1)
344             && (me->tm_isdst != guess_tm->tm_isdst))
345           {
346             /* If we're in this code, we've got the right time but the
347                wrong daylight savings flag.  We need to move away from
348                the time that we have and approach the other time from
349                the other direction.  That is, if I've requested the
350                non-DST version of a time and I get the DST version
351                instead, I want to put us forward in time and search
352                backwards to get the other time.  I checked all of the
353                configuration files for the tz package -- no entry
354                saves more than two hours, so I think we'll be safe by
355                moving 24 hours in one direction.  IF THE AMOUNT OF
356                TIME SAVED IN THE CONFIGURATION FILES CHANGES, THIS
357                VALUE MAY NEED TO BE ADJUSTED.  Luckily, we can never
358                have more than one level of overlaps, or this would
359                never work. */
360
361 #define SKIP_VALUE 86400
362
363             if (guess_tm->tm_isdst == 0)
364               /* we got the later one, but want the earlier one */
365               distance = -SKIP_VALUE;
366             else
367               distance = SKIP_VALUE;
368             
369 #ifdef DEBUG
370             printf ("   Got the right time, wrong DST value -- adjusting\n");
371 #endif
372           }
373
374         last_distance = distance;
375
376       } while (distance != 0);
377
378     /* Check to see that the dst flag matches */
379
380     if (me->tm_isdst != -1)
381       {
382         if (me->tm_isdst != guess_tm->tm_isdst)
383           {
384 #ifdef DEBUG
385             printf ("   DST flag doesn't match!  FIXME?\n");
386 #endif
387             return BAD_STRUCT_TM;
388           }
389       }
390
391     result = guess;             /* Success! */
392
393     /* On successful completion, the values of tm_wday and tm_yday
394        have to be set appropriately. */
395     
396     /* me->tm_yday = guess_tm->tm_yday; 
397        me->tm_mday = guess_tm->tm_mday; */
398
399     *me = *guess_tm;
400   }
401
402   /* Update the caller's version of the structure */
403
404   *timeptr = *me;
405
406   return result;
407 }
408
409 time_t
410 #ifdef DEBUG                    /* make it work even if the system's
411                                    libc has it's own mktime routine */
412 my_mktime (timeptr)
413 #else
414 mktime (timeptr)
415 #endif
416      struct tm *timeptr;
417 {
418   return _mktime_internal (timeptr, localtime);
419 }
420 \f
421 #ifdef DEBUG
422 void
423 main (argc, argv)
424      int argc;
425      char *argv[];
426 {
427   int time;
428   int result_time;
429   struct tm *tmptr;
430   
431   if (argc == 1)
432     {
433       long q;
434       
435       printf ("starting long test...\n");
436
437       for (q = 10000000; q < 1000000000; q += 599)
438         {
439           struct tm *tm = localtime ((time_t *) &q);
440           if ((q % 10000) == 0) { printf ("%ld\n", q); fflush (stdout); }
441           if (q != mktime (tm))
442             { printf ("failed for %ld\n", q); fflush (stdout); }
443         }
444       
445       printf ("test finished\n");
446
447       exit (0);
448     }
449   
450   if (argc != 2)
451     {
452       printf ("wrong # of args\n");
453       exit (0);
454     }
455   
456   debugging_enabled = 1;        /* We want to see the info */
457
458   ++argv;
459   time = atoi (*argv);
460   
461   tmptr = localtime ((time_t *) &time);
462   printf ("Localtime tells us that a time_t of %d represents\n     ", time);
463   printtm (tmptr);
464   putchar ('\n');
465
466   printf ("   Given localtime's return val, mktime returns %d which is\n     ",
467           (int) my_mktime (tmptr));
468   printtm (tmptr);
469   putchar ('\n');
470
471 #if 0
472   tmptr->tm_sec -= 20;
473   tmptr->tm_min -= 20;
474   tmptr->tm_hour -= 20;
475   tmptr->tm_mday -= 20;
476   tmptr->tm_mon -= 20;
477   tmptr->tm_year -= 20;
478   tmptr->tm_gmtoff -= 20000;    /* This has no effect! */
479   tmptr->tm_zone = NULL;        /* Nor does this! */
480   tmptr->tm_isdst = -1;
481 #endif
482   
483   tmptr->tm_hour += 1;
484   tmptr->tm_isdst = -1;
485
486   printf ("\n\nchanged ranges: ");
487   printtm (tmptr);
488   putchar ('\n');
489
490   result_time = my_mktime (tmptr);
491   printf ("\nmktime: %d\n", result_time);
492
493   tmptr->tm_isdst = 0;
494
495   printf ("\n\nchanged ranges: ");
496   printtm (tmptr);
497   putchar ('\n');
498
499   result_time = my_mktime (tmptr);
500   printf ("\nmktime: %d\n", result_time);
501 }
502 #endif /* DEBUG */
503
504 \f
505 /*
506 Local Variables:
507 compile-command: "gcc -g mktime.c -o mktime -DDEBUG"
508 End:
509 */