Trailing-Edge
-
PDP-10 Archives
-
SRI_NIC_PERM_FS_1_19910112
-
c/lib5/ctime.c
There are 5 other files named ctime.c in the archive. Click here to see a list.
/* CTIME and friends - date/time library routines
**
** Copyright (c) 1987 by Ken Harrenstien, SRI International
**
** These routines conform to those described by H&S (2nd ed) sec. 20.
** They are standard for V7, BSD, and SYSV, with two additional ANSI functions.
**
** However, the conventions for handling timezones and daylight savings time
** are NOT standardized. The currently known mechanisms are:
** V7: the ftime() syscall returns ".timezone" and ".dstflag" as
** structure components.
** A function "timezone()" returns the string for a timezone+dst.
** BSD: the gettimeofday() syscall returns ".tz_minuteswest" and
** ".tz_dsttime" as structure components.
** V7 ftime() is emulated for compatibility.
** The V7 function "timezone()" also exists.
** S5: There is no way to query the system re timezone. That info is
** furnished by the environment variable "TZ".
** A function "void tzset(void)" reads TZ and sets the vars below:
** "extern long timezone" holds the timezone in seconds.
** "extern int daylight" is nonzero only if USA DST is applicable.
** "extern char *tzname[2]" holds timezone strings (STD & DST).
**
** Note the conflict for the "timezone" symbol between S5 and everything else.
** We chose to support BSD-compatible software.
*/
#include "c-env.h"
#include "time.h" /* Note this is NOT sys/time.h ! */
#include "sys/time.h" /* For _DST_ values */
#include "sys/timeb.h" /* For ftime() syscall */
#define DAYSECS (24*60*60) /* # seconds in a day */
/* These two functions are only used externally by KCC's TIME.C
** Unix emulation code, and only on TOPS-10, WAITS, or ITS.
** This seems better than duplicating them just for modularity.
*/
int _tmisdst(); /* Check for local DST */
struct tm *_lbrktime(); /* Internal version of localtime */
/* Purely internal functions */
static struct tm *_gbrktime(); /* Internal version of gmtime */
static char *_tad2str(); /* Internal version of asctime */
static long _tzloc(); /* Get local timezone in secs (note long) */
static char cbuf[26]; /* Return value for asctime, ctime */
static struct tm ctm; /* Return value for gmtime, localtime */
/* Define local enum values here for our convenience. Unfortunately
** there is no standard for external symbols representing these values,
** so we cannot let user programs depend on them. Sigh.
*/
enum dow {Sun=0, Mon, Tue, Wed, Thu, Fri, Sat };
enum month {Jan=0, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };
/* CTMONTB - Table of days thus far in year, assuming non-leap year.
** CTLMONTB - ditto, assuming leap year.
** Indexed by month, with 0 = January.
*/
static int ctmontb[] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
static int ctlmontb[] = {
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
};
static int ctdaysinmon[] = {
/* J F M A M J J A S O N D */
31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static char *ctstrday[] = {
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
};
static char *ctstrmon[] = {
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
/* Externally available library routines are all on this one page. */
/* CTIME - Given a time_t value, return pointer to date/time string
*/
char *
ctime(tp)
time_t *tp;
{
struct tm tmp;
return _tad2str(_lbrktime(&tmp, *tp), cbuf);
}
/* ASCTIME - Given a broken-down tm struct, return pointer to date/time string
*/
char *
asctime(t)
struct tm *t;
{
return _tad2str(t, cbuf);
}
/* GMTIME - Break down time into structure, using GMT (no zone or DST)
*/
struct tm *
gmtime(tp)
time_t *tp;
{
return _gbrktime(&ctm, *tp);
}
/* LOCALTIME - Break down time into structure, using local timezone and DST
*/
struct tm *
localtime(tp)
time_t *tp;
{
return _lbrktime(&ctm, *tp);
}
/* TIMEZONE - V7/BSD specific function.
** Given timezone in minutes, returns pointer to a string
** representing that timezone.
*/
/* Table indexed by hour difference */
static char *ctzonstr[] = {
0, 0, 0, 0, 0, 0, 0, 0, /* -12 to -9 */
0, 0, 0, 0, 0, 0, 0, 0, /* -8 to -5 */
0, 0, 0, 0, 0, 0, 0, 0, /* -4 to -1 */
"GMT", "BST", /* 0 Greenwich Mean (British Summer) */
0, 0, 0, 0, 0, 0, /* 1 to 3 */
"AST", "ADT", /* 4 Atlantic Standard */
"EST", "EDT", /* 5 Eastern Standard */
"CST", "CDT", /* 6 Central Standard */
"MST", "MDT", /* 7 Mountain */
"PST", "PDT", /* 8 Pacific */
"YST", "YDT", /* 9 Yukon */
"HST", "HDT", /* 10 Hawaii */
"BST", "BDT", /* 11 Bering */
0, 0 /* 12 */
};
static char tzbuf[10]; /* For "GMT+hh:mm\0" */
char *
timezone(mwest, dstt)
int mwest, dstt;
{
register int i, rem;
register char *cp;
if (mwest < 0) {
i = -(-mwest/60); /* Get pos or neg hour number */
rem = -mwest%60; /* Get positive remainder */
} else {
i = mwest/60;
rem = mwest % 60;
}
/* See if name appears in table */
if (rem == 0 /* Round multiple of hours? */
&& -12 <= i && i <= 12 /* Between -12 and +12? */
&& ctzonstr[12*2 + i*2]) /* And exists in table? */
return ctzonstr[12*2 + i*2 + (dstt ? 1 : 0)];
/* Not in table, generate string with difference from GMT */
if (dstt) ++i; /* Assume DST always means bump up 1 hour */
cp = tzbuf;
*cp = 'G';
*++cp = 'M';
*++cp = 'T';
if (mwest < 0) { /* Neg means ahead of GMT */
*++cp = '+'; /* Convention is to indicate "ahead" with plus sign */
i = -i;
} else *++cp = '-'; /* Convention is to indicate "behind" with minus */
if (i > 10)
*++cp = '0' + ((i % 100) / 10); /* Output tens digit of hour */
*++cp = '0' + i%10; /* Output ones digit of hour */
*++cp = ':';
*++cp = '0' + (rem / 10); /* tens digit of remainder (min) */
*++cp = '0' + (rem % 10); /* ones digit of remainder (min) */
*++cp = '\0'; /* Polish off */
return tzbuf;
}
/* Miscellaneous auxiliary routines */
/* _TZLOC - return local timezone in seconds.
** First time, asks system for timezone, then stores it so future
** calls can avoid overhead of system call. Note that this value is given
** type "long" so that the code can work on 16-bit systems.
** The system call used is V7 ftime() rather than BSD gettimeofday()
** because the former is more likely to be widely supported, in case this
** code is used elsewhere. There is however a subtle assumption that ftime()
** will return 1 for "dstflag" if a USA type DST algorithm is to be used.
*/
static int _tlzknown = 0; /* Non-zero when next two values are known */
static long _tlzone; /* Local timezone in secs */
static int _tldstf; /* Local DST type to apply (a _DST_xxx val) */
static long
_tzloc()
{
struct timeb tmb;
if (!_tlzknown && ftime(&tmb) == 0) {
_tlzknown++; /* Say zone is known */
_tlzone = tmb.timezone * 60; /* Get zone in sec */
_tldstf = tmb.dstflag; /* Get type of DST to apply */
}
return _tlzone;
}
/* _TAD2STR - Time-And-Date to String.
** Workhorse routine for asctime().
*/
static char *
_tad2str(t, str)
register struct tm *t;
char *str;
{
register char *cp = str;
register char *src;
register int i;
/* First do day-of-week name (with range check) */
src = (0 <= t->tm_wday && t->tm_wday < 7)
? ctstrday[t->tm_wday] : "Badday";
*cp = *src; /* Copy 1st 3 chars of day name */
*++cp = *++src;
*++cp = *++src;
*++cp = ' ';
/* Then do month name (with range check) */
src = (0 <= t->tm_mon && t->tm_mon < 12)
? ctstrmon[t->tm_mon] : "Badember";
*++cp = *src; /* Copy 1st 3 chars of month name */
*++cp = *++src;
*++cp = *++src;
*++cp = ' ';
/* And then day of month */
if (1 <= t->tm_mday && t->tm_mday <= 31 ) {
*++cp = (i = (t->tm_mday / 10)) == 0 ? ' ' : (i + '0');
*++cp = (t->tm_mday % 10) + '0';
} else *++cp = '*', *++cp = '*';
*++cp = ' ';
/* Now do hours. */
if (0 <= t->tm_hour && t->tm_hour < 24) {
*++cp = (t->tm_hour / 10) + '0';
*++cp = (t->tm_hour % 10) + '0';
} else *++cp = '*', *++cp = '*';
*++cp = ':';
/* Minutes */
if (0 <= t->tm_min && t->tm_min < 60) {
*++cp = (t->tm_min / 10) + '0';
*++cp = (t->tm_min % 10) + '0';
} else *++cp = '*', *++cp = '*';
*++cp = ':';
/* Seconds */
if (0 <= t->tm_sec && t->tm_sec < 60) {
*++cp = (t->tm_sec / 10) + '0';
*++cp = (t->tm_sec % 10) + '0';
} else *++cp = '*', *++cp = '*';
*++cp = ' ';
/* Year (years since 1900) */
if (0 <= t->tm_year && t->tm_year < (10000-1900)) {
i = t->tm_year + 1900;
*++cp = (i / 1000) + '0';
*++cp = ((i %= 1000) / 100) + '0';
*++cp = ((i %= 100) / 10) + '0';
*++cp = (i % 10) + '0';
} else *++cp = '*', *++cp = '*', *++cp = '*', *++cp = '*';
*++cp = '\n';
*++cp = '\0'; /* Finally all done! */
return str; /* Return pointer to string */
}
/* _GBRKTIME - Workhorse routine for gmtime().
*/
static struct tm *
_gbrktime(t, utim)
struct tm *t;
time_t utim;
{
register int i, days, secs, yday, year;
/* Divide into days and seconds */
days = utim / DAYSECS; /* Get # of days */
secs = utim % DAYSECS; /* and remaining # secs in day */
/* Now split up seconds into hh:mm:ss */
t->tm_sec = secs % 60;
t->tm_min = (secs / 60) % 60;
t->tm_hour = (secs / 60) / 60;
/* Now a few other things that are easy to do */
t->tm_wday = (days + Thu) % 7; /* epoch was a Thurs, must adjust. */
t->tm_isdst = 0; /* GMT, so DST never applies */
/* Now use days to find year plus remaining # of days in that year */
year = (days / 365); /* Find # of "normal" years */
yday = (days % 365) - ((year+1)/4); /* Get rem days minus # of leap yrs */
year += 70; /* Now can make base 1900, not 1970 */
if (yday < 0) { /* Backed past beg of year? */
year--; /* decrement # years */
yday += 365 /* and adjust remaining # days */
+ ((year&03)==0 ? 1 : 0); /* If now in leap year, add one more */
}
t->tm_year = year; /* Now can set these! */
t->tm_yday = yday;
/* Finally, find and set month and day in month */
i = Dec; /* Start from December backwards */
if ((year&03)==0) { /* If leap year, use leap table */
while (yday < ctlmontb[i]) --i; /* Find leap month this yday is in */
t->tm_mday = (yday - ctlmontb[i]) + 1;
} else {
while (yday < ctmontb[i]) --i; /* Find month this yday is in */
t->tm_mday = (yday - ctmontb[i]) + 1;
}
t->tm_mon = i;
return t;
}
struct tm *
_lbrktime(t, utim)
struct tm *t;
time_t utim;
{
if (_gbrktime(t, utim - _tzloc()) == 0) /* Crack zone-adjusted time */
return 0;
/* Now check for DST adjustment. Note that the above call to _tzloc()
** ensures that _tldstf is already set.
*/
if (t->tm_isdst = _tmisdst(t, _tldstf)) { /* Set DST-applied flag */
if (++(t->tm_hour) >= 24) {
/* Bumped into next day, sigh. Rather than checking for more and
** more global bumpings, just add an hour to the original time
** and crack it again!
*/
int savdst = t->tm_isdst; /* Remember DST flag */
_gbrktime(t, (time_t)60*60 + utim - _tzloc());
t->tm_isdst = savdst; /* Restore DST flag, */
} /* this avoids re-asking */
}
return t;
}
/* _TMISDST - Given a broken-down time and DST type, determine whether DST
** applies to that time.
**
** _DST_USA algorithm:
** Congress enacted nationwide DST in 1967 such that it:
** Starts at 2AM on the last Sunday of April, ends at 1AM standard
** time on the last Sunday in October.
** This is herein termed "old DST".
** There was also forced daylight time in 1974 and 1975: "forced DST".
** As of 1987 the new rule is that DST starts on the FIRST Sunday of
** April (it still ends on the last Sunday of October): "new DST".
** There will undoubtedly be future rule revisions.
*/
static struct {int beg, end; } _ctdstus[] = {
115, 297, /* 1970 old DST 26-Apr to 25-Oct */
114, 303, /* 1971 old DST 25-Apr to 31-Oct */
120, 302, /* 1972 old DST 30-Apr to 29-Oct */
118, 300, /* 1973 old DST 29-Apr to 28-Oct */
5, 299, /* 1974 forced 5-Jan (first Sun), to 27-Oct */
53, 298, /* 1975 forced 23-Feb (last Sun), to 26-Oct */
/* Note: 4.3BSD claims 1974 DST ended on 24-Nov (last Sun in Nov) */
115, 304, /* 1976 old DST 25-Apr to 31-Oct */
113, 302, /* 1977 old DST 24-Apr to 30-Oct */
119, 301, /* 1978 old DST 30-Apr to 29-Oct */
118, 300, /* 1979 old DST 29-Apr to 28-Oct */
117, 299, /* 1980 old DST 27-Apr to 26-Oct */
115, 297, /* 1981 old DST 26-Apr to 25-Oct */
114, 303, /* 1982 old DST 25-Apr to 31-Oct */
113, 302, /* 1983 old DST 24-Apr to 30-Oct */
119, 301, /* 1984 old DST 29-Apr to 28-Oct */
117, 299, /* 1985 old DST 28-Apr to 27-Oct */
116, 298, /* 1986 old DST 27-Apr to 26-Oct */
};
#define NDSTYRS (sizeof(_ctdstus)/sizeof(_ctdstus[0])) /* # entries */
/* This is an explanation of how the 1st and last Sunday of a month is
** calculated here. Given a tm structure with an already broken-down time,
** things are relatively straightforward:
**
** First we find the day-of-week for day 1 of the month, by subtracting
** the current day-of-month from the wday (going backwards that many days),
** modulo 7.
** dow1 = (35 + wday - (mday-1)) % 7;
** The addition of 35 ensures that we don't try to take the remainder
** of a negative number.
**
** Second, given the wday for mday 1, we find how many days there are
** between that wday and the one we want (in this case Sunday); a simple
** modular subtraction.
** desired-mday = (7 + desired-wday - dow1) % 7 + 1;
** Again, the addition of 7 ensures that the operand of % is positive.
** The result is zero-based, so 1 must be added to produce the desired
** day-of-month.
**
** The following macro combines the above two equations:
*/
#define newmday(dow, mday, wday) \
((7 + (dow) - (35+(wday)-(mday-1))%7)%7 + 1)
/*
** Third, now that we have the desired day-of-month, we can
** get the desired day-of-year by removing the old mday value and adding
** the new one.
** desired-yday = (yday - mday) + desired-mday;
**
** In order to find the LAST day-of-week in a month, we simply move up
** to a fictional "next month", find the desired-yday of the FIRST
** desired-wday within that new month, and subtract 7 (one week) to land
** back in the old month.
*/
int
_tmisdst(t, dsttyp)
struct tm *t; /* Broken-down time. Uses year,yday,wday,mon,hour */
int dsttyp; /* DST type, a _DST_xxx value */
{
register int i, beg, end;
switch (dsttyp) {
default: /* Unknown DST type, ignore it. */
case _DST_OFF: /* DST always off */
break;
case _DST_ON: /* DST always on */
return 1;
case _DST_USA: /* Use US DST algorithms */
if (0 <= (t->tm_year-70) && (t->tm_year-70) < NDSTYRS) {
/* Within table? */
beg = _ctdstus[t->tm_year-70].beg; /* Yes, get ranges from tbl */
end = _ctdstus[t->tm_year-70].end;
} else {
/* Year not in precomputed table. Assume we're using the
** "new DST" rule (from 1st Sunday in April, to last Sunday in Oct)
** and do hairy computations if necessary.
*/
switch (t->tm_mon) { /* Quick cheat */
case Jan: case Feb: case Mar: /* Before April? */
case Nov: case Dec: /* After October? */
default:
return 0; /* Yes, no DST - quick lossage! */
case May: case Jun: /* Or within normal DST range? */
case Jul: case Aug:
case Sep:
return 1; /* Yes, in DST - quick winnage! */
case Apr: /* In April. Find 1st Sunday */
beg = t->tm_yday - t->tm_mday /* See explanation */
+ newmday(Sun, t->tm_mday, t->tm_wday);
end = ctmontb[Oct]; /* Just needs to be bigger than Apr */
break; /* Drop thru for check */
case Oct: /* In October. Find last Sunday */
beg = ctmontb[Apr]; /* Just needs to be smaller than Oct */
i = ctdaysinmon[Oct] - t->tm_mday; /* Days left in mon */
end = t->tm_yday + i /* See explanation */
+ newmday(Sun, 1, (t->tm_wday+i+1)%7)
- 7;
break;
}
}
/* At this point, we have the Julian-day (tm_yday) ranges for which
** DST applies during the current year.
*/
if ( t->tm_yday < beg /* Check ranges */
|| t->tm_yday > end)
return 0; /* Outside range, no DST */
if (t->tm_yday == beg) /* Start day? */
return (t->tm_hour >= 2); /* DST if 2AM or past */
if (t->tm_yday == end) /* End day? */
return (t->tm_hour < 2); /* DST if before 2AM */
return 1; /* Assume inside range of DST */
}
return 0;
}
/* New ANSI routines - MKTIME and DIFFTIME */
/* MKTIME - Make a time_t value out of a broken-down time.
*/
time_t
mktime(t)
register struct tm *t;
{
register int days, tim;
int leap, yday;
time_t ret;
/* Find # of days thus far in this year. */
days = yday =
((t->tm_year&03)==0 ? /* Leap year? */
ctlmontb[t->tm_mon] /* # days in leap months so far */
: ctmontb[t->tm_mon]) /* # days in reg months so far */
+ t->tm_mday - 1; /* plus # days this month */
/* Now add # days in years thus far */
days += ((t->tm_year-70) * 365) /* # days in years so far */
+ (((t->tm_year-70) + 1)>>2); /* plus # of leap days since 1970 */
/* Now figure out the time in seconds, plus adjustments. */
tim = ((t->tm_hour * 60) + t->tm_min) * 60 + t->tm_sec /* Base time */
+ _tzloc() /* plus timezone */
+ (t->tm_isdst ? -60*60 : 0); /* plus DST adjust */
ret = days * DAYSECS + tim; /* Add together all the secs */
/* Have time value, now check source structure to see if must fix up. */
if ( tim < 0 || DAYSECS <= tim /* time underflow/overflow? */
|| t->tm_hour < 0 || 23 < t->tm_hour
|| t->tm_min < 0 || 59 < t->tm_min
|| t->tm_sec < 0 || 59 < t->tm_sec
|| t->tm_mon < 0 || 11 < t->tm_mon
|| t->tm_mday < 1 || ctdaysinmon[t->tm_mon] < t->tm_mday
|| t->tm_year < 0 || 135 < t->tm_year)
_lbrktime(t, ret); /* Do full breakdown */
else {
t->tm_wday = (days + Thu) % 7;
t->tm_yday = yday;
}
return ret;
}
double
difftime(t1, t0)
time_t t1, t0;
{
return (double) (t1 - t0);
}