minix/commands/simple/date.c
2005-04-21 14:53:53 +00:00

444 lines
9.5 KiB
C
Executable file

/* date - Display (or set) the date and time Author: V. Archer */
#include <sys/types.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#define MIN 60L /* # seconds in a minute */
#define HOUR (60 * MIN) /* # seconds in an hour */
#define DAY (24 * HOUR) /* # seconds in a day */
#define YEAR (365 * DAY) /* # seconds in a (non-leap) year */
int qflag, uflag, sflag, Sflag;
/* Default output file descriptor.
*/
int outfd = 1;
_PROTOTYPE(int main, (int argc, char **argv));
_PROTOTYPE(void putchar, (int c));
_PROTOTYPE(void pstring, (char *s, int len));
_PROTOTYPE(void pldecimal, (unsigned long d, int digits));
_PROTOTYPE(void pdecimal, (int d, int digits));
_PROTOTYPE(void fmtdate, (char *format, time_t t, struct tm *p));
_PROTOTYPE(time_t make_time, (char *t));
_PROTOTYPE(struct tm *september, (time_t *tp));
_PROTOTYPE(void usage, (void));
/* Main module. Handles P1003.2 date and system administrator's date. The
* date entered should be given GMT, regardless of the system's TZ!
*/
int main(argc, argv)
int argc;
char **argv;
{
time_t t;
struct tm *tm;
char *format;
char time_buf[40];
int n;
int i;
time(&t);
i = 1;
while (i < argc && argv[i][0] == '-') {
char *opt = argv[i++] + 1, *end;
if (opt[0] == '-' && opt[1] == 0) break;
while (*opt != 0) switch (*opt++) {
case 'q':
qflag = 1;
break;
case 's':
sflag = 1;
break;
case 'u':
uflag = 1;
break;
case 'S':
Sflag = 1;
break;
case 't':
/* (obsolete, now -r) */
case 'r':
if (*opt == 0) {
if (i == argc) usage();
opt = argv[i++];
}
t = strtoul(opt, &end, 10);
if (*end != 0) usage();
opt = "";
break;
default:
usage();
}
}
if (!qflag && i < argc && ('0' <= argv[i][0] && argv[i][0] <= '9')) {
t = make_time(argv[i++]);
sflag = 1;
}
format = "%c";
if (i < argc && argv[i][0] == '+') format = argv[i++] + 1;
if (i != argc) usage();
if (qflag) {
pstring("\nPlease enter date: MMDDYYhhmmss. Then hit the RETURN key.\n", -1);
n = read(0, time_buf, sizeof(time_buf));
if (n > 0 && time_buf[n-1] == '\n') n--;
if (n >= 0) time_buf[n] = 0;
t = make_time(time_buf);
sflag = 1;
}
if (sflag && stime(&t) != 0) {
outfd = 2;
pstring("No permission to set time\n", -1);
return(1);
}
tm = Sflag ? september(&t) : uflag ? gmtime(&t) : localtime(&t);
fmtdate(format, t, tm);
putchar('\n');
return(0);
}
/* Replacement for stdio putchar().
*/
void putchar(c)
int c;
{
static char buf[1024];
static char *bp = buf;
if (c != 0) *bp++ = c;
if (c == 0 || c == '\n' || bp == buf + sizeof(buf)) {
write(outfd, buf, bp - buf);
bp = buf;
}
}
/* Internal function that prints a n-digits number. Replaces stdio in our
* specific case.
*/
void pldecimal(d, digits)
unsigned long d;
int digits;
{
digits--;
if (d > 9 || digits > 0) pldecimal(d / 10, digits);
putchar('0' + (d % 10));
}
void pdecimal(d, digits)
int d, digits;
{
pldecimal((unsigned long) d, digits);
}
/* Internal function that prints a fixed-size string. Replaces stdio in our
* specific case.
*/
void pstring(s, len)
char *s;
int len;
{
while (*s)
if (len--)
putchar(*s++);
else
break;
}
/* Format the date, using the given locale string. A special case is the
* TZ which might be a sign followed by four digits (New format time zone).
*/
void fmtdate(format, t, p)
char *format;
time_t t;
struct tm *p;
{
int i;
char *s;
static char *wday[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"};
static char *month[] = {"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};
while (*format)
if (*format == '%') {
switch (*++format) {
case 'A':
pstring(wday[p->tm_wday], -1);
break;
case 'B':
pstring(month[p->tm_mon], -1);
break;
case 'D':
pdecimal(p->tm_mon + 1, 2);
putchar('/');
pdecimal(p->tm_mday, 2);
putchar('/');
case 'y':
pdecimal(p->tm_year % 100, 2);
break;
case 'H':
pdecimal(p->tm_hour, 2);
break;
case 'I':
i = p->tm_hour % 12;
pdecimal(i ? i : 12, 2);
break;
case 'M':
pdecimal(p->tm_min, 2);
break;
case 'X':
case 'T':
pdecimal(p->tm_hour, 2);
putchar(':');
pdecimal(p->tm_min, 2);
putchar(':');
case 'S':
pdecimal(p->tm_sec, 2);
break;
case 'U':
pdecimal((p->tm_yday - p->tm_wday + 13) / 7, 2);
break;
case 'W':
if (--(p->tm_wday) < 0) p->tm_wday = 6;
pdecimal((p->tm_yday - p->tm_wday + 13) / 7, 2);
if (++(p->tm_wday) > 6) p->tm_wday = 0;
break;
case 'Y':
pdecimal(p->tm_year + 1900, 4);
break;
case 'Z':
if (uflag) {
s = "GMT";
} else {
s = (p->tm_isdst == 1) ? tzname[1] : tzname[0];
}
pstring(s, strlen(s));
break;
case 'a':
pstring(wday[p->tm_wday], 3);
break;
case 'b':
case 'h':
pstring(month[p->tm_mon], 3);
break;
case 'c':
if (!(s = getenv("LC_TIME")))
s = "%a %b %e %T %Z %Y";
fmtdate(s, t, p);
break;
case 'd':
pdecimal(p->tm_mday, 2);
break;
case 'e':
if (p->tm_mday < 10) putchar(' ');
pdecimal(p->tm_mday, 1);
break;
case 'j':
pdecimal(p->tm_yday + 1, 3);
break;
case 'm':
pdecimal(p->tm_mon + 1, 2);
break;
case 'n': putchar('\n'); break;
case 'p':
if (p->tm_hour < 12)
putchar('A');
else
putchar('P');
putchar('M');
break;
case 'r':
fmtdate("%I:%M:%S %p", t, p);
break;
case 's':
pldecimal((unsigned long) t, 0);
break;
case 't': putchar('\t'); break;
case 'w':
putchar('0' + p->tm_wday);
break;
case 'x':
fmtdate("%B %e %Y", t, p);
break;
case '%': putchar('%'); break;
case '\0': format--;
}
format++;
} else
putchar(*format++);
}
/* Convert a local date string into GMT time in seconds. */
time_t make_time(t)
char *t;
{
struct tm tm; /* user specified time */
time_t now; /* current time */
int leap; /* current year is leap year */
int i; /* general index */
int fld; /* number of fields */
int f[6]; /* time fields */
static int days_per_month[2][12] = {
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }};
/* Get current time just in case */
now = time((time_t *) 0);
tm = *localtime(&now);
tm.tm_sec = 0;
tm.tm_mon++;
tm.tm_year %= 100;
/* Parse the time */
#if '0'+1 != '1' || '1'+1 != '2' || '2'+1 != '3' || '3'+1 != '4' || \
'4'+1 != '5' || '5'+1 != '6' || '6'+1 != '7' || '7'+1 != '8' || '8'+1 != '9'
<< Code unsuitable for character collating sequence >>
#endif
for (fld = 0; fld < sizeof(f)/sizeof(f[0]); fld++) {
if (*t == 0) break;
f[fld] = 0;
for (i = 0; i < 2; i++, t++) {
if (*t < '0' || *t > '9') usage();
f[fld] = f[fld] * 10 + *t - '0';
}
}
switch (fld) {
case 2:
tm.tm_hour = f[0]; tm.tm_min = f[1]; break;
case 3:
tm.tm_hour = f[0]; tm.tm_min = f[1]; tm.tm_sec = f[2];
break;
case 5:
tm.tm_mon = f[0]; tm.tm_mday = f[1]; tm.tm_year = f[2];
tm.tm_hour = f[3]; tm.tm_min = f[4];
break;
case 6:
tm.tm_mon = f[0]; tm.tm_mday = f[1]; tm.tm_year = f[2];
tm.tm_hour = f[3]; tm.tm_min = f[4]; tm.tm_sec = f[5];
break;
default:
usage();
}
/* Convert the time into seconds since 1 January 1970 */
if (tm.tm_year < 70)
tm.tm_year += 100;
leap = (tm.tm_year % 4 == 0 && tm.tm_year % 400 != 0);
if (tm.tm_mon < 1 || tm.tm_mon > 12 ||
tm.tm_mday < 1 || tm.tm_mday > days_per_month[leap][tm.tm_mon-1] ||
tm.tm_hour > 23 || tm.tm_min > 59) {
outfd = 2;
pstring("Illegal date format\n", -1);
exit(1);
}
/* Convert the time into Minix time - zone independent code */
{
time_t utctime; /* guess at unix time */
time_t nextbit; /* next bit to try */
int rv; /* result of try */
struct tm *tmp; /* local time conversion */
#define COMPARE(a,b) ((a) != (b)) ? ((a) - (b)) :
utctime = 1;
do {
nextbit = utctime;
utctime = nextbit << 1;
} while (utctime >= 1);
for (utctime = 0; ; nextbit >>= 1) {
utctime |= nextbit;
tmp = localtime(&utctime);
if (tmp == 0) continue;
rv = COMPARE(tmp->tm_year, tm.tm_year)
COMPARE(tmp->tm_mon + 1, tm.tm_mon)
COMPARE(tmp->tm_mday, tm.tm_mday)
COMPARE(tmp->tm_hour, tm.tm_hour)
COMPARE(tmp->tm_min, tm.tm_min)
COMPARE(tmp->tm_sec, tm.tm_sec)
0;
if (rv > 0)
utctime &= ~nextbit;
else if (rv == 0)
break;
if (nextbit == 0) {
uflag = 1;
outfd = 2;
pstring("Inexact conversion to UTC from ", -1);
fmtdate("%c\n", utctime, localtime(&utctime) );
exit(1);
}
}
return utctime;
}
}
/* Correct the time to the reckoning of Eternal September. */
struct tm *september(tp)
time_t *tp;
{
time_t t;
int days;
struct tm *tm;
tm = localtime(tp);
t = *tp - (tm->tm_hour - 12) * 3600L; /* No zone troubles around noon. */
days = 0;
while (tm->tm_year > 93 || (tm->tm_year == 93 && tm->tm_mon >= 8)) {
/* Step back a year or a month. */
days += tm->tm_year > 93 ? tm->tm_yday+1 : tm->tm_mday;
t = *tp - days * (24 * 3600L);
tm = localtime(&t);
}
if (days > 0) {
tm = localtime(tp);
tm->tm_mday = days;
tm->tm_year = 93;
tm->tm_mon = 8;
#if SANITY
t = mktime(tm);
tm = localtime(&t);
#endif
}
return tm;
}
/* (Extended) Posix prototype of date. */
void usage()
{
outfd = 2;
pstring("Usage: date [-qsuS] [-r seconds] [[MMDDYY]hhmm[ss]] [+format]\n", -1);
exit(1);
}