Icons are changed
[gnuplot] / src / time.c
1 #ifndef lint
2 static char *RCSid() { return RCSid("$Id: time.c,v 1.19.2.2 2009/06/12 05:05:44 sfeam Exp $"); }
3 #endif
4
5 /* GNUPLOT - time.c */
6
7 /*[
8  * Copyright 1986 - 1993, 1998, 2004   Thomas Williams, Colin Kelley
9  *
10  * Permission to use, copy, and distribute this software and its
11  * documentation for any purpose with or without fee is hereby granted,
12  * provided that the above copyright notice appear in all copies and
13  * that both that copyright notice and this permission notice appear
14  * in supporting documentation.
15  *
16  * Permission to modify the software is granted, but not the right to
17  * distribute the complete modified source code.  Modifications are to
18  * be distributed as patches to the released version.  Permission to
19  * distribute binaries produced by compiling modified sources is granted,
20  * provided you
21  *   1. distribute the corresponding source modifications from the
22  *    released version in the form of a patch file along with the binaries,
23  *   2. add special version identification to distinguish your version
24  *    in addition to the base release version number,
25  *   3. provide your name and address as the primary contact for the
26  *    support of your modified version, and
27  *   4. retain our contact information in regard to use of the base
28  *    software.
29  * Permission to distribute the released version of the source code along
30  * with corresponding source modifications in the form of a patch file is
31  * granted with same provisions 2 through 4 for binary distributions.
32  *
33  * This software is provided "as is" without express or implied warranty
34  * to the extent permitted by applicable law.
35 ]*/
36
37
38 /* some systems may not implement time very well ; in particular,
39  * things might break as the year 2000 approaches.
40  * This module either adds a routine gstrptime() to read a formatted time,
41  * augmenting the standard suite of time routines provided by ansi,
42  * or it completely replaces the whole lot with a new set of routines,
43  * which count time relative to the year 2000. Default is to use the
44  * new routines. define USE_SYSTEM_TIME to use the system routines, at your
45  * own risk. One problem in particular is that not all systems allow
46  * the time with integer value 0 to be represented symbolically, which
47  * prevents use of relative times.
48  */
49
50
51 #include "gp_time.h"
52
53 #include "util.h"
54 #include "variable.h"
55
56 /* build as a standalone test */
57
58 #ifdef TEST_TIME
59
60 # ifdef HAVE_SYS_TIMEB_H
61 #  include <sys/timeb.h>
62 # else
63 /* declare struct timeb */
64 extern int ftime(struct timeb *);
65 # endif                         /* !HAVE_SYS_TIMEB_H */
66
67 # define int_error(x,y) fprintf(stderr, "Error: " y "\n")
68 # define int_warn(x,y) fprintf(stderr, "Warn: " y "\n")
69
70 /* need (only) these from plot.h */
71 # define ZERO_YEAR      2000
72 /* 1st jan, 2000 is a Saturday (cal 1 2000 on unix) */
73 # define JAN_FIRST_WDAY 6
74
75 /*  zero gnuplot (2000) - zero system (1970) */
76 # define SEC_OFFS_SYS   946684800.0
77
78 /* avg, incl. leap year */
79 # define YEAR_SEC       31557600.0
80
81 /* YEAR_SEC / 12 */
82 # define MON_SEC                2629800.0
83
84 # define WEEK_SEC       604800.0
85 # define DAY_SEC                86400.0
86
87 /* HBB 990826: moved definitions up here, to avoid 'extern' where
88  * it is neither wanted nor needed */
89 char *abbrev_month_names[] =
90 { "jan", "feb", "mar", "apr", "may", "jun", "jul",
91   "aug", "sep", "oct", "nov", "dec"
92 };
93
94 char *full_month_names[] =
95 { "January", "February", "March", "April", "May",
96   "June", "July", "August", "September", "October",
97   "November", "December"
98 };
99
100 char *abbrev_day_names[] =
101 { "sun", "mon", "tue", "wed", "thu", "fri", "sat"};
102
103 char *full_day_names[] =
104 { "Sunday", "Monday", "Tuesday", "Wednesday",
105   "Thursday", "Friday", "Saturday"
106 };
107
108 #else /* TEST_TIME */
109
110 /*  # include "setshow.h" */            /* for month names etc */
111
112 #endif /* TEST_TIME */
113
114 static char *read_int __PROTO((char *s, int nr, int *d));
115
116
117 static char *
118 read_int(char *s, int nr, int *d)
119 {
120     int result = 0;
121
122     while (--nr >= 0 && *s >= '0' && *s <= '9')
123         result = result * 10 + (*s++ - '0');
124
125     *d = result;
126     return (s);
127 }
128
129
130
131 #ifndef USE_SYSTEM_TIME
132
133 /* a new set of routines to completely replace the ansi ones
134  * Use at your own risk
135  */
136
137 static int gdysize __PROTO((int yr));
138
139 static int mndday[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
140
141 static size_t xstrftime __PROTO((char *buf, size_t bufsz, const char *fmt, struct tm * tm));
142
143 /* days in year */
144 static int
145 gdysize(int yr)
146 {
147
148     if (!(yr % 4)) {
149         if ((!(yr % 100)) && yr % 400)
150             return (365);
151         return (366);
152     }
153     return (365);
154 }
155
156
157 /* new strptime() and gmtime() to allow time to be read as 24 hour,
158  * and spaces in the format string. time is converted to seconds from
159  * year 2000.... */
160
161 char *
162 gstrptime(char *s, char *fmt, struct tm *tm)
163 {
164     int yday, date;
165
166     date = yday = 0;
167     tm->tm_mday = 1;
168     tm->tm_mon = tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
169     /* make relative times work (user-defined tic step) */
170     tm->tm_year = ZERO_YEAR;
171
172     /* we do not yet calculate wday or yday, so make them illegal
173      * [but yday will be read by %j]
174      */
175
176     tm->tm_yday = tm->tm_wday = -1;
177
178     while (*fmt) {
179         if (*fmt != '%') {
180             if (*fmt == ' ') {
181                 /* space in format means zero or more spaces in input */
182                 while (*s == ' ')
183                     ++s;
184                 ++fmt;
185                 continue;
186             } else if (*fmt == *s) {
187                 ++s;
188                 ++fmt;
189                 continue;
190             } else
191                 break;          /* literal match has failed */
192         }
193         /* we are processing a percent escape */
194
195         switch (*++fmt) {
196         case 'b':               /* abbreviated month name */
197             {
198                 int m;
199
200                 for (m = 0; m < 12; ++m)
201                     if (strncasecmp(s, abbrev_month_names[m],
202                                     strlen(abbrev_month_names[m])) == 0) {
203                         s += strlen(abbrev_month_names[m]);
204                         goto found_abbrev_mon;
205                     }
206                 /* get here => not found */
207                 int_warn(DATAFILE, "Bad abbreviated month name");
208                 m = 0;
209               found_abbrev_mon:
210                 tm->tm_mon = m;
211                 break;
212             }
213
214         case 'B':               /* full month name */
215             {
216                 int m;
217
218                 for (m = 0; m < 12; ++m)
219                     if (strncasecmp(s, full_month_names[m],
220                                     strlen(full_month_names[m])) == 0) {
221                         s += strlen(full_month_names[m]);
222                         goto found_full_mon;
223                     }
224                 /* get here => not found */
225                 int_warn(DATAFILE, "Bad full month name");
226                 m = 0;
227               found_full_mon:
228                 tm->tm_mon = m;
229                 break;
230             }
231
232         case 'd':               /* read a day of month */
233             s = read_int(s, 2, &tm->tm_mday);
234             date++;
235             break;
236
237         case 'm':               /* month number */
238             s = read_int(s, 2, &tm->tm_mon);
239             date++;
240             --tm->tm_mon;
241             break;
242
243         case 'y':               /* year number */
244             s = read_int(s, 2, &tm->tm_year);
245             /* In line with the current UNIX98 specification by
246              * The Open Group and major Unix vendors,
247              * two-digit years 69-99 refer to the 20th century, and
248              * values in the range 00-68 refer to the 21st century.
249              */
250             if (tm->tm_year <= 68)
251                 tm->tm_year += 100;
252             date++;
253             tm->tm_year += 1900;
254             break;
255
256         case 'Y':
257             s = read_int(s, 4, &tm->tm_year);
258             date++;
259             break;
260
261         case 'j':
262             s = read_int(s, 3, &tm->tm_yday);
263             tm->tm_yday--;
264             date++;
265             yday++;
266             break;
267
268         case 'H':
269             s = read_int(s, 2, &tm->tm_hour);
270             break;
271
272         case 'M':
273             s = read_int(s, 2, &tm->tm_min);
274             break;
275
276         case 'S':
277             s = read_int(s, 2, &tm->tm_sec);
278             break;
279
280         /* read EPOCH data
281          * EPOCH is the std. unixtimeformat seconds since 01.01.1970 UTC
282          * actualy i would need a read_long (or what time_t is else)
283          *  aktualy this is not my idea       i got if from
284          * AlunDa Penguin Jones (21.11.97)
285          * but changed %t to %s because of strftime
286          * and fixed the localtime() into gmtime()
287          * maybe we should use ggmtime() ? but who choose double ????
288          * Walter Harms <WHarms@bfs.de> 26 Mar 2000
289          */
290
291         case 's':
292 #if 0 /* HBB 20040213: comment this out, but keep it around for now */
293             {
294                 /* time_t when; */
295                 int when;
296                 struct tm *tmwhen;
297                 s = read_int(s, 10, &when);
298                 tmwhen = gmtime((time_t*)&when);
299                 tmwhen->tm_year += 1900;
300                 *tm = *tmwhen;
301                 break;
302             }
303 #else
304             /* HBB 20040213: New version of this.  64-bit proof and
305              * more in line with the rest of this module than the
306              * original one. */
307             {
308                 double when;
309                 /* offset from UNIX epoch (1970) to gnuplot epoch */
310                 static const long epoch_offset
311                     = (long)((ZERO_YEAR - 1970) * 365.25) * DAY_SEC;
312
313                 when = strtod (s, &s) - epoch_offset;
314                 ggmtime(tm, when);
315                 break;
316             }
317 #endif
318         default:
319             int_warn(DATAFILE, "Bad time format in string");
320         }
321         fmt++;
322     }
323
324     FPRINTF((stderr, "read date-time : %02d/%02d/%d:%02d:%02d:%02d\n", tm->tm_mday, tm->tm_mon + 1, tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec));
325
326     /* now check the date/time entered, normalising if necessary
327      * read_int cannot read a -ve number, but can read %m=0 then decrement
328      * it to -1
329      */
330
331 #define S (tm->tm_sec)
332 #define M (tm->tm_min)
333 #define H (tm->tm_hour)
334
335     if (S >= 60) {
336         M += S / 60;
337         S %= 60;
338     }
339     if (M >= 60) {
340         H += M / 60;
341         M %= 60;
342     }
343     if (H >= 24) {
344         if (yday)
345             tm->tm_yday += H / 24;
346         tm->tm_mday += H / 24;
347         H %= 24;
348     }
349 #undef S
350 #undef M
351 #undef H
352
353     FPRINTF((stderr, "normalised time : %02d/%02d/%d:%02d:%02d:%02d\n", tm->tm_mday, tm->tm_mon + 1, tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec));
354
355     if (date) {
356         if (yday) {
357
358             if (tm->tm_yday < 0)
359                 int_error(DATAFILE, "Illegal day of year");
360
361             /* we just set month to jan, day to yday, and let the
362              * normalising code do the work.
363              */
364
365             tm->tm_mon = 0;
366             /* yday is 0->365, day is 1->31 */
367             tm->tm_mday = tm->tm_yday + 1;
368         }
369         if (tm->tm_mon < 0) {
370             int_error(DATAFILE, "illegal month");
371             return (NULL);
372         }
373         if (tm->tm_mday < 1) {
374             int_error(DATAFILE, "illegal day of month");
375             return (NULL);
376         }
377         if (tm->tm_mon > 11) {
378             tm->tm_year += tm->tm_mon / 12;
379             tm->tm_mon %= 12;
380         } {
381             int days_in_month;
382             while (tm->tm_mday > (days_in_month = (mndday[tm->tm_mon] + (tm->tm_mon == 1 && (gdysize(tm->tm_year) > 365))))) {
383                 if (++tm->tm_mon == 12) {
384                     ++tm->tm_year;
385                     tm->tm_mon = 0;
386                 }
387                 tm->tm_mday -= days_in_month;
388             }
389         }
390     }
391     return (s);
392 }
393
394 size_t
395 gstrftime(char *s, size_t bsz, const char *fmt, double l_clock)
396 {
397     struct tm tm;
398
399     ggmtime(&tm, l_clock);
400 #if 0
401     if ((tm.tm_zone = (char *) malloc(strlen(xtm->tm_zone) + 1)))
402         strcpy(tm.tm_zone, xtm->tm_zone);
403     /* printf("zone: %s - %s\n",tm.tm_zone,xtm->tm_zone); */
404 #endif
405
406     return xstrftime(s, bsz, fmt, &tm);
407 }
408
409
410 static size_t
411 xstrftime(
412     char *str,                  /* output buffer */
413     size_t bsz,                 /* space available */
414     const char *fmt,
415     struct tm *tm)
416 {
417     size_t l = 0;                       /* chars written so far */
418
419     char *s = str;
420
421     memset(s, '\0', bsz);
422
423     while (*fmt != '\0') {
424         if (*fmt != '%') {
425             if (l >= bsz)
426                 return (0);
427             *s++ = *fmt++;
428             l++;
429         } else {
430             /* set up format modifiers */
431             int w = 0;
432             int z = 0;
433
434             if (*++fmt == '0') {
435                 z = 1;
436                 ++fmt;
437             }
438             while (*fmt >= '0' && *fmt <= '9') {
439                 w = w * 10 + (*fmt - '0');
440                 ++fmt;
441             }
442
443             switch (*fmt++) {
444
445                 /* some shorthands : check that there is space in the
446                  * output string. */
447 #define CHECK_SPACE(n) do {                             \
448                     if ((l+(n)) > bsz) return 0;        \
449                 } while (0)
450
451                 /* copy a fixed string, checking that there's room */
452 #define COPY_STRING(z) do {                     \
453                     CHECK_SPACE(strlen(z)) ;    \
454                     strcpy(s, z);               \
455                 } while (0)
456
457                 /* format a string, using default spec if none given w
458                  * and z are width and zero-flag dw and dz are the
459                  * defaults for these In fact, CHECK_SPACE(w) is not a
460                  * sufficient test, since sprintf("%2d", 365) outputs
461                  * three characters
462                  */
463 #define FORMAT_STRING(dz, dw, x) do {                           \
464                     if (w==0) {                                 \
465                         w=(dw);                                 \
466                         if (!z)                                 \
467                             z=(dz);                             \
468                     }                                           \
469                     CHECK_SPACE(w);                             \
470                     sprintf(s, z ? "%0*d" : "%*d", w, (x));     \
471                 } while(0)
472
473             case '%':
474                 CHECK_SPACE(1);
475                 *s = '%';
476                 break;
477
478             case 'a':
479                 COPY_STRING(abbrev_day_names[tm->tm_wday]);
480                 break;
481
482             case 'A':
483                 COPY_STRING(full_day_names[tm->tm_wday]);
484                 break;
485
486             case 'b':
487             case 'h':
488                 COPY_STRING(abbrev_month_names[tm->tm_mon]);
489                 break;
490
491             case 'B':
492                 COPY_STRING(full_month_names[tm->tm_mon]);
493                 break;
494
495
496 #if 0
497                 /* %x not currently supported, so neither is c */
498             case 'c':
499                 if (!xstrftime(s, bsz - l, "%x %X", tm))
500                     return (0);
501                 break;
502 #endif
503
504             case 'd':
505                 FORMAT_STRING(1, 2, tm->tm_mday);       /* %02d */
506                 break;
507
508             case 'D':
509                 if (!xstrftime(s, bsz - l, "%m/%d/%y", tm))
510                     return (0);
511                 break;
512
513             case 'F':
514                 if (!xstrftime(s, bsz - l, "%Y-%m-%d", tm))
515                     return (0);
516                 break;
517
518             case 'H':
519                 FORMAT_STRING(1, 2, tm->tm_hour);       /* %02d */
520                 break;
521
522             case 'I':
523                 FORMAT_STRING(1, 2, (tm->tm_hour + 11) % 12 + 1); /* %02d */
524                 break;
525
526             case 'j':
527                 FORMAT_STRING(1, 3, tm->tm_yday + 1);   /* %03d */
528                 break;
529
530                 /* not in linux strftime man page. Not really needed now */
531             case 'k':
532                 FORMAT_STRING(0, 2, tm->tm_hour);       /* %2d */
533                 break;
534
535             case 'l':
536                 FORMAT_STRING(0, 2, (tm->tm_hour + 11) % 12 + 1); /* %2d */
537                 break;
538
539             case 'm':
540                 FORMAT_STRING(1, 2, tm->tm_mon + 1);    /* %02d */
541                 break;
542
543             case 'M':
544                 FORMAT_STRING(1, 2, tm->tm_min);        /* %02d */
545                 break;
546
547             case 'p':
548                 CHECK_SPACE(2);
549                 strcpy(s, (tm->tm_hour < 12) ? "am" : "pm");
550                 break;
551
552             case 'r':
553                 if (!xstrftime(s, bsz - l, "%I:%M:%S %p", tm))
554                     return (0);
555                 break;
556
557             case 'R':
558                 if (!xstrftime(s, bsz - l, "%H:%M", tm))
559                     return (0);
560                 break;
561
562             case 'S':
563                 FORMAT_STRING(1, 2, tm->tm_sec);        /* %02d */
564                 break;
565
566             case 'T':
567                 if (!xstrftime(s, bsz - l, "%H:%M:%S", tm))
568                     return (0);
569                 break;
570
571             case 'W':           /* mon 1 day of week */
572                 {
573                     int week;
574                     if (tm->tm_yday <= tm->tm_wday) {
575                         week = 1;
576
577                         if ((tm->tm_mday - tm->tm_yday) > 4) {
578                             week = 52;
579                         }
580                         if (tm->tm_yday == tm->tm_wday && tm->tm_wday == 0)
581                             week = 52;
582
583                     } else {
584
585                         /* sun prev week */
586                         int bw = tm->tm_yday - tm->tm_wday;
587
588                         if (tm->tm_wday > 0)
589                             bw += 7;    /* sun end of week */
590
591                         week = (int) bw / 7;
592
593                         if ((bw % 7) > 2)       /* jan 1 is before friday */
594                             week++;
595                     }
596                     FORMAT_STRING(1, 2, week);  /* %02d */
597                     break;
598                 }
599
600             case 'U':           /* sun 1 day of week */
601                 {
602                     int week, bw;
603
604                     if (tm->tm_yday <= tm->tm_wday) {
605                         week = 1;
606                         if ((tm->tm_mday - tm->tm_yday) > 4) {
607                             week = 52;
608                         }
609                     } else {
610                         /* sat prev week */
611                         bw = tm->tm_yday - tm->tm_wday - 1;
612                         if (tm->tm_wday >= 0)
613                             bw += 7;    /* sat end of week */
614                         week = (int) bw / 7;
615                         if ((bw % 7) > 1) {     /* jan 1 is before friday */
616                             week++;
617                         }
618                     }
619                     FORMAT_STRING(1, 2, week);  /* %02d */
620                     break;
621                 }
622
623             case 'w':           /* day of week, sun=0 */
624                 FORMAT_STRING(1, 2, tm->tm_wday);       /* %02d */
625                 break;
626
627             case 'y':
628                 FORMAT_STRING(1, 2, tm->tm_year % 100);         /* %02d */
629                 break;
630
631             case 'Y':
632                 FORMAT_STRING(1, 4, tm->tm_year);       /* %04d */
633                 break;
634
635 #if 0
636             case 'Z':
637                 COPY_STRING(tm->tm_zone);
638                 break;
639 #endif
640             }                   /* switch */
641
642             while (*s != '\0') {
643                 s++;
644                 l++;
645             }
646 #undef CHECK_SPACE
647 #undef COPY_STRING
648 #undef FORMAT_STRING
649         } /* switch(fmt letter) */
650     } /* if(fmt letter not '%') */
651     return (l);
652 }
653
654
655
656 /* time_t  */
657 double
658 gtimegm(struct tm *tm)
659 {
660     int i;
661     /* returns sec from year ZERO_YEAR, defined in plot.h */
662     double dsec = 0.;
663
664     if (tm->tm_year < ZERO_YEAR) {
665         for (i = tm->tm_year; i < ZERO_YEAR; i++) {
666             dsec -= (double) gdysize(i);
667         }
668     } else {
669         for (i = ZERO_YEAR; i < tm->tm_year; i++) {
670             dsec += (double) gdysize(i);
671         }
672     }
673     if (tm->tm_mday > 0) {
674         for (i = 0; i < tm->tm_mon; i++) {
675             dsec += (double) mndday[i] + (i == 1 && (gdysize(tm->tm_year) > 365));
676         }
677         dsec += (double) tm->tm_mday - 1;
678     } else {
679         dsec += (double) tm->tm_yday;
680     }
681     dsec *= (double) 24;
682
683     dsec += tm->tm_hour;
684     dsec *= 60.0;
685     dsec += tm->tm_min;
686     dsec *= 60.0;
687     dsec += tm->tm_sec;
688
689     FPRINTF((stderr, "broken-down time : %02d/%02d/%d:%02d:%02d:%02d = %g seconds\n", tm->tm_mday, tm->tm_mon + 1, tm->tm_year, tm->tm_hour,
690              tm->tm_min, tm->tm_sec, dsec));
691
692     return (dsec);
693 }
694
695 int
696 ggmtime(struct tm *tm, double l_clock)
697 {
698     /* l_clock is relative to ZERO_YEAR, jan 1, 00:00:00,defined in plot.h */
699     int i, days;
700
701     /* dodgy way of doing wday - i hope it works ! */
702     int wday = JAN_FIRST_WDAY;  /* eg 6 for 2000 */
703
704     FPRINTF((stderr, "%g seconds = ", l_clock));
705     if (fabs(l_clock) > 1.e12) {  /* Some time in the year 33688 */
706         int_warn(NO_CARET, "time value out of range");
707         return(-1);
708     }
709
710     tm->tm_year = ZERO_YEAR;
711     tm->tm_mday = tm->tm_yday = tm->tm_mon = tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
712     if (l_clock < 0) {
713         while (l_clock < 0) {
714             int days_in_year = gdysize(--tm->tm_year);
715             l_clock += days_in_year * DAY_SEC;  /* 24*3600 */
716             /* adding 371 is noop in modulo 7 arithmetic, but keeps wday +ve */
717             wday += 371 - days_in_year;
718         }
719     } else {
720         for (;;) {
721             int days_in_year = gdysize(tm->tm_year);
722             if (l_clock < days_in_year * DAY_SEC)
723                 break;
724             l_clock -= days_in_year * DAY_SEC;
725             tm->tm_year++;
726             /* only interested in result modulo 7, but %7 is expensive */
727             wday += (days_in_year - 364);
728         }
729     }
730     tm->tm_yday = (int) (l_clock / DAY_SEC);
731     l_clock -= tm->tm_yday * DAY_SEC;
732     tm->tm_hour = (int) l_clock / 3600;
733     l_clock -= tm->tm_hour * 3600;
734     tm->tm_min = (int) l_clock / 60;
735     l_clock -= tm->tm_min * 60;
736     tm->tm_sec = (int) l_clock;
737
738     days = tm->tm_yday;
739
740     /* wday%7 should be day of week of first day of year */
741     tm->tm_wday = (wday + days) % 7;
742
743     while (days >= (i = mndday[tm->tm_mon] + (tm->tm_mon == 1 && (gdysize(tm->tm_year) > 365)))) {
744         days -= i;
745         tm->tm_mon++;
746     }
747     tm->tm_mday = days + 1;
748
749     FPRINTF((stderr, "broken-down time : %02d/%02d/%d:%02d:%02d:%02d\n", tm->tm_mday, tm->tm_mon + 1, tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec));
750
751     return (0);
752 }
753
754
755
756
757 #else /* USE_SYSTEM_TIME */
758
759 /* define gnu time routines in terms of system time routines */
760
761 size_t
762 gstrftime(char *buf, size_t bufsz, const char *fmt, double l_clock)
763 {
764     time_t t = (time_t) l_clock;
765     return strftime(buf, bufsz, fmt, gmtime(&t));
766 }
767
768 double
769 gtimegm(struct tm *tm)
770 {
771     return (double) mktime(tm);
772 }
773
774 int
775 ggmtime(struct tm *tm, double l_clock)
776 {
777     time_t t = (time_t) l_clock;
778     struct tm *m = gmtime(&t);
779     *tm = *m;                   /* can any non-ansi compilers not do this ? */
780     return 0;
781 }
782
783 /* supplemental routine gstrptime() to read a formatted time */
784
785 char *
786 gstrptime(char *s, char *fmt, struct tm *tm)
787 {
788     FPRINTF((stderr, "gstrptime(\"%s\", \"%s\")\n", s, fmt));
789
790     /* linux does not appear to like years before 1902
791      * NT complains if its before 1970
792      * initialise fields to midnight, 1st Jan, 1970 (for relative times)
793      */
794     tm->tm_sec = tm->tm_min = tm->tm_hour = 0;
795     tm->tm_mday = 1;
796     tm->tm_mon = 0;
797     tm->tm_year = 70;
798     /* oops - it goes wrong without this */
799     tm->tm_isdst = 0;
800
801     for (; *fmt && *s; ++fmt) {
802         if (*fmt != '%') {
803             if (*s != *fmt)
804                 return s;
805             ++s;
806             continue;
807         }
808         assert(*fmt == '%');
809
810         switch (*++fmt) {
811         case 0:
812             /* uh oh - % is last character in format */
813             return s;
814         case '%':
815             /* literal % */
816             if (*s++ != '%')
817                 return s - 1;
818             continue;
819
820 #define NOTHING /* nothing */
821 #define LETTER(L, width, field, extra)          \
822         case L:                                 \
823             s=read_int(s,width,&tm->field);     \
824             extra;                              \
825             continue;
826
827             LETTER('d', 2, tm_mday, NOTHING);
828             LETTER('m', 2, tm_mon, NOTHING);
829             LETTER('y', 2, tm_year, NOTHING);
830             LETTER('Y', 4, tm_year, tm->tm_year -= 1900);
831             LETTER('H', 2, tm_hour, NOTHING);
832             LETTER('M', 2, tm_min, NOTHING);
833             LETTER('S', 2, tm_sec, NOTHING);
834 #undef NOTHING
835 #undef LETTER
836
837         default:
838             int_error(DATAFILE, "incorrect time format character");
839         }
840     }
841
842     FPRINTF((stderr, "Before mktime : %d/%d/%d:%d:%d:%d\n", tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec));
843     /* mktime range-checks the time */
844
845     if (mktime(tm) == -1) {
846         FPRINTF((stderr, "mktime() was not happy\n"));
847         int_error(DATAFILE, "Invalid date/time [mktime() did not like it]");
848     }
849     FPRINTF((stderr, "After mktime : %d/%d/%d:%d:%d:%d\n", tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec));
850
851     return s;
852 }
853
854
855 #endif /* USE_SYSTEM_TIME */
856
857
858 #ifdef TEST_TIME
859
860 /* either print current time using supplied format, or read
861  * supplied time using supplied format
862  */
863
864
865 int
866 main(int argc, char *argv[])
867 {
868     char output[80];
869
870     if (argc < 2) {
871         fputs("usage : test 'format' ['time']\n", stderr);
872         exit(EXIT_FAILURE);
873     }
874     if (argc == 2) {
875         struct timeb now;
876         struct tm *tm;
877         ftime(&now);
878         tm = gmtime(&now.time);
879         xstrftime(output, 80, argv[1], tm);
880         puts(output);
881     } else {
882         struct tm tm;
883         gstrptime(argv[2], argv[1], &tm);
884         puts(asctime(&tm));
885     }
886     exit(EXIT_SUCCESS);
887 }
888
889 #endif /* TEST_TIME */