diff options
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | src/.gitignore | 2 | ||||
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/binlogcat.c | 17 | ||||
-rw-r--r-- | src/binlogsel.c | 474 | ||||
-rw-r--r-- | src/parse-datetime.h | 3 | ||||
-rw-r--r-- | src/parse-datetime.y | 1598 | ||||
-rw-r--r-- | src/xalloc.c | 53 | ||||
-rw-r--r-- | src/xalloc.h | 3 |
9 files changed, 2145 insertions, 16 deletions
diff --git a/configure.ac b/configure.ac index ea7c061..2bb8f38 100644 --- a/configure.ac +++ b/configure.ac @@ -32,7 +32,7 @@ if test "x$ac_cv_prog_cc_c99" = xno; then AC_MSG_ERROR([could not find a C99 compatible compiler]) fi AC_PROG_CPP - +AC_PROG_YACC AC_PROG_INSTALL AC_PROG_LIBTOOL AC_PROG_MAKE_SET diff --git a/src/.gitignore b/src/.gitignore index a75fe6d..8f5a6ab 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,5 @@ binlogcat +binlogsel +parse-datetime.c vcc_if.c vcc_if.h diff --git a/src/Makefile.am b/src/Makefile.am index 4e31e89..565444a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,13 +17,16 @@ AM_CPPFLAGS = -I$(VARNISHSRC)/include -I$(VARNISHSRC) noinst_LIBRARIES = libbinlog.a -libbinlog_a_SOURCES = pack.c err.c +libbinlog_a_SOURCES = pack.c err.c parse-datetime.y xalloc.c libbinlog_a_CFLAGS = $(AM_CFLAGS) -bin_PROGRAMS = binlogcat +bin_PROGRAMS = binlogcat binlogsel binlogcat_SOURCES = binlogcat.c binlogcat_LDADD = ./libbinlog.a +binlogsel_SOURCES = binlogsel.c +binlogsel_LDADD = ./libbinlog.a + vmoddir = $(VMODDIR) vmod_LTLIBRARIES = libvmod_binlog.la @@ -36,7 +39,7 @@ libvmod_binlog_la_SOURCES = \ vmod-binlog.h\ vcc_if.c vcc_if.h -noinst_HEADERS = pack.h err.h +noinst_HEADERS = pack.h err.h parse-datetime.h xalloc.h BUILT_SOURCES = vcc_if.c vcc_if.h diff --git a/src/binlogcat.c b/src/binlogcat.c index 505bbc2..656d8f2 100644 --- a/src/binlogcat.c +++ b/src/binlogcat.c @@ -27,6 +27,7 @@ #include "vmod-binlog.h" #include "pack.h" #include "err.h" +#include "xalloc.h" char *timefmt = "%c"; int number_option; @@ -75,11 +76,7 @@ catlog(const char *fname) } size = header.hdrsize - sizeof(header); - dataspec = malloc(size); - if (!dataspec) { - error("not enough memory"); - abort(); - } + dataspec = xmalloc(size); if (fread(dataspec, size, 1, fp) != 1) { error("error reading header of %s: %s", @@ -94,7 +91,7 @@ catlog(const char *fname) inst = packcomp(dataspec, &p); if (!inst) { if (errno == EINVAL) { - error("%s: bad dataspec near %s", dataspec, p); + error("%s: %s: bad dataspec near %s", fname, dataspec, p); exit(1); } @@ -103,11 +100,7 @@ catlog(const char *fname) } free(dataspec); - rec = malloc(header.recsize); - if (!rec) { - error("not enough memory"); - exit(1); - } + rec = xmalloc(header.recsize); env = packenv_create(header.recsize - offsetof(struct binlog_record,data)); env->fp = stdout; @@ -147,7 +140,7 @@ catlog(const char *fname) void help() { - printf("usage: %s [-dhnv] [t FORMAT] [FILE...]\n", progname); + printf("usage: %s [-dhnv] [-t FORMAT] [FILE...]\n", progname); } int diff --git a/src/binlogsel.c b/src/binlogsel.c new file mode 100644 index 0000000..62299e5 --- /dev/null +++ b/src/binlogsel.c @@ -0,0 +1,474 @@ +/* This file is part of vmod-binlog + Copyright (C) 2013 Sergey Poznyakoff + + Vmod-binlog is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + Vmod-binlog is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with vmod-binlog. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <config.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <time.h> +#include <string.h> +#include <glob.h> +#include "vmod-binlog.h" +#include "pack.h" +#include "err.h" +#include "xalloc.h" +#include "parse-datetime.h" + +char *timefmt = "%c"; +int number_option; +int verbose_option; +int timediff_option; +char *pattern; + +#define FROM_TIME 0x01 +#define TO_TIME 0x02 +int timemask; +time_t from_time, to_time; + +void +help() +{ + printf("usage: %s [-dhnv] [-t FORMAT] [-F FROMTIME] [-T TOTIME] [-p PATTERN] [-D DIR] [FILE...]\n", progname); +} + +/* Convert strftime-like pattern into globbing pattern */ +void +convpattern(const char *dir) +{ + char *p, *q; + char *newpat; + size_t size = strlen(pattern) + 1; + + if (dir) + size += strlen(dir) + 1; + + newpat = xmalloc(size); + + p = newpat; + if (dir) { + strcpy(p, dir); + p += strlen(dir); + *p++ = '/'; + } + + for (q = pattern; *q; ) { + if (*q == '%') { + *p++ = '*'; + q += 2; + } else + *p++ = *q++; + } + *p = 0; + pattern = newpat; +} + +#define getrec(base, recsize, n) \ + ((struct binlog_record*)((char *)(base) + (n) * (recsize))) + +int +searchts(void *base, size_t recsize, size_t from, size_t to, time_t ts, + size_t *ret) +{ + struct binlog_record *rec; + size_t middle; + + rec = getrec(base, recsize, from); + + if (ts < rec->ts) { + *ret = from; + return -1; + } + + if (to == from) { + *ret = from; + return 0; + } + + rec = getrec(base, recsize, to); + if (ts > rec->ts) { + *ret = from; + return 1; + } + + middle = (to + from) / 2; + + rec = getrec(base, recsize, middle); + + if (ts == rec->ts) { + *ret = middle; + return 0; + } + + if (ts > rec->ts) + return searchts(base, recsize, middle + 1, to, ts, ret); + + return searchts(base, recsize, from, middle, ts, ret); +} + +void +selmem(const char *name, void *base) +{ + struct binlog_file_header *hdr; + struct binlog_record *rec; + struct packenv *env; + struct packinst *inst; + char *p; + size_t i, start; + time_t start_ts; + char timebuf[128]; + char *dataspec; + + hdr = base; + + if (memcmp(hdr->magic, BINLOG_MAGIC_STR, BINLOG_MAGIC_LEN)) { + error("%s is not a binlog file", name); + return; + } + + if (hdr->version != BINLOG_VERSION) { + error("%s: unknown version", name); + return; + } + + dataspec = (char*)(hdr + 1); + + if (verbose_option) + printf("# %s; format=%s; recsize=%lu; recnum=%lu\n", + name, dataspec, hdr->recsize, hdr->recnum); + + inst = packcomp(dataspec, &p); + if (!inst) { + if (errno == EINVAL) { + error("%s: %s: bad dataspec near %s", name, + dataspec, p); + return; + } + + error("%s", strerror(errno)); + return; + } + + env = packenv_create(hdr->recsize - + offsetof(struct binlog_record,data)); + env->fp = stdout; + + base = (char*)base + hdr->hdrsize; + + if (timemask & FROM_TIME) { + switch (searchts(base, hdr->recsize, 0, hdr->recnum - 1, + from_time, &start)) { + case 0: + break; + case -1: + for (; start < hdr->recnum; start++) { + if (getrec(base, hdr->recsize, start)->ts > from_time) + break; + } + break; + case 1: + for (; start + 1 >= 0; start--) { + if (from_time < getrec(base, hdr->recsize, start)->ts) + break; + } + ++start; + } + } else + start = 0; + + for (i = 0; start < hdr->recnum; i++, start++) { + rec = getrec(base, hdr->recsize, start); + + if ((timemask & TO_TIME) && rec->ts > to_time) + break; + + if (timediff_option) { + if (i == 0) + start_ts = rec->ts; + rec->ts -= start_ts; + } + strftime(timebuf, sizeof timebuf, timefmt, + localtime(&rec->ts)); + if (number_option) + printf("%lu ", (unsigned long) start); + printf("%s ", timebuf); + memcpy(env->buf_base, rec->data, env->buf_size); + env->buf_pos = 0; + + packout(inst, env); + fputc('\n', stdout); + } +} + +static int +fchecktime(FILE *fp, const char *fname, time_t *ts) +{ + struct binlog_file_header header; + struct binlog_record rec; + time_t start, end; + + if (fread(&header, sizeof(header), 1, fp) != 1) { + error("error reading header of %s: %s", + fname, strerror(errno)); + return -1; + } + + if (memcmp(header.magic, BINLOG_MAGIC_STR, BINLOG_MAGIC_LEN)) { + error("%s is not a binlog file", fname); + return -1; + } + + if (header.version != BINLOG_VERSION) { + error("%s: unknown version", fname); + return -1; + } + + if (fseek(fp, header.hdrsize, SEEK_SET)) { + error("%s: seek error: %s", fname, strerror(errno)); + return -1; + } + + if (fread(&rec, sizeof(rec), 1, fp) != 1) { + error("%s: %s", fname, strerror(errno)); + return -1; + } + start = rec.ts; + + if (fseek(fp, header.hdrsize + + header.recsize * (header.recnum - 1), + SEEK_SET)) { + error("%s: seek error: %s", fname, strerror(errno)); + return -1; + } + if (fread(&rec, sizeof(rec), 1, fp) != 1) { + error("%s: %s", fname, strerror(errno)); + return -1; + } + end = rec.ts; + + if ((timemask & TO_TIME) && header.recnum > 1) { + if (to_time < start) + return 1; + + if (timemask & FROM_TIME) { + if (from_time > end) + return 1; + } + } else if ((timemask & FROM_TIME) && from_time > end) + return 1; + + *ts = start; + return 0; +} + +static int +checktime(const char *name, time_t *ts) +{ + int rc; + FILE *fp = fopen(name, "r"); + + if (!fp) { + error("can't open %s: %s", name, strerror(errno)); + return -1; + } + rc = fchecktime(fp, name, ts); + fclose(fp); + return rc; +} + +void +selfile(char *name) +{ + int fd; + struct stat st; + void *base; + + fd = open(name, O_RDONLY); + if (fd == -1) { + error("can't open %s: %s", name, strerror(errno)); + exit(1); + } + + if (fstat(fd, &st)) { + error("can't stat %s: %s", name, strerror(errno)); + exit(1); + } + + base = mmap((caddr_t)0, st.st_size, + PROT_READ, MAP_SHARED, + fd, 0); + if (base == MAP_FAILED) { + error("mmap(%s): %s", name, strerror(errno)); + exit(1); + } + + selmem(name, base); + munmap(base, st.st_size); + close(fd); +} + +void +selfilelist(char **argv) +{ + for (;*argv;++argv) + selfile(*argv); +} + +int +globerrfunc (const char *epath, int eerrno) +{ + error("%s: %s", strerror(eerrno)); + return 0; +} + +struct logfile { + char *name; + time_t start; +}; + +static int +tsort(const void *a, const void *b) +{ + struct logfile const *la = a; + struct logfile const *lb = b; + if (la->start > lb->start) + return 1; + if (la->start < lb->start) + return -1; + return 0; +} + +void +selpattern(void) +{ + size_t i, j; + glob_t gl; + struct logfile *logfiles; + + switch (glob(pattern, GLOB_ERR|GLOB_NOSORT, globerrfunc, &gl)) { + case 0: + break; + case GLOB_NOSPACE: + error("out of memory"); + exit(1); + + case GLOB_ABORTED: + error("read error"); + exit(1); + + case GLOB_NOMATCH: + error("no files matched pattern"); + exit(1); + } + + logfiles = xcalloc(gl.gl_pathc, sizeof(*logfiles)); + + for (i = j = 0; i < gl.gl_pathc; i++) { + time_t t; + if (checktime(gl.gl_pathv[i], &t) == 0) { + logfiles[j].name = gl.gl_pathv[i]; + logfiles[j].start = t; + ++j; + } + } + + /* sort the returned array */ + qsort(logfiles, j, sizeof(*logfiles), tsort); + + for (i = 0; i < j; i++) + selfile(logfiles[i].name); + + free(logfiles); + globfree(&gl); +} + +int +main(int argc, char **argv) +{ + int c; + struct timespec ts; + char *directory; + + setprogname(argv[0]); + while ((c = getopt(argc, argv, "D:dF:hp:T:t:nv")) != EOF) + switch (c) { + case 'D': + directory = optarg; + break; + case 'd': + timediff_option = 1; + timefmt = "%s"; + break; + case 'F': + if (!parse_datetime(&ts, optarg, NULL)) { + error("invalid timespec: %s", optarg); + exit(1); + } + from_time = ts.tv_sec; + timemask |= FROM_TIME; + break; + case 'h': + help(); + return 0; + case 'p': + pattern = optarg; + break; + case 'T': + if (!parse_datetime(&ts, optarg, NULL)) { + error("invalid timespec: %s", optarg); + exit(1); + } + to_time = ts.tv_sec; + timemask |= TO_TIME; + break; + case 't': + timefmt = optarg; + break; + case 'n': + number_option = 1; + break; + case 'v': + verbose_option = 1; + break; + default: + exit(1); + } + + argc -= optind; + argv += optind; + + if (argc) { + if (pattern) { + error("either files or pattern (-p) must be given, " + "but not both"); + exit(1); + } + selfilelist(argv); + } else { + if (!pattern) + pattern = BINLOG_PATTERN; + convpattern(directory); + selpattern(); + } + exit(0); +} + diff --git a/src/parse-datetime.h b/src/parse-datetime.h new file mode 100644 index 0000000..933a54a --- /dev/null +++ b/src/parse-datetime.h @@ -0,0 +1,3 @@ +#include <time.h> +int parse_datetime (struct timespec *result, char const *p, + struct timespec const *now); diff --git a/src/parse-datetime.y b/src/parse-datetime.y new file mode 100644 index 0000000..2dd5e1c --- /dev/null +++ b/src/parse-datetime.y @@ -0,0 +1,1598 @@ +%{ +/* Parse a string into an internal time stamp. + + Copyright (C) 1999-2000, 2002-2013 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* Originally written by Steven M. Bellovin <smb@research.att.com> while + at the University of North Carolina at Chapel Hill. Later tweaked by + a couple of people on Usenet. Completely overhauled by Rich $alz + <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990. + + Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do + the right thing about local DST. Also modified by Paul Eggert + <eggert@cs.ucla.edu> in February 2004 to support + nanosecond-resolution time stamps, and in October 2004 to support + TZ strings in dates. */ + +/* FIXME: Check for arithmetic overflow in all cases, not just + some of them. */ + +#include <config.h> +#include <time.h> +#include <sys/time.h> + +/* There's no need to extend the stack, so there's no need to involve + alloca. */ +#define YYSTACK_USE_ALLOCA 0 + +/* Tell Bison how much stack space is needed. 20 should be plenty for + this grammar, which is not right recursive. Beware setting it too + high, since that might cause problems on machines whose + implementations have lame stack-overflow checking. */ +#define YYMAXDEPTH 20 +#define YYINITDEPTH YYMAXDEPTH + +/* Since the code of parse-datetime.y is not included in the Emacs executable + itself, there is no need to #define static in this file. Even if + the code were included in the Emacs executable, it probably + wouldn't do any harm to #undef it here; this will only cause + problems if we try to write to a static variable, which I don't + think this code needs to do. */ +#ifdef emacs +# undef static +#endif + +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "err.h" +#include "xalloc.h" + +/* ISDIGIT differs from isdigit, as follows: + - Its arg may be any int or unsigned int; it need not be an unsigned char + or EOF. + - It's typically faster. + POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to + isdigit unless it's important to use the locale's definition + of "digit" even when the host does not conform to POSIX. */ +#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9) + +/* Shift A right by B bits portably, by dividing A by 2**B and + truncating towards minus infinity. A and B should be free of side + effects, and B should be in the range 0 <= B <= INT_BITS - 2, where + INT_BITS is the number of useful bits in an int. GNU code can + assume that INT_BITS is at least 32. + + ISO C99 says that A >> B is implementation-defined if A < 0. Some + implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift + right in the usual way when A < 0, so SHR falls back on division if + ordinary A >> B doesn't seem to be the usual signed shift. */ +#define SHR(a, b) \ + (-1 >> 1 == -1 \ + ? (a) >> (b) \ + : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0)) + +#define EPOCH_YEAR 1970 +#define TM_YEAR_BASE 1900 + +#define HOUR(x) ((x) * 60) + +/* Convert a possibly-signed character to an unsigned character. This is + a bit safer than casting to unsigned char, since it catches some type + errors that the cast doesn't. */ +static unsigned char to_uchar (char ch) { return ch; } + +#define long_time_t time_t + +/* An integer value, and the number of digits in its textual + representation. */ +typedef struct +{ + int negative; + long int value; + size_t digits; +} textint; + +/* An entry in the lexical lookup table. */ +typedef struct +{ + char const *name; + int type; + int value; +} table; + +/* Meridian: am, pm, or 24-hour style. */ +enum { MERam, MERpm, MER24 }; + +enum { BILLION = 1000000000, LOG10_BILLION = 9 }; + +/* Relative times. */ +typedef struct +{ + /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */ + long int year; + long int month; + long int day; + long int hour; + long int minutes; + long_time_t seconds; + long int ns; +} relative_time; + +#if HAVE_COMPOUND_LITERALS +# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 }) +#else +static relative_time const RELATIVE_TIME_0; +#endif + +/* Information passed to and from the parser. */ +typedef struct +{ + /* The input string remaining to be parsed. */ + const char *input; + + /* N, if this is the Nth Tuesday. */ + long int day_ordinal; + + /* Day of week; Sunday is 0. */ + int day_number; + + /* tm_isdst flag for the local zone. */ + int local_isdst; + + /* Time zone, in minutes east of UTC. */ + long int time_zone; + + /* Style used for time. */ + int meridian; + + /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */ + textint year; + long int month; + long int day; + long int hour; + long int minutes; + struct timespec seconds; /* includes nanoseconds */ + + /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */ + relative_time rel; + + /* Presence or counts of nonterminals of various flavors parsed so far. */ + int timespec_seen; + int rels_seen; + size_t dates_seen; + size_t days_seen; + size_t local_zones_seen; + size_t dsts_seen; + size_t times_seen; + size_t zones_seen; + + /* Table of local time zone abbreviations, terminated by a null entry. */ + table local_time_zone_table[3]; +} parser_control; + +union YYSTYPE; +static int yylex (union YYSTYPE *, parser_control *); +static int yyerror (parser_control const *, char const *); +static long int time_zone_hhmm (parser_control *, textint, long int); + +/* Extract into *PC any date and time info from a string of digits + of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY, + YYYY, ...). */ +static void +digits_to_date_time (parser_control *pc, textint text_int) +{ + if (pc->dates_seen && ! pc->year.digits + && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits)) + pc->year = text_int; + else + { + if (4 < text_int.digits) + { + pc->dates_seen++; + pc->day = text_int.value % 100; + pc->month = (text_int.value / 100) % 100; + pc->year.value = text_int.value / 10000; + pc->year.digits = text_int.digits - 4; + } + else + { + pc->times_seen++; + if (text_int.digits <= 2) + { + pc->hour = text_int.value; + pc->minutes = 0; + } + else + { + pc->hour = text_int.value / 100; + pc->minutes = text_int.value % 100; + } + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = MER24; + } + } +} + +/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */ +static void +apply_relative_time (parser_control *pc, relative_time rel, int factor) +{ + pc->rel.ns += factor * rel.ns; + pc->rel.seconds += factor * rel.seconds; + pc->rel.minutes += factor * rel.minutes; + pc->rel.hour += factor * rel.hour; + pc->rel.day += factor * rel.day; + pc->rel.month += factor * rel.month; + pc->rel.year += factor * rel.year; + pc->rels_seen = 1; +} + +/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */ +static void +set_hhmmss (parser_control *pc, long int hour, long int minutes, + time_t sec, long int nsec) +{ + pc->hour = hour; + pc->minutes = minutes; + pc->seconds.tv_sec = sec; + pc->seconds.tv_nsec = nsec; +} + +%} + +/* We want a reentrant parser, even if the TZ manipulation and the calls to + localtime and gmtime are not reentrant. */ +%pure-parser +%parse-param { parser_control *pc } +%lex-param { parser_control *pc } + +/* This grammar has 31 shift/reduce conflicts. */ +%expect 31 + +%union +{ + long int intval; + textint textintval; + struct timespec timespec; + relative_time rel; +} + +%token <intval> tAGO +%token tDST + +%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT +%token <intval> tDAY_UNIT tDAY_SHIFT + +%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN +%token <intval> tMONTH tORDINAL tZONE + +%token <textintval> tSNUMBER tUNUMBER +%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER + +%type <intval> o_colon_minutes +%type <timespec> seconds signed_seconds unsigned_seconds + +%type <rel> relunit relunit_snumber dayshift + +%% + +spec: + timespec + | items + ; + +timespec: + '@' seconds + { + pc->seconds = $2; + pc->timespec_seen = 1; + } + ; + +items: + /* empty */ + | items item + ; + +item: + datetime + { pc->times_seen++; pc->dates_seen++; } + | time + { pc->times_seen++; } + | local_zone + { pc->local_zones_seen++; } + | zone + { pc->zones_seen++; } + | date + { pc->dates_seen++; } + | day + { pc->days_seen++; } + | rel + | number + | hybrid + ; + +datetime: + iso_8601_datetime + ; + +iso_8601_datetime: + iso_8601_date 'T' iso_8601_time + ; + +time: + tUNUMBER tMERIDIAN + { + set_hhmmss (pc, $1.value, 0, 0, 0); + pc->meridian = $2; + } + | tUNUMBER ':' tUNUMBER tMERIDIAN + { + set_hhmmss (pc, $1.value, $3.value, 0, 0); + pc->meridian = $4; + } + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN + { + set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec); + pc->meridian = $6; + } + | iso_8601_time + ; + +iso_8601_time: + tUNUMBER zone_offset + { + set_hhmmss (pc, $1.value, 0, 0, 0); + pc->meridian = MER24; + } + | tUNUMBER ':' tUNUMBER o_zone_offset + { + set_hhmmss (pc, $1.value, $3.value, 0, 0); + pc->meridian = MER24; + } + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset + { + set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec); + pc->meridian = MER24; + } + ; + +o_zone_offset: + /* empty */ + | zone_offset + ; + +zone_offset: + tSNUMBER o_colon_minutes + { + pc->zones_seen++; + pc->time_zone = time_zone_hhmm (pc, $1, $2); + } + ; + +local_zone: + tLOCAL_ZONE + { + pc->local_isdst = $1; + pc->dsts_seen += (0 < $1); + } + | tLOCAL_ZONE tDST + { + pc->local_isdst = 1; + pc->dsts_seen += (0 < $1) + 1; + } + ; + +/* Note 'T' is a special case, as it is used as the separator in ISO + 8601 date and time of day representation. */ +zone: + tZONE + { pc->time_zone = $1; } + | 'T' + { pc->time_zone = HOUR(7); } + | tZONE relunit_snumber + { pc->time_zone = $1; + apply_relative_time (pc, $2, 1); } + | 'T' relunit_snumber + { pc->time_zone = HOUR(7); + apply_relative_time (pc, $2, 1); } + | tZONE tSNUMBER o_colon_minutes + { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); } + | tDAYZONE + { pc->time_zone = $1 + 60; } + | tZONE tDST + { pc->time_zone = $1 + 60; } + ; + +day: + tDAY + { + pc->day_ordinal = 0; + pc->day_number = $1; + } + | tDAY ',' + { + pc->day_ordinal = 0; + pc->day_number = $1; + } + | tORDINAL tDAY + { + pc->day_ordinal = $1; + pc->day_number = $2; + } + | tUNUMBER tDAY + { + pc->day_ordinal = $1.value; + pc->day_number = $2; + } + ; + +date: + tUNUMBER '/' tUNUMBER + { + pc->month = $1.value; + pc->day = $3.value; + } + | tUNUMBER '/' tUNUMBER '/' tUNUMBER + { + /* Interpret as YYYY/MM/DD if the first value has 4 or more digits, + otherwise as MM/DD/YY. + The goal in recognizing YYYY/MM/DD is solely to support legacy + machine-generated dates like those in an RCS log listing. If + you want portability, use the ISO 8601 format. */ + if (4 <= $1.digits) + { + pc->year = $1; + pc->month = $3.value; + pc->day = $5.value; + } + else + { + pc->month = $1.value; + pc->day = $3.value; + pc->year = $5; + } + } + | tUNUMBER tMONTH tSNUMBER + { + /* e.g. 17-JUN-1992. */ + pc->day = $1.value; + pc->month = $2; + pc->year.value = -$3.value; + pc->year.digits = $3.digits; + } + | tMONTH tSNUMBER tSNUMBER + { + /* e.g. JUN-17-1992. */ + pc->month = $1; + pc->day = -$2.value; + pc->year.value = -$3.value; + pc->year.digits = $3.digits; + } + | tMONTH tUNUMBER + { + pc->month = $1; + pc->day = $2.value; + } + | tMONTH tUNUMBER ',' tUNUMBER + { + pc->month = $1; + pc->day = $2.value; + pc->year = $4; + } + | tUNUMBER tMONTH + { + pc->day = $1.value; + pc->month = $2; + } + | tUNUMBER tMONTH tUNUMBER + { + pc->day = $1.value; + pc->month = $2; + pc->year = $3; + } + | iso_8601_date + ; + +iso_8601_date: + tUNUMBER tSNUMBER tSNUMBER + { + /* ISO 8601 format. YYYY-MM-DD. */ + pc->year = $1; + pc->month = -$2.value; + pc->day = -$3.value; + } + ; + +rel: + relunit tAGO + { apply_relative_time (pc, $1, $2); } + | relunit + { apply_relative_time (pc, $1, 1); } + | dayshift + { apply_relative_time (pc, $1, 1); } + ; + +relunit: + tORDINAL tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1; } + | tUNUMBER tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1.value; } + | tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = 1; } + | tORDINAL tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = $1; } + | tUNUMBER tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = $1.value; } + | tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = 1; } + | tORDINAL tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; } + | tUNUMBER tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; } + | tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1; } + | tORDINAL tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = $1; } + | tUNUMBER tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = $1.value; } + | tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = 1; } + | tORDINAL tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = $1; } + | tUNUMBER tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; } + | tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = 1; } + | tORDINAL tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1; } + | tUNUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; } + | tSDECIMAL_NUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; } + | tUDECIMAL_NUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; } + | tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = 1; } + | relunit_snumber + ; + +relunit_snumber: + tSNUMBER tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1.value; } + | tSNUMBER tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = $1.value; } + | tSNUMBER tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; } + | tSNUMBER tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = $1.value; } + | tSNUMBER tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; } + | tSNUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; } + ; + +dayshift: + tDAY_SHIFT + { $$ = RELATIVE_TIME_0; $$.day = $1; } + ; + +seconds: signed_seconds | unsigned_seconds; + +signed_seconds: + tSDECIMAL_NUMBER + | tSNUMBER + { $$.tv_sec = $1.value; $$.tv_nsec = 0; } + ; + +unsigned_seconds: + tUDECIMAL_NUMBER + | tUNUMBER + { $$.tv_sec = $1.value; $$.tv_nsec = 0; } + ; + +number: + tUNUMBER + { digits_to_date_time (pc, $1); } + ; + +hybrid: + tUNUMBER relunit_snumber + { |