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