/* mktime, localtime, gmtime */ /* written by ERS and placed in the public domain */ #include #include #include #include #ifndef _COMPILER_H #include #endif #if 0 static void DEBUG_TM(nm, tm) char *nm; struct tm *tm; { char buf[100]; (void)strftime(buf, 100, "%c %z", tm); printf("%s: %s\n", nm, buf); } #else #define DEBUG_TM(nm, tm) #endif #define SECS_PER_MIN (60L) #define SECS_PER_HOUR (3600L) #define SECS_PER_DAY (86400L) #define SECS_PER_YEAR (31536000L) #define SECS_PER_LEAPYEAR (SECS_PER_DAY + SECS_PER_YEAR) time_t _timezone = -1; /* holds # seconds west of GMT */ static int days_per_mth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static int mth_start[13] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; static time_t tzoffset __PROTO((char *s, int *hasdst)); static int indst __PROTO((const struct tm *t)); static int dst = -1; /* whether dst holds in current timezone */ /* * FIXME: none of these routines is very efficient. Also, none of them * handle dates before Jan 1, 1970. * */ /* * mktime: take a time structure representing the local time (such as is * returned by localtime() and convert it into the standard representation * (as seconds since midnight Jan. 1 1970, GMT). * * Note that time() sends us such a structure with tm_yday undefined, so * we shouldn't count on tm_yday being correct. */ time_t mktime(t) const struct tm *t; { time_t s; int y; DEBUG_TM("mktime", t); if (t->tm_year < 70) /* year before 1970 */ return (time_t) -1; /* calculate tm_yday here */ y = (t->tm_mday - 1) + mth_start[t->tm_mon] + /* leap year correction */ ( ( (t->tm_year % 4) != 0 ) ? 0 : (t->tm_mon > 1) ); s = (t->tm_sec)+(t->tm_min*SECS_PER_MIN)+(t->tm_hour*SECS_PER_HOUR) + (y*SECS_PER_DAY)+((t->tm_year - 70)*SECS_PER_YEAR) + ((t->tm_year - 69)/4)*SECS_PER_DAY; /* Now adjust for the time zone and possible daylight savings time */ /* note that we have to call tzset() every time; see 1003.1 sect 8.1.1 */ tzset(); s += _timezone; if (dst == 1 && indst(t)) s -= 3600; return s; } struct tm *_gmtime(t, stm) const time_t *t; struct tm *stm; { time_t time = *t; int year, mday, i; if (time < 0) /* negative times are bad */ return 0; stm->tm_wday = ((time/SECS_PER_DAY) + 4) % 7; year = 70; for (;;) { if (time < SECS_PER_YEAR) break; if ((year % 4) == 0) { if (time < SECS_PER_LEAPYEAR) break; else time -= SECS_PER_LEAPYEAR; } else time -= SECS_PER_YEAR; year++; } stm->tm_year = year; mday = stm->tm_yday = time/SECS_PER_DAY; days_per_mth[1] = (year % 4) ? 28 : 29; for (i = 0; mday >= days_per_mth[i]; i++) mday -= days_per_mth[i]; stm->tm_mon = i; stm->tm_mday = mday + 1; time = time % SECS_PER_DAY; stm->tm_hour = time/SECS_PER_HOUR; time = time % SECS_PER_HOUR; stm->tm_min = time/SECS_PER_MIN; stm->tm_sec = time % SECS_PER_MIN; stm->tm_isdst = 0; DEBUG_TM("gmtime", stm); return stm; } struct tm *gmtime(t) const time_t *t; { static struct tm gtime; return _gmtime(t, >ime); } /* given a standard time, convert it to a local time */ struct tm *localtime(t) const time_t *t; { static struct tm ltim; struct tm *stm; time_t offset; /* seconds between local time and GMT */ tzset(); offset = *t - _timezone; stm = _gmtime(&offset, <im); if (stm == NULL) return stm; /* check for illegal time */ stm->tm_isdst = (dst == -1) ? -1 : 0; if (dst == 1 && indst((const struct tm *)stm)) { /* daylight savings time in effect */ stm->tm_isdst = 1; if (++stm->tm_hour > 23) { stm->tm_hour -= 24; stm->tm_wday = (stm->tm_wday + 1) % 7; stm->tm_yday++; stm->tm_mday++; if (stm->tm_mday > days_per_mth[stm->tm_mon]) { stm->tm_mday = 1; stm->tm_mon++; } } } DEBUG_TM("localtime", stm); return stm; } /* * THIS IS A DELIBERATE VIOLATION OF THE ANSI STANDARD: * there appears to be a conflict between Posix and ANSI; the former * mandates a "tzset()" function that gets called whenever time() * does, and which sets some global variables. ANSI wants none of * this. Several Unix implementations have tzset(), and few people are * going to be hurt by it, so it's included here. */ /* set the timezone and dst flag to the local rules. Also sets the global variable tzname to the names of the timezones */ char *tzname[2] = {"UCT", "UCT"}; void tzset() { _timezone = tzoffset(getenv("TZ"), &dst); } /* * determine the difference, in seconds, between the given time zone * and Greenwich Mean. As a side effect, the integer pointed to * by hasdst is set to 1 if the given time zone follows daylight * savings time, 0 if there is no DST. * * Time zones are given as strings of the form * "[TZNAME][h][:m][TZDSTNAME]" where h:m gives the hours:minutes * east of GMT for the timezone (if [:m] does not appear, 0 is assumed). * If the final field, TZDSTNAME, appears, then the time zone follows * daylight savings time. * * Example: EST5EDT would represent the N. American Eastern time zone * CST6CDT would represent the N. American Central time zone * NFLD3:30NFLD would represent Newfoundland time (one and a * half hours ahead of Eastern). * OZCST-9:30 would represent the Australian central time zone. * (which, so I hear, doesn't have DST). * * NOTE: support for daylight savings time is currently very bogus. * It's probably best to do without, unless you live in North America. * */ #define TZNAMLEN 8 /* max. length of time zone name */ static time_t tzoffset(s, hasdst) char *s; int *hasdst; { time_t off = 0; int x, sgn = 1; static char stdname[TZNAMLEN+1], dstname[TZNAMLEN+1]; static char unknwn[4] = "???"; char *n; *hasdst = -1; /* Assume unknown */ if (!s || !*s) return 0; /* Assume GMT */ *hasdst = 0; n = stdname; while (*s && isalpha(*s)) { *n++ = *s++; /* skip name */ } *n++ = 0; /* now figure out the offset */ x = 0; if (*s == '-') { sgn = -1; s++; } while (isdigit(*s)) { x = 10 * x + toint(*s); s++; } off = x * SECS_PER_HOUR; if (*s == ':') { x = 0; s++; while (isdigit(*s)) { x = 10 * x + toint(*s); s++; } off += (x * SECS_PER_MIN); } n = dstname; if (isalpha(*s)) { *hasdst = 1; while (*s && isalpha(*s)) *n++ = *s++; } *n++ = 0; if (stdname[0]) tzname[0] = stdname; else tzname[0] = unknwn; if (dstname[0]) tzname[1] = dstname; else tzname[1] = stdname; return sgn * off; } /* * Given a tm struct representing the local time, determine whether * DST is currently in effect. This should only be * called if it is known that the time zone indeed supports DST. * * FIXME: For now, assume that everyone follows the North American * time zone rules, all the time. This means daylight savings * time is assumed to be in effect from the first Sunday in April * to the last Sunday in October. Prior to 1987, the old rules * (last Sunday in April to last Sunday in Oct.) are used, even when * (as in 1974) they're not applicable. Sorry. * */ static int indst(t) const struct tm *t; { if (t->tm_mon == 3) { /* April */ /* before 1987, see if there's another sunday in the month */ if (t->tm_year < 87 && t->tm_wday + 30 - t->tm_mday < 7) return 1; /* no there isn't */ /* after 1987, see if a sunday has happened yet */ if (t->tm_wday - t->tm_mday < 0) return 1; /* yep */ return 0; } if (t->tm_mon == 9) { /* October */ if (t->tm_wday + 31 - t->tm_mday < 7) return 0; /* there are no more sundays */ return 1; } /* Otherwise, see if it's a month between April and October exclusive */ return (t->tm_mon > 3 && t->tm_mon < 9); }