#ifndef lint static char *RCSid = "$Id: time.c,v 1.5.2.2 2002/01/31 21:28:05 lhecking Exp $"; #endif /* GNUPLOT - time.c */ /*[ * Copyright 1986 - 1993, 1998 Thomas Williams, Colin Kelley * * Permission to use, copy, and distribute this software and its * documentation for any purpose with or without fee is hereby granted, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. * * Permission to modify the software is granted, but not the right to * distribute the complete modified source code. Modifications are to * be distributed as patches to the released version. Permission to * distribute binaries produced by compiling modified sources is granted, * provided you * 1. distribute the corresponding source modifications from the * released version in the form of a patch file along with the binaries, * 2. add special version identification to distinguish your version * in addition to the base release version number, * 3. provide your name and address as the primary contact for the * support of your modified version, and * 4. retain our contact information in regard to use of the base * software. * Permission to distribute the released version of the source code along * with corresponding source modifications in the form of a patch file is * granted with same provisions 2 through 4 for binary distributions. * * This software is provided "as is" without express or implied warranty * to the extent permitted by applicable law. ]*/ /* some systems may not implement time very well ; in particular, * things might break as the year 2000 approaches. * This module either adds a routine gstrptime() to read a formatted time, * augmenting the standard suite of time routines provided by ansi, * or it completely replaces the whole lot with a new set of routines, * which count time relative to the year 2000. Default is to use the * new routines. define USE_SYSTEM_TIME to use the system routines, at your * own risk. One problem in particular is that not all systems allow * the time with integer value 0 to be represented symbolically, which * prevents use of relative times. */ #include "plot.h" /* build as a standalone test */ #ifdef TEST_TIME # ifdef HAVE_SYS_TIMEB_H # include #else /* declare struct timeb */ extern int ftime(struct timeb *); #endif # define int_error(x,y) fprintf(stderr, "Error: " x "\n") # define int_warn(x,y) fprintf(stderr, "Warn: " x "\n") /* need (only) these from plot.h */ # define ZERO_YEAR 2000 /* 1st jan, 2000 is a Saturday (cal 1 2000 on unix) */ # define JAN_FIRST_WDAY 6 /* zero gnuplot (2000) - zero system (1970) */ # define SEC_OFFS_SYS 946684800.0 /* avg, incl. leap year */ # define YEAR_SEC 31557600.0 /* YEAR_SEC / 12 */ # define MON_SEC 2629800.0 # define WEEK_SEC 604800.0 # define DAY_SEC 86400.0 /*forward decls */ extern char *abbrev_month_names[]; extern char *full_month_names[]; extern char *abbrev_day_names[]; extern char *full_day_names[]; #else /* TEST_TIME */ # include "setshow.h" /* for month names etc */ #endif /* TEST_TIME */ static char *read_int __PROTO((char *s, int nr, int *d)); static int gdysize __PROTO((int yr)); static char * read_int(s, nr, d) char *s; int nr, *d; { int result = 0; while (--nr >= 0 && *s >= '0' && *s <= '9') result = result * 10 + (*s++ - '0'); *d = result; return (s); } #ifndef USE_SYSTEM_TIME /* a new set of routines to completely replace the ansi ones * Use at your own risk */ static int mndday[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static int xstrftime __PROTO((char *buf, int bufsz, char *fmt, struct tm * tm)); /* days in year */ static int gdysize(yr) int yr; { if (!(yr % 4)) { if ((!(yr % 100)) && yr % 400) return (365); return (366); } return (365); } /* new strptime() and gmtime() to allow time to be read as 24 hour, * and spaces in the format string. time is converted to seconds from * year 2000.... */ char * gstrptime(s, fmt, tm) char *s; char *fmt; struct tm *tm; { int yday, date; date = yday = 0; tm->tm_mday = 1; tm->tm_mon = tm->tm_hour = tm->tm_min = tm->tm_sec = 0; /* make relative times work (user-defined tic step) */ tm->tm_year = ZERO_YEAR; /* we do not yet calculate wday or yday, so make them illegal * [but yday will be read by %j] */ tm->tm_yday = tm->tm_wday = -1; while (*fmt) { if (*fmt != '%') { if (*fmt == ' ') { /* space in format means zero or more spaces in input */ while (*s == ' ') ++s; ++fmt; continue; } else if (*fmt == *s) { ++s; ++fmt; continue; } else break; /* literal match has failed */ } /* we are processing a percent escape */ switch (*++fmt) { case 'b': /* abbreviated month name */ { int m; for (m = 0; m < 12; ++m) if (strnicmp(s, abbrev_month_names[m], strlen(abbrev_month_names[m])) == 0) { s += strlen(abbrev_month_names[m]); goto found_abbrev_mon; } /* get here => not found */ int_warn("Bad abbreviated month name", NO_CARET); m = 0; found_abbrev_mon: tm->tm_mon = m; break; } case 'B': /* full month name */ { int m; for (m = 0; m < 12; ++m) if (strnicmp(s, full_month_names[m], strlen(full_month_names[m])) == 0) { s += strlen(full_month_names[m]); goto found_full_mon; } /* get here => not found */ int_warn("Bad full month name", NO_CARET); m = 0; found_full_mon: tm->tm_mon = m; break; } case 'd': /* read a day of month */ s = read_int(s, 2, &tm->tm_mday); date++; break; case 'm': /* month number */ s = read_int(s, 2, &tm->tm_mon); date++; --tm->tm_mon; break; case 'y': /* year number */ s = read_int(s, 2, &tm->tm_year); /* In line with the current UNIX98 specification by * The Open Group and major Unix vendors, * two-digit years 69-99 refer to the 20th century, and * values in the range 00-68 refer to the 21st century. */ if (tm->tm_year <= 68) tm->tm_year += 100; date++; tm->tm_year += 1900; break; case 'Y': s = read_int(s, 4, &tm->tm_year); date++; break; case 'j': s = read_int(s, 3, &tm->tm_yday); tm->tm_yday--; date++; yday++; break; case 'H': s = read_int(s, 2, &tm->tm_hour); break; case 'M': s = read_int(s, 2, &tm->tm_min); break; case 'S': s = read_int(s, 2, &tm->tm_sec); break; /* read EPOCH data * EPOCH is the std. unixtimeformat seconds since 01.01.1970 UTC * actualy i would need a read_long (or what time_t is else) * aktualy this is not my idea i got if from * AlunDa Penguin Jones (21.11.97) * but changed %t to %s because of strftime * and fixed the localtime() into gmtime() * maybe we should use ggmtime() ? but who choose double ???? * Walter Harms 26 Mar 2000 */ case 's': { /* time_t when; */ int when; struct tm *tmwhen; s = read_int(s, 10, &when); tmwhen = gmtime((time_t*)&when); tmwhen->tm_year += 1900; *tm = *tmwhen; break; } default: int_warn("Bad time format in string", NO_CARET); } fmt++; } 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)); /* now check the date/time entered, normalising if necessary * read_int cannot read a -ve number, but can read %m=0 then decrement * it to -1 */ #define S (tm->tm_sec) #define M (tm->tm_min) #define H (tm->tm_hour) if (S >= 60) { M += S / 60; S %= 60; } if (M >= 60) { H += M / 60; M %= 60; } if (H >= 24) { if (yday) tm->tm_yday += H / 24; tm->tm_mday += H / 24; H %= 24; } #undef S #undef M #undef H 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)); if (date) { if (yday) { if (tm->tm_yday < 0) int_error("Illegal day of year", NO_CARET); /* we just set month to jan, day to yday, and let the * normalising code do the work. */ tm->tm_mon = 0; /* yday is 0->365, day is 1->31 */ tm->tm_mday = tm->tm_yday + 1; } if (tm->tm_mon < 0) { int_error("illegal month", NO_CARET); return (NULL); } if (tm->tm_mday < 1) { int_error("illegal day of month", NO_CARET); return (NULL); } if (tm->tm_mon > 11) { tm->tm_year += tm->tm_mon / 12; tm->tm_mon %= 12; } { int days_in_month; while (tm->tm_mday > (days_in_month = (mndday[tm->tm_mon] + (tm->tm_mon == 1 && (gdysize(tm->tm_year) > 365))))) { if (++tm->tm_mon == 12) { ++tm->tm_year; tm->tm_mon = 0; } tm->tm_mday -= days_in_month; } } } return (s); } int gstrftime(s, bsz, fmt, l_clock) char *s; int bsz; char *fmt; double l_clock; { struct tm tm; ggmtime(&tm, l_clock); #if 0 if ((tm.tm_zone = (char *) malloc(strlen(xtm->tm_zone) + 1))) strcpy(tm.tm_zone, xtm->tm_zone); /* printf("zone: %s - %s\n",tm.tm_zone,xtm->tm_zone); */ #endif return (xstrftime(s, bsz, fmt, &tm)); } /* some shorthands : check that there is space in the output string * Odd-looking defn is dangling-else-safe */ #define CHECK_SPACE(n) if ( (l+(n)) <= bsz) ; else return 0 /* copy a fixed string, checking that there's room */ #define COPY_STRING(z) CHECK_SPACE(strlen(z)) ; strcpy(s, z) /* format a string, using default spec if none given * w and z are width and zero-flag * dw and dz are the defaults for these * In fact, CHECK_SPACE(w) is not a sufficient test, since * sprintf("%2d", 365) outputs three characters */ #define FORMAT_STRING(dz, dw, x) \ if (w==0) { w=(dw); if (!z) z=(dz); } \ CHECK_SPACE(w); \ sprintf(s, z ? "%0*d" : "%*d", w, (x) ) static int xstrftime(str, bsz, fmt, tm) char *str; /* output buffer */ int bsz; /* space available */ char *fmt; struct tm *tm; { int l = 0; /* chars written so far */ char *s = str; memset(s, '\0', bsz); while (*fmt != '\0') { if (*fmt != '%') { if (l >= bsz) return (0); *s++ = *fmt++; l++; } else { /* set up format modifiers */ int w = 0; int z = 0; if (*++fmt == '0') { z = 1; ++fmt; } while (*fmt >= '0' && *fmt <= '9') { w = w * 10 + (*fmt - '0'); ++fmt; } switch (*fmt++) { case '%': CHECK_SPACE(1); *s = '%'; break; case 'a': COPY_STRING(abbrev_day_names[tm->tm_wday]); break; case 'A': COPY_STRING(full_day_names[tm->tm_wday]); break; case 'b': case 'h': COPY_STRING(abbrev_month_names[tm->tm_mon]); break; case 'B': COPY_STRING(full_month_names[tm->tm_mon]); break; #if 0 /* %x not currently supported, so neither is c */ case 'c': if (!xstrftime(s, bsz - l, "%x %X", tm)) return (0); break; #endif case 'd': FORMAT_STRING(1, 2, tm->tm_mday); /* %02d */ break; case 'D': if (!xstrftime(s, bsz - l, "%m/%d/%y", tm)) return (0); break; case 'H': FORMAT_STRING(1, 2, tm->tm_hour); /* %02d */ break; case 'I': FORMAT_STRING(1, 2, tm->tm_hour % 12); /* %02d */ break; case 'j': FORMAT_STRING(1, 3, tm->tm_yday + 1); /* %03d */ break; /* not in linux strftime man page. Not really needed now */ case 'k': FORMAT_STRING(0, 2, tm->tm_hour); /* %2d */ break; case 'l': FORMAT_STRING(0, 2, tm->tm_hour % 12); /* %2d */ break; case 'm': FORMAT_STRING(1, 2, tm->tm_mon + 1); /* %02d */ break; case 'M': FORMAT_STRING(1, 2, tm->tm_min); /* %02d */ break; case 'p': CHECK_SPACE(2); strcpy(s, (tm->tm_hour < 12) ? "am" : "pm"); break; case 'r': if (!xstrftime(s, bsz - l, "%I:%M:%S %p", tm)) return (0); break; case 'R': if (!xstrftime(s, bsz - l, "%H:%M", tm)) return (0); break; case 'S': FORMAT_STRING(1, 2, tm->tm_sec); /* %02d */ break; case 'T': if (!xstrftime(s, bsz - l, "%H:%M:%S", tm)) return (0); break; case 'W': /* mon 1 day of week */ { int week; if (tm->tm_yday <= tm->tm_wday) { week = 1; if ((tm->tm_mday - tm->tm_yday) > 4) { week = 52; } if (tm->tm_yday == tm->tm_wday && tm->tm_wday == 0) week = 52; } else { /* sun prev week */ int bw = tm->tm_yday - tm->tm_wday; if (tm->tm_wday > 0) bw += 7; /* sun end of week */ week = (int) bw / 7; if ((bw % 7) > 2) /* jan 1 is before friday */ week++; } FORMAT_STRING(1, 2, week); /* %02d */ break; } case 'U': /* sun 1 day of week */ { int week, bw; if (tm->tm_yday <= tm->tm_wday) { week = 1; if ((tm->tm_mday - tm->tm_yday) > 4) { week = 52; } } else { /* sat prev week */ bw = tm->tm_yday - tm->tm_wday - 1; if (tm->tm_wday >= 0) bw += 7; /* sat end of week */ week = (int) bw / 7; if ((bw % 7) > 1) { /* jan 1 is before friday */ week++; } } FORMAT_STRING(1, 2, week); /* %02d */ break; } case 'w': /* day of week, sun=0 */ FORMAT_STRING(1, 2, tm->tm_wday); /* %02d */ break; case 'y': FORMAT_STRING(1, 2, tm->tm_year % 100); /* %02d */ break; case 'Y': FORMAT_STRING(1, 4, tm->tm_year); /* %04d */ break; #if 0 case 'Z': COPY_STRING(tm->tm_zone); break; #endif } /* switch */ while (*s != '\0') { s++; l++; } } } return (l); } /* time_t */ double gtimegm(tm) struct tm *tm; { register int i; /* returns sec from year ZERO_YEAR, defined in plot.h */ double dsec; dsec = 0; if (tm->tm_year < ZERO_YEAR) { for (i = tm->tm_year; i < ZERO_YEAR; i++) { dsec -= (double) gdysize(i); } } else { for (i = ZERO_YEAR; i < tm->tm_year; i++) { dsec += (double) gdysize(i); } } if (tm->tm_mday > 0) { for (i = 0; i < tm->tm_mon; i++) { dsec += (double) mndday[i] + (i == 1 && (gdysize(tm->tm_year) > 365)); } dsec += (double) tm->tm_mday - 1; } else { dsec += (double) tm->tm_yday; } dsec *= (double) 24; dsec += tm->tm_hour; dsec *= 60.0; dsec += tm->tm_min; dsec *= 60.0; dsec += tm->tm_sec; 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, tm->tm_min, tm->tm_sec, dsec)); return (dsec); } int ggmtime(tm, l_clock) struct tm *tm; /* time_t l_clock; */ double l_clock; { /* l_clock is relative to ZERO_YEAR, jan 1, 00:00:00,defined in plot.h */ int i, days; /* dodgy way of doing wday - i hope it works ! */ int wday = JAN_FIRST_WDAY; /* eg 6 for 2000 */ FPRINTF((stderr, "%g seconds = ", l_clock)); tm->tm_year = ZERO_YEAR; tm->tm_mday = tm->tm_yday = tm->tm_mon = tm->tm_hour = tm->tm_min = tm->tm_sec = 0; if (l_clock < 0) { while (l_clock < 0) { int days_in_year = gdysize(--tm->tm_year); l_clock += days_in_year * DAY_SEC; /* 24*3600 */ /* adding 371 is noop in modulo 7 arithmetic, but keeps wday +ve */ wday += 371 - days_in_year; } } else { for (;;) { int days_in_year = gdysize(tm->tm_year); if (l_clock < days_in_year * DAY_SEC) break; l_clock -= days_in_year * DAY_SEC; tm->tm_year++; /* only interested in result modulo 7, but %7 is expensive */ wday += (days_in_year - 364); } } tm->tm_yday = (int) (l_clock / DAY_SEC); l_clock -= tm->tm_yday * DAY_SEC; tm->tm_hour = (int) l_clock / 3600; l_clock -= tm->tm_hour * 3600; tm->tm_min = (int) l_clock / 60; l_clock -= tm->tm_min * 60; tm->tm_sec = (int) l_clock; days = tm->tm_yday; /* wday%7 should be day of week of first day of year */ tm->tm_wday = (wday + days) % 7; while (days >= (i = mndday[tm->tm_mon] + (tm->tm_mon == 1 && (gdysize(tm->tm_year) > 365)))) { days -= i; tm->tm_mon++; } tm->tm_mday = days + 1; 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)); return (0); } #else /* USE_SYSTEM_TIME */ /* define gnu time routines in terms of system time routines */ int gstrftime(buf, bufsz, fmt, l_clock) char *buf; int bufsz; char *fmt; double l_clock; { time_t t = (time_t) l_clock; return strftime(buf, bufsz, fmt, gmtime(&t)); } double gtimegm(tm) struct tm *tm; { return (double) mktime(tm); } int ggmtime(tm, l_clock) struct tm *tm; double l_clock; { time_t t = (time_t) l_clock; struct tm *m = gmtime(&t); *tm = *m; /* can any non-ansi compilers not do this ? */ } #define NOTHING #define LETTER(L, width, field, extra) \ case L: s=read_int(s,width,&tm->field); extra; continue /* supplemental routine gstrptime() to read a formatted time */ char * gstrptime(s, fmt, tm) char *s; char *fmt; struct tm *tm; { FPRINTF((stderr, "gstrptime(\"%s\", \"%s\")\n", s, fmt)); /* linux does not appear to like years before 1902 * NT complains if its before 1970 * initialise fields to midnight, 1st Jan, 1970 (for relative times) */ tm->tm_sec = tm->tm_min = tm->tm_hour = 0; tm->tm_mday = 1; tm->tm_mon = 0; tm->tm_year = 70; /* oops - it goes wrong without this */ tm->tm_isdst = 0; for (; *fmt && *s; ++fmt) { if (*fmt != '%') { if (*s != *fmt) return s; ++s; continue; } assert(*fmt == '%'); switch (*++fmt) { case 0: /* uh oh - % is last character in format */ return s; case '%': /* literal % */ if (*s++ != '%') return s - 1; continue; LETTER('d', 2, tm_mday, NOTHING); LETTER('m', 2, tm_mon, NOTHING); LETTER('y', 2, tm_year, NOTHING); LETTER('Y', 4, tm_year, tm->tm_year -= 1900); LETTER('H', 2, tm_hour, NOTHING); LETTER('M', 2, tm_min, NOTHING); LETTER('S', 2, tm_sec, NOTHING); default: int_error("incorrect time format character", NO_CARET); } } 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)); /* mktime range-checks the time */ if (mktime(tm) == -1) { FPRINTF((stderr, "mktime() was not happy\n")); int_error("Invalid date/time [mktime() did not like it]", NO_CARET); } 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)); return s; } #endif /* USE_SYSTEM_TIME */ #ifdef TEST_TIME char *abbrev_month_names[] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}; char *full_month_names[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; char *abbrev_day_names[] = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"}; char *full_day_names[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; /* either print current time using supplied format, or read * supplied time using supplied format */ int main(argc,argv) int argc; char *argv[]; { char output[80]; if (argc < 2) { fputs("usage : test 'format' ['time']\n", stderr); exit(EXIT_FAILURE); } if (argc == 2) { struct timeb now; struct tm *tm; ftime(&now); tm = gmtime(&now.time); xstrftime(output, 80, argv[1], tm); puts(output); } else { struct tm tm; gstrptime(argv[2], argv[1], &tm); puts(asctime(&tm)); } exit(EXIT_SUCCESS); } #endif /* TEST_TIME */