[comp.sources.misc] v04i014: yacc date/time parser

edf@ROCKEFELLER.ARPA (David MacKenzie) (08/06/88)

Posting-number: Volume 4, Issue 14
Submitted-by: "David MacKenzie" <edf@ROCKEFELLER.ARPA>
Archive-name: unctime.y

Here's a powerful YACC time/date parser, along with a sample application
called settime that changes file timestamps.  Settime is actually
descended from a program I found on the net several years ago.  Not much
of the original is still recognizable.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  MANIFEST Makefile settime.1 settime.c unctime.y
# Wrapped by dave@zedfdc  on Fri Aug  5 18:49:53 1988
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
echo shar: Extracting \"'MANIFEST'\" \(269 characters\)
sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X MANIFEST                   1	This shipping list
X Makefile                   1	
X settime.1                  1	
X settime.c                  1	
X unctime.y                  1	
END_OF_FILE
if test 269 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
fi
# end of 'MANIFEST'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(671 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X# Makefile for settime
X
X# Defines you can put in CFLAGS for unctime.y:
X# -DBSD=1	use gettimeofday() for timezone correction
X# -DFTIME=1	use ftime() for timezone correction
X# (default)	use tzset() for timezone correction
XCFLAGS = -O
XBIN = /edf4/dave/bin
X
Xsettime:	settime.o y.tab.o
X	$(CC) settime.o y.tab.o -o settime
X	strip settime
X
Xinstall: settime
X	cp settime $(BIN)
X
Xy.tab.c:	unctime.y
X	$(YACC) unctime.y
X
Xshar:	settime.c unctime.y Makefile settime.1
X	shar settime.c unctime.y Makefile settime.1 > settime.shar
X
Xkit:
X	makekit -m -p
X
Xclean:
X	-rm -f settime core settime.o y.tab.o y.tab.c
X
X# Add missing default rule for UNOS.
X.SUFFIXES:	.o
X.c.o:
X	$(CC) $(CFLAGS) -c $<
END_OF_FILE
if test 671 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'settime.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'settime.1'\"
else
echo shar: Extracting \"'settime.1'\" \(1935 characters\)
sed "s/^X//" >'settime.1' <<'END_OF_FILE'
X.TH SETTIME 1
X.SH NAME
Xsettime \- change the access and modification times of files
X.SH SYNOPSIS
X.B settime
X[
X.B \-amv
X] [
X.B \-f file
X] [
X.B time
X]
X.B file ...
X.SH DESCRIPTION
X.I Settime
Xchanges the access and modification timestamps of the specified
X.I files
Xto the given
X.I time,
Xwhich is a time and/or date in almost any reasonable format.
XAny parts of the time left unspecified take on the current values.
X.I Settime
Xwas written in response to the problem of the modification times of
Xrestored files. 
X.PP
XOptions:
X.TP
X.I \-a
Xchange only the access timestamp
X.TP
X.I \-m
Xchange only the modification timestamp
X.TP
X.I \-f file
Xuse the timestamps on
X.I file
Xinstead of an absolute
X.I time,
Xwhich is omitted.
X.TP
X.I \-v
X(verbose) display the access and modification times that the
X.I files
Xwill be set to in ctime() format.
X.PP
XExamples:
X.sp
XTo set the access and modification times of files foo and bar to the
Xgiven date and time:
X.RS
Xsettime 'Jan 4, 1987 7:00 pm' foo bar
X.RE
X.sp
XTo set the access and modification times of foo to the given date and
Xthe current time, and display the times which are being set:
X.RS
Xsettime -v '12/8/90' foo
X.RE
X.sp
XTo set the access time of foo to the access time of bar:
X.RS
Xsettime -a -fbar foo
X.RE
X.SH BUGS
XThe inode change time will be set to the time you ran
X.I settime.
XIf an absolute
X.I time
Xis specified, it must be enclosed in quotes so that it
Xis contained within one argument.  
X.I Settime
Xwill not complain if you specify a
X.I time
Xbefore the earliest time that
Xcan be stored internally (Jan 1, 1970 on Unix; Jan 1, 1980 on UNOS), but
Xthe set time will be incorrect.
X.SH AUTHORS
X.TP
XJohn F. Haugh (jfh@killer.UUCP) 
XOriginal author.
X.TP
XDavid MacKenzie (edf@rocky2.rockefeller.edu)
XReplaced John's original time evaluator with an improved version of
XMike Haertel's unctime() parser and rewrote the argument parser.
X.TP
XMichael Haertel (mike@wheaties.ai.mit.edu)
XWrote most of unctime().
END_OF_FILE
if test 1935 -ne `wc -c <'settime.1'`; then
    echo shar: \"'settime.1'\" unpacked with wrong size!
fi
# end of 'settime.1'
fi
if test -f 'settime.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'settime.c'\"
else
echo shar: Extracting \"'settime.c'\" \(2647 characters\)
sed "s/^X//" >'settime.c' <<'END_OF_FILE'
X/*
X * settime - change the access and modification times of files
X * Usage: settime [-amv] [-f file] [time] file...
X * Options:
X * -a		only change access time
X * -m		only change modification time
X * -v		(verbose) display time being set
X * -f file	set time to time of file
X * 
X * Exit status = number of files that couldn't be set.
X *
X * Latest revision: 08/05/88
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X
Xint     verbose = 0;		/* Show time being set? */
Xint     seta = 0;		/* Change access time? */
Xint     setm = 0;		/* Change modification time? */
X
Xmain(argc, argv)
X    int     argc;
X    char  **argv;
X{
X    char   *ctime();
X    time_t  unctime();
X    extern int optind;
X    extern char *optarg;
X    time_t  newatime;		/* Value to set access time to. */
X    time_t  newmtime;		/* Value to set modification time to. */
X    struct stat statbuf;
X    char   *fromfile = NULL;	/* Filename to set from. */
X    int     nerrs = 0;
X    int     c;			/* Option character. */
X
X    while ((c = getopt(argc, argv, "amvf:")) != EOF)
X	switch (c) {
X	case 'a':
X	    seta = 1;
X	    break;
X	case 'f':
X	    fromfile = optarg;
X	    break;
X	case 'm':
X	    setm = 1;
X	    break;
X	case 'v':
X	    verbose = 1;
X	    break;
X	default:
X	    usage(argv[0]);
X	}
X
X    if (seta + setm == 0)
X	seta = setm = 1;
X
X    if (optind == argc)
X	usage(argv[0]);
X
X    if (fromfile) {
X	if (stat(fromfile, &statbuf)) {
X	    perror(fromfile);
X	    exit(1);
X	}
X	newatime = statbuf.st_atime;
X	newmtime = statbuf.st_mtime;
X    } else if (optind == argc - 1) {
X	usage(argv[0]);
X    } else {
X	newatime = newmtime = unctime(argv[optind++]);
X	if (newatime == (time_t) - 1) {
X	    fprintf(stderr, "%s: bad time\n", argv[0]);
X	    exit(1);
X	}
X    }
X
X    if (verbose) {
X	if (seta)
X	    printf("Setting access time to       %s", ctime(&newatime));
X	if (setm)
X	    printf("Setting modification time to %s", ctime(&newmtime));
X    }
X    for (; optind < argc; ++optind)
X	if (settm(argv[optind], newatime, newmtime)) {
X	    perror(argv[optind]);
X	    ++nerrs;
X	}
X    exit(nerrs);
X}
X
X/*
X * Return 1 if fail, 0 if ok.
X */
Xsettm(file, newa, newm)
X    char   *file;
X    time_t  newa,
X            newm;
X{
X    time_t  utimebuf[2];	/* [0] = accessed, [1] = modified. */
X    struct stat statbuf;
X
X    utimebuf[0] = newa;
X    utimebuf[1] = newm;
X
X    if (seta == 0 || setm == 0) {
X	if (stat(file, &statbuf) == -1)
X	    return 1;
X	if (seta == 0)
X	    utimebuf[0] = statbuf.st_atime;
X	if (setm == 0)
X	    utimebuf[1] = statbuf.st_mtime;
X    }
X    return utime(file, utimebuf);
X}
X
Xusage(f)
X    char   *f;
X{
X    fprintf(stderr, "Usage: %s [-amv] [-f file] [time] file...\n", f);
X    exit(1);
X}
END_OF_FILE
if test 2647 -ne `wc -c <'settime.c'`; then
    echo shar: \"'settime.c'\" unpacked with wrong size!
fi
# end of 'settime.c'
fi
if test -f 'unctime.y' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'unctime.y'\"
else
echo shar: Extracting \"'unctime.y'\" \(10015 characters\)
sed "s/^X//" >'unctime.y' <<'END_OF_FILE'
X/* time_t
X   unctime(s)
X  	char *s;
X
XConvert s, which may be in almost any reasonable date format, to
Xa time_t integer suitable for consumption by ctime(3).  Coincidentally
Xdestroys the contents of s.  Return -1 if s is not a recognizable legal date.
X
XAny parts of the time left unspecified take on the current values.
X
X"4 CST + 23[:10]" adds 23 minutes and optionally 10 seconds to the correction.
X"# nnnnnn" forces exactly nnnnnn seconds GMT since Jan. 1, 1970.
X  
XCopyright 1988, Michael J. Haertel.  Use this routine at your own risk.
XYou may redistribute verbatim copies of this file.  You may redistribute
Xmodified versions of this file so long as (1) you state who last changed
Xit, and (2) this copyright notice appears unmodified.
X
XSome debugging by W. Anthony Smith.
X
XBug fix, minor enhancements, and non-BSD modifications by David MacKenzie. */
X
X%{
X#include <ctype.h>
X#if BSD
X# include <sys/time.h>
X#else
X# include <time.h>
X# if FTIME
X#  include <sys/timeb.h>
X# endif
X#endif
X#include <sys/types.h>
X
X#define FALSE (0)
X#define TRUE  (1)
X
Xextern long atol();
X
X/* Delta is correction to turn specified time into GMT. */
X/* if (zoneflag), a timezone was explicitly specified. */
Xstatic year, month, day, hour, minute, second, delta, zoneflag, error, iflag;
Xstatic long iresult;
X
X#define YYSTYPE long
X%}
X
X%token NUM MONTH AM PM
X
X%%
X
Xdate:
X  day time year
X  | day year time
X  | time day year
X  | time day
X  | day time
X  | day year
X  | day
X  | time
X  | '#' NUM		{ iflag = TRUE; iresult = $2; }
X  ;			/* previous line forces exact time in seconds GMT */
X
Xday:
X  NUM MONTH		{ month = $2; day = $1; }
X  | MONTH NUM		{ month = $1; day = $2; }
X  | NUM '/' NUM		{ month = $1; day = $3; }
X  ;
X
Xyear:
X  ',' NUM		{ year = $2; }
X  | '/' NUM		{ year = $2; }
X  | NUM			{ year = $1; }
X  ;
X
Xtime:
X  clock AM		{ hour %= 12; }
X  | clock PM		{ hour = hour % 12 + 12; }
X  | clock
X  ;
X
Xclock:
X  NUM ':' NUM ':' NUM	{ hour = $1; minute = $3; second = $5; }
X  | NUM ':' NUM		{ hour = $1; minute = $3; }
X  ;
X
X%%
X
X/* Return true if s is a prefix of t; e.g. prefix("mar", "march") = true. */
Xstatic
Xprefix(s,t)
X     char *s, *t;
X{
X  while (*s == *t && *s)
X    s++, t++;
X  return *s == 0;
X}
X
Xstatic char *lexptr;
X
Xstatic void
Xinitlex(s)
X     char *s;
X{
X  lexptr = s;
X  while (*s)
X    {
X      if (isupper(*s))
X	*s = tolower(*s);
X      s++;
X    }
X}
X
Xstatic char *
Xmonths[] =
X{
X  "jan",
X  "feb",
X  "mar",
X  "apr",
X  "may",
X  "jun",
X  "jul",
X  "aug",
X  "sep",
X  "oct",
X  "nov",
X  "dec",
X  0
X};
X
Xstruct zonename
X{
X  char *name;			/* Name of the time zone. */
X  int delta;			/* Correction to add to GMT (in minutes) */
X};
X
Xstatic struct zonename zones[] =
X{
X  "gmt", 0,
X  "est", -5 * 60,
X  "edt", -6 * 60,
X  "cst", -6 * 60,
X  "cdt", -7 * 60,
X  "mst", -7 * 60,
X  "mdt", -8 * 60,
X  "pst", -8 * 60,
X  "pdt", -9 * 60,
X  0, 0
X};
X
X/* Lexical analyzer.  Gather alphabetics into tokens; if they are unknown
X   strings ignore them, and if they are months return the appropriate value.
X   If the token is the name of the time zone set delta = correction and
X   zoneflag = TRUE, and skip ahead to the next token (the parser itself
X   never sees time zones).
X   If the token is a number, return its value.
X   If it is a punctuation mark, return the character code.
X   Ignore white space.  */
Xstatic
Xyylex()
X{
X  register i;
X  char token[40];	/* Probably paranoid. */
X  
X  for (;;)
X    {
X      while (isspace(*lexptr))
X	lexptr++;
X      if (isalpha(*lexptr))
X	{
X	  i = 0;
X	  while (isalpha(*lexptr))
X	    token[i++] = *lexptr++;	/* Null termination is automatic. */
X	  for (i = 0; months[i]; i++)
X	    if (prefix(months[i],token))
X	      {
X		yylval = i + 1;
X		return MONTH;
X	      }
X	  for (i = 0; zones[i].name; i++)
X	    if (prefix(zones[i].name,token))
X	      {
X		int oper, next;
X
X		zoneflag = TRUE;
X		delta = zones[i].delta;
X		oper = yylex();
X		/* Syntax: "4 CST + 23[:10]" adds 23 minutes and
X		optionally 10 seconds to delta (the correction). */
X		if (oper == '+' || oper == '-')
X		  {
X		    (void) yylex();
X		    delta += (oper == '+' ? 60 : -60) * yylval;
X		    next = yylex();
X		    if (next == ':')
X		      {
X			(void) yylex();
X			delta += (oper == '+' ? 1 : -1) * yylval;
X		      }
X		    else
X		      return next;
X		  }
X		else
X		  return oper;
X	      }
X	  if (prefix("pm",token) || prefix("p.m.", token))
X	    return PM;
X	  if (prefix("am",token) || prefix("a.m.", token))
X	    return AM;
X	  continue;
X	}
X      else if (isdigit(*lexptr))
X	{
X	  i = 0;
X	  while (isdigit(*lexptr))
X	    token[i++] = *lexptr++;
X	  token[i] = '\0';
X	  yylval = atoi(token);
X	  return NUM;
X	}
X      else
X	return *lexptr++;
X    }
X}
X
X/* ARGSUSED */
Xstatic
Xyyerror(s)
X     char *s;
X{
X  error = TRUE;
X}
X
X/* Is y a leap year? */
X#define leap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)
X
X/* Number of leap years from 1970 to y (not including y itself) */
X#define nleap(y) (((y) - 1969) / 4 - ((y) - 1901) / 100 + ((y) - 1601) / 400)
X
X/* This macro returns the "day" number of the sunday immediately
X   preceding or equal to the argument in the current year. */
X#define FIRST_SUNDAY 3
X#define dayofepoch(day) ((day) + (year - 1970) * 365 + nleap(year))
X#define sunday(day)  ((day) - (dayofepoch(day) + 7 - FIRST_SUNDAY) % 7)
X
X/* correction()
X   returns the daylight savings correction in seconds to ADD to GMT
X   to get correct local time.
X   Since we are converting local back to GMT, we SUBTRACT this later on
X   (local = gmt + correction(); gmt = local - correction()).
X
X   While we're at it, we also add the longitude correction for minutes
X   west of Greenwich.  To do this, we have all these fascinating tables
X   here . . .  */
X
X#if BSD
X
Xstruct dstinfo
X{
X  int year;			/* Info is for this year, or default if zero. */
X  int start;			/* DST begins sunday before this day. */
X  int end;			/* DST ends sunday before this day. */
X};
X
X/* USA. */
Xstatic struct dstinfo
Xusa_dst[] =
X{
X  1974, 5, 333,
X  1975, 58, 303,
X  0, 119, 303
X};
X
X/* Australia. */
Xstatic struct dstinfo
Xaus_dst[] =
X{
X  1970, 999, 0,
X  1971, 303, 0,
X  1972, 303, 58,
X  0, 303, 65
X};
X
X/* Western Europe. */
Xstatic struct dstinfo
Xweur_dst[] =
X{
X  1983, 89, 296,
X  0, 89, 303
X};
X
X/* Middle Europe (also used for Eastern Europe, for lack of better
X   information). */
Xstatic struct dstinfo
Xmeur_dst[] =
X{
X  1983, 89, 296,
X  0, 89, 272
X};
X
X/* Canada is same as US, except no early 70's insanity. */
Xstatic struct dstinfo
Xcan_dst[] =
X{
X  0, 119, 303
X};
X
Xstruct dst_rules
X{
X  int magic;			/* Gettimeofday magic number for rule type */
X  struct dstinfo *entry;	/* Pointer to struct dstinfo array. */
X  int correction;		/* Correction in minutes to GMT. */
X};
X
Xstatic struct dst_rules
Xdstrules[] =
X{
X  DST_USA, usa_dst, 60,
X  DST_AUST, aus_dst, -60,	/* Southern hemisphere */
X  DST_WET, weur_dst, 60,
X  DST_MET, meur_dst, 60,
X  DST_EET, meur_dst, 60,
X  DST_CAN, can_dst, 60,
X  -1, 0, 0
X};
X
Xstatic
Xcorrection(day,tz)
X     int day;				/* Day number in current year.  */
X     struct timezone *tz;
X{
X  int i, correc = 0;
X  struct dstinfo *dst;
X  
X  /* Did the user specify in the input string a timezone correction to use? */
X  if (zoneflag)
X    return delta * 60;
X
X  /* Since no correction was explicitly specified, we use local time zone and
X     DST, as returned by gettimeofday() earlier . . . */
X  if (tz->tz_dsttime)
X    for (i = 0; dstrules[i].magic != -1; i++)
X      if (dstrules[i].magic == tz->tz_dsttime)
X	{
X	  dst = dstrules[i].entry;
X	  while (dst->year != year && dst->year)
X	    dst++;
X	  if (sunday(dst->start) <= day && day <= sunday(dst->end)
X	      /* For some reason, DST starts/ends at 2 am sunday mornings. */
X	      && !(day == sunday(dst->start) && hour < 2)
X	      && !(day == sunday(dst->end) && hour >= 2))
X	    correc = dstrules[i].correction;
X	  break;
X	}
X  correc -= tz->tz_minuteswest;
X  return correc * 60;
X}
X
X#else /* !BSD */
X
Xstatic
Xcorrection()
X{
X#if FTIME
X  struct timeb tb;
X#else
X  extern long timezone;
X#endif
X  
X  /* Did the user specify in the input string a timezone correction to use? */
X  if (zoneflag)
X    return delta * 60;
X
X  /* Since no correction was explicitly specified, we use local time zone. */
X#if FTIME
X  ftime(&tb);
X  return tb.timezone * -60;
X#else
X  tzset();
X  return (int) -timezone;
X#endif
X}
X
X#endif
X
Xstatic short
Xmonthlens[] =
X{
X  31,				/* January */
X  28,				/* February */
X  31,				/* March */
X  30,				/* April */
X  31,				/* May */
X  30,				/* June */
X  31,				/* July */
X  31,				/* August */
X  30,				/* September */
X  31,				/* October */
X  30,				/* November */
X  31				/* December */
X};
X
Xtime_t
Xunctime(s)
X     char *s;
X{
X#if BSD
X  struct timeval tv;
X  struct timezone tz;
X#else
X  time_t now;
X#endif
X  struct tm *tm;
X  int dayofyear;
X
X#if BSD
X  (void) gettimeofday(&tv,&tz);
X  /* The cast is required to shut lint up.  Berkeley goes to all the effort
X     to define time_t, why don't they use it? */
X  tm = localtime(&(time_t) tv.tv_sec);
X#else
X  (void) time(&now);
X  tm = localtime(&now);
X#endif
X  year = tm->tm_year;
X  month = tm->tm_mon + 1;
X  day = tm->tm_mday;
X  hour = tm->tm_hour;
X  minute = tm->tm_min;
X  second = tm->tm_sec;
X  zoneflag = FALSE;
X  error = FALSE;
X
X  initlex(s);
X  (void) yyparse();
X
X  if (error)
X    return -1;
X
X  /* User forced the exact time in seconds GMT, no further work necessary. */
X  if (iflag)
X    return iresult;
X
X  /* Try to keep the year reasonable (i.e., within the domain of ctime()). */
X  if (year < 1970)
X    year += 1900;
X  if (year < 1970)
X    year += 100;
X
X  /* Check for preposterous months/days/times. */
X  if (month < 1 || month > 12 || day < 1 ||
X      day > monthlens[month - 1] && !(month == 2 && day == 29 && leap(year))
X      || hour > 23 || minute > 59 || second > 59)
X    return -1;
X
X  /* Mostly for convenience in sunday() macro, we use zero-origin days. */
X  dayofyear = day - 1;
X  if (month > 2 && leap(year))
X    ++dayofyear;
X  while (--month > 0)
X    dayofyear += monthlens[month - 1];
X
X  /* Wow! */
X  return 86400 * (dayofyear + 365 * (year - 1970) + nleap(year))
X    + 3600 * hour + 60 * minute + second -
X#if BSD
X    correction(dayofyear,&tz);
X#else
X    correction();
X#endif
X}
END_OF_FILE
if test 10015 -ne `wc -c <'unctime.y'`; then
    echo shar: \"'unctime.y'\" unpacked with wrong size!
fi
# end of 'unctime.y'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have the archive.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0