2 static char *RCSid() { return RCSid("$Id: time.c,v 1.19.2.2 2009/06/12 05:05:44 sfeam Exp $"); }
8 * Copyright 1986 - 1993, 1998, 2004 Thomas Williams, Colin Kelley
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.
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,
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
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.
33 * This software is provided "as is" without express or implied warranty
34 * to the extent permitted by applicable law.
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.
56 /* build as a standalone test */
60 # ifdef HAVE_SYS_TIMEB_H
61 # include <sys/timeb.h>
63 /* declare struct timeb */
64 extern int ftime(struct timeb *);
65 # endif /* !HAVE_SYS_TIMEB_H */
67 # define int_error(x,y) fprintf(stderr, "Error: " y "\n")
68 # define int_warn(x,y) fprintf(stderr, "Warn: " y "\n")
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
75 /* zero gnuplot (2000) - zero system (1970) */
76 # define SEC_OFFS_SYS 946684800.0
78 /* avg, incl. leap year */
79 # define YEAR_SEC 31557600.0
82 # define MON_SEC 2629800.0
84 # define WEEK_SEC 604800.0
85 # define DAY_SEC 86400.0
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"
94 char *full_month_names[] =
95 { "January", "February", "March", "April", "May",
96 "June", "July", "August", "September", "October",
97 "November", "December"
100 char *abbrev_day_names[] =
101 { "sun", "mon", "tue", "wed", "thu", "fri", "sat"};
103 char *full_day_names[] =
104 { "Sunday", "Monday", "Tuesday", "Wednesday",
105 "Thursday", "Friday", "Saturday"
108 #else /* TEST_TIME */
110 /* # include "setshow.h" */ /* for month names etc */
112 #endif /* TEST_TIME */
114 static char *read_int __PROTO((char *s, int nr, int *d));
118 read_int(char *s, int nr, int *d)
122 while (--nr >= 0 && *s >= '0' && *s <= '9')
123 result = result * 10 + (*s++ - '0');
131 #ifndef USE_SYSTEM_TIME
133 /* a new set of routines to completely replace the ansi ones
134 * Use at your own risk
137 static int gdysize __PROTO((int yr));
139 static int mndday[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
141 static size_t xstrftime __PROTO((char *buf, size_t bufsz, const char *fmt, struct tm * tm));
149 if ((!(yr % 100)) && yr % 400)
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
162 gstrptime(char *s, char *fmt, struct tm *tm)
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;
172 /* we do not yet calculate wday or yday, so make them illegal
173 * [but yday will be read by %j]
176 tm->tm_yday = tm->tm_wday = -1;
181 /* space in format means zero or more spaces in input */
186 } else if (*fmt == *s) {
191 break; /* literal match has failed */
193 /* we are processing a percent escape */
196 case 'b': /* abbreviated month name */
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;
206 /* get here => not found */
207 int_warn(DATAFILE, "Bad abbreviated month name");
214 case 'B': /* full month name */
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]);
224 /* get here => not found */
225 int_warn(DATAFILE, "Bad full month name");
232 case 'd': /* read a day of month */
233 s = read_int(s, 2, &tm->tm_mday);
237 case 'm': /* month number */
238 s = read_int(s, 2, &tm->tm_mon);
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.
250 if (tm->tm_year <= 68)
257 s = read_int(s, 4, &tm->tm_year);
262 s = read_int(s, 3, &tm->tm_yday);
269 s = read_int(s, 2, &tm->tm_hour);
273 s = read_int(s, 2, &tm->tm_min);
277 s = read_int(s, 2, &tm->tm_sec);
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
292 #if 0 /* HBB 20040213: comment this out, but keep it around for now */
297 s = read_int(s, 10, &when);
298 tmwhen = gmtime((time_t*)&when);
299 tmwhen->tm_year += 1900;
304 /* HBB 20040213: New version of this. 64-bit proof and
305 * more in line with the rest of this module than the
309 /* offset from UNIX epoch (1970) to gnuplot epoch */
310 static const long epoch_offset
311 = (long)((ZERO_YEAR - 1970) * 365.25) * DAY_SEC;
313 when = strtod (s, &s) - epoch_offset;
319 int_warn(DATAFILE, "Bad time format in string");
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));
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
331 #define S (tm->tm_sec)
332 #define M (tm->tm_min)
333 #define H (tm->tm_hour)
345 tm->tm_yday += H / 24;
346 tm->tm_mday += H / 24;
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));
359 int_error(DATAFILE, "Illegal day of year");
361 /* we just set month to jan, day to yday, and let the
362 * normalising code do the work.
366 /* yday is 0->365, day is 1->31 */
367 tm->tm_mday = tm->tm_yday + 1;
369 if (tm->tm_mon < 0) {
370 int_error(DATAFILE, "illegal month");
373 if (tm->tm_mday < 1) {
374 int_error(DATAFILE, "illegal day of month");
377 if (tm->tm_mon > 11) {
378 tm->tm_year += tm->tm_mon / 12;
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) {
387 tm->tm_mday -= days_in_month;
395 gstrftime(char *s, size_t bsz, const char *fmt, double l_clock)
399 ggmtime(&tm, l_clock);
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); */
406 return xstrftime(s, bsz, fmt, &tm);
412 char *str, /* output buffer */
413 size_t bsz, /* space available */
417 size_t l = 0; /* chars written so far */
421 memset(s, '\0', bsz);
423 while (*fmt != '\0') {
430 /* set up format modifiers */
438 while (*fmt >= '0' && *fmt <= '9') {
439 w = w * 10 + (*fmt - '0');
445 /* some shorthands : check that there is space in the
447 #define CHECK_SPACE(n) do { \
448 if ((l+(n)) > bsz) return 0; \
451 /* copy a fixed string, checking that there's room */
452 #define COPY_STRING(z) do { \
453 CHECK_SPACE(strlen(z)) ; \
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
463 #define FORMAT_STRING(dz, dw, x) do { \
470 sprintf(s, z ? "%0*d" : "%*d", w, (x)); \
479 COPY_STRING(abbrev_day_names[tm->tm_wday]);
483 COPY_STRING(full_day_names[tm->tm_wday]);
488 COPY_STRING(abbrev_month_names[tm->tm_mon]);
492 COPY_STRING(full_month_names[tm->tm_mon]);
497 /* %x not currently supported, so neither is c */
499 if (!xstrftime(s, bsz - l, "%x %X", tm))
505 FORMAT_STRING(1, 2, tm->tm_mday); /* %02d */
509 if (!xstrftime(s, bsz - l, "%m/%d/%y", tm))
514 if (!xstrftime(s, bsz - l, "%Y-%m-%d", tm))
519 FORMAT_STRING(1, 2, tm->tm_hour); /* %02d */
523 FORMAT_STRING(1, 2, (tm->tm_hour + 11) % 12 + 1); /* %02d */
527 FORMAT_STRING(1, 3, tm->tm_yday + 1); /* %03d */
530 /* not in linux strftime man page. Not really needed now */
532 FORMAT_STRING(0, 2, tm->tm_hour); /* %2d */
536 FORMAT_STRING(0, 2, (tm->tm_hour + 11) % 12 + 1); /* %2d */
540 FORMAT_STRING(1, 2, tm->tm_mon + 1); /* %02d */
544 FORMAT_STRING(1, 2, tm->tm_min); /* %02d */
549 strcpy(s, (tm->tm_hour < 12) ? "am" : "pm");
553 if (!xstrftime(s, bsz - l, "%I:%M:%S %p", tm))
558 if (!xstrftime(s, bsz - l, "%H:%M", tm))
563 FORMAT_STRING(1, 2, tm->tm_sec); /* %02d */
567 if (!xstrftime(s, bsz - l, "%H:%M:%S", tm))
571 case 'W': /* mon 1 day of week */
574 if (tm->tm_yday <= tm->tm_wday) {
577 if ((tm->tm_mday - tm->tm_yday) > 4) {
580 if (tm->tm_yday == tm->tm_wday && tm->tm_wday == 0)
586 int bw = tm->tm_yday - tm->tm_wday;
589 bw += 7; /* sun end of week */
593 if ((bw % 7) > 2) /* jan 1 is before friday */
596 FORMAT_STRING(1, 2, week); /* %02d */
600 case 'U': /* sun 1 day of week */
604 if (tm->tm_yday <= tm->tm_wday) {
606 if ((tm->tm_mday - tm->tm_yday) > 4) {
611 bw = tm->tm_yday - tm->tm_wday - 1;
612 if (tm->tm_wday >= 0)
613 bw += 7; /* sat end of week */
615 if ((bw % 7) > 1) { /* jan 1 is before friday */
619 FORMAT_STRING(1, 2, week); /* %02d */
623 case 'w': /* day of week, sun=0 */
624 FORMAT_STRING(1, 2, tm->tm_wday); /* %02d */
628 FORMAT_STRING(1, 2, tm->tm_year % 100); /* %02d */
632 FORMAT_STRING(1, 4, tm->tm_year); /* %04d */
637 COPY_STRING(tm->tm_zone);
649 } /* switch(fmt letter) */
650 } /* if(fmt letter not '%') */
658 gtimegm(struct tm *tm)
661 /* returns sec from year ZERO_YEAR, defined in plot.h */
664 if (tm->tm_year < ZERO_YEAR) {
665 for (i = tm->tm_year; i < ZERO_YEAR; i++) {
666 dsec -= (double) gdysize(i);
669 for (i = ZERO_YEAR; i < tm->tm_year; i++) {
670 dsec += (double) gdysize(i);
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));
677 dsec += (double) tm->tm_mday - 1;
679 dsec += (double) tm->tm_yday;
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));
696 ggmtime(struct tm *tm, double l_clock)
698 /* l_clock is relative to ZERO_YEAR, jan 1, 00:00:00,defined in plot.h */
701 /* dodgy way of doing wday - i hope it works ! */
702 int wday = JAN_FIRST_WDAY; /* eg 6 for 2000 */
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");
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;
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;
721 int days_in_year = gdysize(tm->tm_year);
722 if (l_clock < days_in_year * DAY_SEC)
724 l_clock -= days_in_year * DAY_SEC;
726 /* only interested in result modulo 7, but %7 is expensive */
727 wday += (days_in_year - 364);
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;
740 /* wday%7 should be day of week of first day of year */
741 tm->tm_wday = (wday + days) % 7;
743 while (days >= (i = mndday[tm->tm_mon] + (tm->tm_mon == 1 && (gdysize(tm->tm_year) > 365)))) {
747 tm->tm_mday = days + 1;
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));
757 #else /* USE_SYSTEM_TIME */
759 /* define gnu time routines in terms of system time routines */
762 gstrftime(char *buf, size_t bufsz, const char *fmt, double l_clock)
764 time_t t = (time_t) l_clock;
765 return strftime(buf, bufsz, fmt, gmtime(&t));
769 gtimegm(struct tm *tm)
771 return (double) mktime(tm);
775 ggmtime(struct tm *tm, double l_clock)
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 ? */
783 /* supplemental routine gstrptime() to read a formatted time */
786 gstrptime(char *s, char *fmt, struct tm *tm)
788 FPRINTF((stderr, "gstrptime(\"%s\", \"%s\")\n", s, fmt));
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)
794 tm->tm_sec = tm->tm_min = tm->tm_hour = 0;
798 /* oops - it goes wrong without this */
801 for (; *fmt && *s; ++fmt) {
812 /* uh oh - % is last character in format */
820 #define NOTHING /* nothing */
821 #define LETTER(L, width, field, extra) \
823 s=read_int(s,width,&tm->field); \
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);
838 int_error(DATAFILE, "incorrect time format character");
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 */
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]");
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));
855 #endif /* USE_SYSTEM_TIME */
860 /* either print current time using supplied format, or read
861 * supplied time using supplied format
866 main(int argc, char *argv[])
871 fputs("usage : test 'format' ['time']\n", stderr);
878 tm = gmtime(&now.time);
879 xstrftime(output, 80, argv[1], tm);
883 gstrptime(argv[2], argv[1], &tm);
889 #endif /* TEST_TIME */