diff options
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/tar/Makefile | 2 | ||||
-rw-r--r-- | usr.bin/tar/bsdtar.c | 9 | ||||
-rw-r--r-- | usr.bin/tar/getdate.c | 1050 | ||||
-rw-r--r-- | usr.bin/tar/getdate.y | 815 | ||||
-rw-r--r-- | usr.bin/tar/test/Makefile | 2 | ||||
-rw-r--r-- | usr.bin/tar/test/test_getdate.c | 46 |
6 files changed, 1102 insertions, 822 deletions
diff --git a/usr.bin/tar/Makefile b/usr.bin/tar/Makefile index e721c6e..3032882 100644 --- a/usr.bin/tar/Makefile +++ b/usr.bin/tar/Makefile @@ -2,7 +2,7 @@ PROG= bsdtar BSDTAR_VERSION_STRING=2.5.903a -SRCS= bsdtar.c cmdline.c getdate.y matching.c read.c siginfo.c subst.c tree.c util.c write.c +SRCS= bsdtar.c cmdline.c getdate.c matching.c read.c siginfo.c subst.c tree.c util.c write.c WARNS?= 5 DPADD= ${LIBARCHIVE} ${LIBBZ2} ${LIBZ} LDADD= -larchive -lbz2 -lz diff --git a/usr.bin/tar/bsdtar.c b/usr.bin/tar/bsdtar.c index af762ea..7520555 100644 --- a/usr.bin/tar/bsdtar.c +++ b/usr.bin/tar/bsdtar.c @@ -82,7 +82,7 @@ __FBSDID("$FreeBSD$"); #endif /* External function to parse a date/time string (from getdate.y) */ -time_t get_date(const char *); +time_t get_date(time_t, const char *); static void long_help(struct bsdtar *); static void only_mode(struct bsdtar *, const char *opt, @@ -103,6 +103,7 @@ main(int argc, char **argv) char option_o; char possible_help_request; char buff[16]; + time_t now; /* * Use a pointer for consistency, but stack-allocated storage @@ -132,6 +133,8 @@ main(int argc, char **argv) bsdtar->progname = *argv; } + time(&now); + if (setlocale(LC_ALL, "") == NULL) bsdtar_warnc(bsdtar, 0, "Failed to set default locale"); #if defined(HAVE_NL_LANGINFO) && defined(HAVE_D_MD_ORDER) @@ -290,7 +293,7 @@ main(int argc, char **argv) * TODO: Add corresponding "older" options to reverse these. */ case OPTION_NEWER_CTIME: /* GNU tar */ - bsdtar->newer_ctime_sec = get_date(bsdtar->optarg); + bsdtar->newer_ctime_sec = get_date(now, bsdtar->optarg); break; case OPTION_NEWER_CTIME_THAN: { @@ -304,7 +307,7 @@ main(int argc, char **argv) } break; case OPTION_NEWER_MTIME: /* GNU tar */ - bsdtar->newer_mtime_sec = get_date(bsdtar->optarg); + bsdtar->newer_mtime_sec = get_date(now, bsdtar->optarg); break; case OPTION_NEWER_MTIME_THAN: { diff --git a/usr.bin/tar/getdate.c b/usr.bin/tar/getdate.c new file mode 100644 index 0000000..8df1e26 --- /dev/null +++ b/usr.bin/tar/getdate.c @@ -0,0 +1,1050 @@ +/* + * This code is in the public domain and has no copyright. + * + * This is a plain C recursive-descent translation of an old + * public-domain YACC grammar that has been used for parsing dates in + * very many open-source projects. + * + * Since the original authors were generous enough to donate their + * work to the public domain, I feel compelled to match their + * generosity. + * + * Tim Kientzle, February 2009. + */ + +/* + * Header comment from original getdate.y: + */ + +/* +** 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; +** +** This grammar has 10 shift/reduce conflicts. +** +** This code is in the public domain and has no copyright. +*/ + +#ifdef __FreeBSD__ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); +#endif + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +/* This file defines a single public function. */ +time_t get_date(time_t now, char *); + +/* Basic time units. */ +#define EPOCH 1970 +#define MINUTE (60L) +#define HOUR (60L * MINUTE) +#define DAY (24L * HOUR) + +/* Daylight-savings mode: on, off, or not yet known. */ +enum DSTMODE { DSTon, DSToff, DSTmaybe }; +/* Meridian: am or pm. */ +enum { tAM, tPM }; +/* Token types returned by nexttoken() */ +enum { tAGO = 260, tDAY, tDAYZONE, tAMPM, tMONTH, tMONTH_UNIT, tSEC_UNIT, + tUNUMBER, tZONE, tDST }; +struct token { int token; time_t value; }; + +/* + * Parser state. + */ +struct gdstate { + struct token *tokenp; /* Pointer to next token. */ + /* HaveXxxx counts how many of this kind of phrase we've seen; + * it's a fatal error to have more than one time, zone, day, + * or date phrase. */ + int HaveYear; + int HaveMonth; + int HaveDay; + int HaveWeekDay; /* Day of week */ + int HaveTime; /* Hour/minute/second */ + int HaveZone; /* timezone and/or DST info */ + int HaveRel; /* time offset; we can have more than one */ + /* Absolute time values. */ + time_t Timezone; /* Seconds offset from GMT */ + time_t Day; + time_t Hour; + time_t Minutes; + time_t Month; + time_t Seconds; + time_t Year; + /* DST selection */ + enum DSTMODE DSTmode; + /* Day of week accounting, e.g., "3rd Tuesday" */ + time_t DayOrdinal; /* "3" in "3rd Tuesday" */ + time_t DayNumber; /* "Tuesday" in "3rd Tuesday" */ + /* Relative time values: hour/day/week offsets are measured in + * seconds, month/year are counted in months. */ + time_t RelMonth; + time_t RelSeconds; +}; + +/* + * A series of functions that recognize certain common time phrases. + * Each function returns 1 if it managed to make sense of some of the + * tokens, zero otherwise. + */ + +/* + * hour:minute or hour:minute:second with optional AM, PM, or numeric + * timezone offset + */ +static int +timephrase(struct gdstate *gds) +{ + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == ':' + && gds->tokenp[2].token == tUNUMBER + && gds->tokenp[3].token == ':' + && gds->tokenp[4].token == tUNUMBER) { + /* "12:14:18" or "22:08:07" */ + ++gds->HaveTime; + gds->Hour = gds->tokenp[0].value; + gds->Minutes = gds->tokenp[2].value; + gds->Seconds = gds->tokenp[4].value; + gds->tokenp += 5; + } + else if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == ':' + && gds->tokenp[2].token == tUNUMBER) { + /* "12:14" or "22:08" */ + ++gds->HaveTime; + gds->Hour = gds->tokenp[0].value; + gds->Minutes = gds->tokenp[2].value; + gds->Seconds = 0; + gds->tokenp += 3; + } + else if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == tAMPM) { + /* "7" is a time if it's followed by "am" or "pm" */ + ++gds->HaveTime; + gds->Hour = gds->tokenp[0].value; + gds->Minutes = gds->Seconds = 0; + /* We'll handle the AM/PM below. */ + gds->tokenp += 1; + } else { + /* We can't handle this. */ + return 0; + } + + if (gds->tokenp[0].token == tAMPM) { + /* "7:12pm", "12:20:13am" */ + if (gds->Hour == 12) + gds->Hour = 0; + if (gds->tokenp[0].value == tPM) + gds->Hour += 12; + gds->tokenp += 1; + } + if (gds->tokenp[0].token == '+' + && gds->tokenp[1].token == tUNUMBER) { + /* "7:14+0700" */ + gds->HaveZone++; + gds->DSTmode = DSToff; + gds->Timezone = - ((gds->tokenp[1].value / 100) * HOUR + + (gds->tokenp[1].value % 100) * MINUTE); + gds->tokenp += 2; + } + if (gds->tokenp[0].token == '-' + && gds->tokenp[1].token == tUNUMBER) { + /* "19:14:12-0530" */ + gds->HaveZone++; + gds->DSTmode = DSToff; + gds->Timezone = + ((gds->tokenp[1].value / 100) * HOUR + + (gds->tokenp[1].value % 100) * MINUTE); + gds->tokenp += 2; + } + return 1; +} + +/* + * Timezone name, possibly including DST. + */ +static int +zonephrase(struct gdstate *gds) +{ + if (gds->tokenp[0].token == tZONE + && gds->tokenp[1].token == tDST) { + gds->HaveZone++; + gds->Timezone = gds->tokenp[0].value; + gds->DSTmode = DSTon; + gds->tokenp += 1; + return 1; + } + + if (gds->tokenp[0].token == tZONE) { + gds->HaveZone++; + gds->Timezone = gds->tokenp[0].value; + gds->DSTmode = DSToff; + gds->tokenp += 1; + return 1; + } + + if (gds->tokenp[0].token == tDAYZONE) { + gds->HaveZone++; + gds->Timezone = gds->tokenp[0].value; + gds->DSTmode = DSTon; + gds->tokenp += 1; + return 1; + } + return 0; +} + +/* + * Year/month/day in various combinations. + */ +static int +datephrase(struct gdstate *gds) +{ + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == '/' + && gds->tokenp[2].token == tUNUMBER + && gds->tokenp[3].token == '/' + && gds->tokenp[4].token == tUNUMBER) { + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; + if (gds->tokenp[0].value >= 13) { + /* First number is big: 2004/01/29, 99/02/17 */ + gds->Year = gds->tokenp[0].value; + gds->Month = gds->tokenp[2].value; + gds->Day = gds->tokenp[4].value; + } else if ((gds->tokenp[4].value >= 13) || (gds->tokenp[2].value >= 13)) { + /* Last number is big: 01/07/98 */ + /* Middle number is big: 01/29/04 */ + gds->Month = gds->tokenp[0].value; + gds->Day = gds->tokenp[2].value; + gds->Year = gds->tokenp[4].value; + } else { + /* No significant clues: 02/03/04 */ + gds->Month = gds->tokenp[0].value; + gds->Day = gds->tokenp[2].value; + gds->Year = gds->tokenp[4].value; + } + gds->tokenp += 5; + return 1; + } + + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == '/' + && gds->tokenp[2].token == tUNUMBER) { + /* "1/15" */ + gds->HaveMonth++; + gds->HaveDay++; + gds->Month = gds->tokenp[0].value; + gds->Day = gds->tokenp[2].value; + gds->tokenp += 3; + return 1; + } + + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == '-' + && gds->tokenp[2].token == tUNUMBER + && gds->tokenp[3].token == '-' + && gds->tokenp[4].token == tUNUMBER) { + /* ISO 8601 format. yyyy-mm-dd. */ + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; + gds->Year = gds->tokenp[0].value; + gds->Month = gds->tokenp[2].value; + gds->Day = gds->tokenp[4].value; + gds->tokenp += 5; + return 1; + } + + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == '-' + && gds->tokenp[2].token == tMONTH + && gds->tokenp[3].token == '-' + && gds->tokenp[4].token == tUNUMBER) { + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; + if (gds->tokenp[0].value > 31) { + /* e.g. 1992-Jun-17 */ + gds->Year = gds->tokenp[0].value; + gds->Month = gds->tokenp[2].value; + gds->Day = gds->tokenp[4].value; + } else { + /* e.g. 17-JUN-1992. */ + gds->Day = gds->tokenp[0].value; + gds->Month = gds->tokenp[2].value; + gds->Year = gds->tokenp[4].value; + } + gds->tokenp += 5; + return 1; + } + + if (gds->tokenp[0].token == tMONTH + && gds->tokenp[1].token == tUNUMBER + && gds->tokenp[2].token == ',' + && gds->tokenp[3].token == tUNUMBER) { + /* "June 17, 2001" */ + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; + gds->Month = gds->tokenp[0].value; + gds->Day = gds->tokenp[1].value; + gds->Year = gds->tokenp[3].value; + gds->tokenp += 4; + return 1; + } + + if (gds->tokenp[0].token == tMONTH + && gds->tokenp[1].token == tUNUMBER) { + /* "May 3" */ + gds->HaveMonth++; + gds->HaveDay++; + gds->Month = gds->tokenp[0].value; + gds->Day = gds->tokenp[1].value; + gds->tokenp += 2; + return 1; + } + + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == tMONTH + && gds->tokenp[2].token == tUNUMBER) { + /* "12 Sept 1997" */ + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; + gds->Day = gds->tokenp[0].value; + gds->Month = gds->tokenp[1].value; + gds->Year = gds->tokenp[2].value; + gds->tokenp += 3; + return 1; + } + + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == tMONTH) { + /* "12 Sept" */ + gds->HaveMonth++; + gds->HaveDay++; + gds->Day = gds->tokenp[0].value; + gds->Month = gds->tokenp[1].value; + gds->tokenp += 2; + return 1; + } + + return 0; +} + +/* + * Relative time phrase: "tomorrow", "yesterday", "+1 hour", etc. + */ +static int +relunitphrase(struct gdstate *gds) +{ + if (gds->tokenp[0].token == '-' + && gds->tokenp[1].token == tUNUMBER + && gds->tokenp[2].token == tSEC_UNIT) { + /* "-3 hours" */ + gds->HaveRel++; + gds->RelSeconds -= gds->tokenp[1].value * gds->tokenp[2].value; + gds->tokenp += 3; + return 1; + } + if (gds->tokenp[0].token == '+' + && gds->tokenp[1].token == tUNUMBER + && gds->tokenp[2].token == tSEC_UNIT) { + /* "+1 minute" */ + gds->HaveRel++; + gds->RelSeconds += gds->tokenp[1].value * gds->tokenp[2].value; + gds->tokenp += 3; + return 1; + } + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == tSEC_UNIT) { + /* "1 day" */ + gds->HaveRel++; + gds->RelSeconds += gds->tokenp[1].value * gds->tokenp[2].value; + gds->tokenp += 3; + return 1; + } + if (gds->tokenp[0].token == '-' + && gds->tokenp[1].token == tUNUMBER + && gds->tokenp[2].token == tMONTH_UNIT) { + /* "-3 months" */ + gds->HaveRel++; + gds->RelMonth -= gds->tokenp[1].value * gds->tokenp[2].value; + gds->tokenp += 3; + return 1; + } + if (gds->tokenp[0].token == '+' + && gds->tokenp[1].token == tUNUMBER + && gds->tokenp[2].token == tMONTH_UNIT) { + /* "+5 years" */ + gds->HaveRel++; + gds->RelMonth += gds->tokenp[1].value * gds->tokenp[2].value; + gds->tokenp += 3; + return 1; + } + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == tMONTH_UNIT) { + /* "2 years" */ + gds->HaveRel++; + gds->RelMonth += gds->tokenp[0].value * gds->tokenp[1].value; + gds->tokenp += 2; + return 1; + } + if (gds->tokenp[0].token == tSEC_UNIT) { + /* "now", "tomorrow" */ + gds->HaveRel++; + gds->RelSeconds += gds->tokenp[0].value; + ++gds->tokenp; + return 1; + } + if (gds->tokenp[0].token == tMONTH_UNIT) { + /* "month" */ + gds->HaveRel++; + gds->RelMonth += gds->tokenp[0].value; + gds->tokenp += 1; + return 1; + } + return 0; +} + +/* + * Day of the week specification. + */ +static int +dayphrase(struct gdstate *gds) +{ + if (gds->tokenp[0].token == tDAY) { + /* "tues", "wednesday," */ + gds->HaveWeekDay++; + gds->DayOrdinal = 1; + gds->DayNumber = gds->tokenp[0].value; + gds->tokenp += 1; + if (gds->tokenp[0].token == ',') + gds->tokenp += 1; + return 1; + } + if (gds->tokenp[0].token == tUNUMBER + && gds->tokenp[1].token == tDAY) { + /* "second tues" "3 wed" */ + gds->HaveWeekDay++; + gds->DayOrdinal = gds->tokenp[0].value; + gds->DayNumber = gds->tokenp[1].value; + gds->tokenp += 2; + return 1; + } + return 0; +} + +/* + * Try to match a phrase using one of the above functions. + * This layer also deals with a couple of generic issues. + */ +static int +phrase(struct gdstate *gds) +{ + if (timephrase(gds)) + return 1; + if (zonephrase(gds)) + return 1; + if (datephrase(gds)) + return 1; + if (dayphrase(gds)) + return 1; + if (relunitphrase(gds)) { + if (gds->tokenp[0].token == tAGO) { + gds->RelSeconds = -gds->RelSeconds; + gds->RelMonth = -gds->RelMonth; + gds->tokenp += 1; + } + return 1; + } + + /* Bare numbers sometimes have meaning. */ + if (gds->tokenp[0].token == tUNUMBER) { + if (gds->HaveTime && !gds->HaveYear && !gds->HaveRel) { + gds->HaveYear++; + gds->Year = gds->tokenp[0].value; + gds->tokenp += 1; + return 1; + } + + if(gds->tokenp[0].value > 10000) { + /* "20040301" */ + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; + gds->Day= (gds->tokenp[0].value)%100; + gds->Month= (gds->tokenp[0].value/100)%100; + gds->Year = gds->tokenp[0].value/10000; + gds->tokenp += 1; + return 1; + } + + if (gds->tokenp[0].value < 24) { + gds->HaveTime++; + gds->Hour = gds->tokenp[0].value; + gds->Minutes = 0; + gds->Seconds = 0; + gds->tokenp += 1; + return 1; + } + + if ((gds->tokenp[0].value / 100 < 24) + && (gds->tokenp[0].value % 100 < 60)) { + /* "513" is same as "5:13" */ + gds->Hour = gds->tokenp[0].value / 100; + gds->Minutes = gds->tokenp[0].value % 100; + gds->Seconds = 0; + gds->tokenp += 1; + return 1; + } + } + + return 0; +} + +/* + * A dictionary of time words. + */ +static struct LEXICON { + size_t abbrev; + const char *name; + int type; + time_t value; +} const TimeWords[] = { + /* am/pm */ + { 0, "am", tAMPM, tAM }, + { 0, "pm", tAMPM, tPM }, + + /* Month names. */ + { 3, "january", tMONTH, 1 }, + { 3, "february", tMONTH, 2 }, + { 3, "march", tMONTH, 3 }, + { 3, "april", tMONTH, 4 }, + { 3, "may", tMONTH, 5 }, + { 3, "june", tMONTH, 6 }, + { 3, "july", tMONTH, 7 }, + { 3, "august", tMONTH, 8 }, + { 3, "september", tMONTH, 9 }, + { 3, "october", tMONTH, 10 }, + { 3, "november", tMONTH, 11 }, + { 3, "december", tMONTH, 12 }, + + /* Days of the week. */ + { 2, "sunday", tDAY, 0 }, + { 3, "monday", tDAY, 1 }, + { 2, "tuesday", tDAY, 2 }, + { 3, "wednesday", tDAY, 3 }, + { 2, "thursday", tDAY, 4 }, + { 2, "friday", tDAY, 5 }, + { 2, "saturday", tDAY, 6 }, + + /* Timezones: Offsets are in seconds. */ + { 0, "gmt", tZONE, 0*HOUR }, /* Greenwich Mean */ + { 0, "ut", tZONE, 0*HOUR }, /* Universal (Coordinated) */ + { 0, "utc", tZONE, 0*HOUR }, + { 0, "wet", tZONE, 0*HOUR }, /* Western European */ + { 0, "bst", tDAYZONE, 0*HOUR }, /* British Summer */ + { 0, "wat", tZONE, 1*HOUR }, /* West Africa */ + { 0, "at", tZONE, 2*HOUR }, /* Azores */ + /* { 0, "bst", tZONE, 3*HOUR }, */ /* Brazil Standard: Conflict */ + /* { 0, "gst", tZONE, 3*HOUR }, */ /* Greenland Standard: Conflict*/ + { 0, "nft", tZONE, 3*HOUR+30*MINUTE }, /* Newfoundland */ + { 0, "nst", tZONE, 3*HOUR+30*MINUTE }, /* Newfoundland Standard */ + { 0, "ndt", tDAYZONE, 3*HOUR+30*MINUTE }, /* Newfoundland Daylight */ + { 0, "ast", tZONE, 4*HOUR }, /* Atlantic Standard */ + { 0, "adt", tDAYZONE, 4*HOUR }, /* Atlantic Daylight */ + { 0, "est", tZONE, 5*HOUR }, /* Eastern Standard */ + { 0, "edt", tDAYZONE, 5*HOUR }, /* Eastern Daylight */ + { 0, "cst", tZONE, 6*HOUR }, /* Central Standard */ + { 0, "cdt", tDAYZONE, 6*HOUR }, /* Central Daylight */ + { 0, "mst", tZONE, 7*HOUR }, /* Mountain Standard */ + { 0, "mdt", tDAYZONE, 7*HOUR }, /* Mountain Daylight */ + { 0, "pst", tZONE, 8*HOUR }, /* Pacific Standard */ + { 0, "pdt", tDAYZONE, 8*HOUR }, /* Pacific Daylight */ + { 0, "yst", tZONE, 9*HOUR }, /* Yukon Standard */ + { 0, "ydt", tDAYZONE, 9*HOUR }, /* Yukon Daylight */ + { 0, "hst", tZONE, 10*HOUR }, /* Hawaii Standard */ + { 0, "hdt", tDAYZONE, 10*HOUR }, /* Hawaii Daylight */ + { 0, "cat", tZONE, 10*HOUR }, /* Central Alaska */ + { 0, "ahst", tZONE, 10*HOUR }, /* Alaska-Hawaii Standard */ + { 0, "nt", tZONE, 11*HOUR }, /* Nome */ + { 0, "idlw", tZONE, 12*HOUR }, /* Intl Date Line West */ + { 0, "cet", tZONE, -1*HOUR }, /* Central European */ + { 0, "met", tZONE, -1*HOUR }, /* Middle European */ + { 0, "mewt", tZONE, -1*HOUR }, /* Middle European Winter */ + { 0, "mest", tDAYZONE, -1*HOUR }, /* Middle European Summer */ + { 0, "swt", tZONE, -1*HOUR }, /* Swedish Winter */ + { 0, "sst", tDAYZONE, -1*HOUR }, /* Swedish Summer */ + { 0, "fwt", tZONE, -1*HOUR }, /* French Winter */ + { 0, "fst", tDAYZONE, -1*HOUR }, /* French Summer */ + { 0, "eet", tZONE, -2*HOUR }, /* Eastern Eur, USSR Zone 1 */ + { 0, "bt", tZONE, -3*HOUR }, /* Baghdad, USSR Zone 2 */ + { 0, "it", tZONE, -3*HOUR-30*MINUTE },/* Iran */ + { 0, "zp4", tZONE, -4*HOUR }, /* USSR Zone 3 */ + { 0, "zp5", tZONE, -5*HOUR }, /* USSR Zone 4 */ + { 0, "ist", tZONE, -5*HOUR-30*MINUTE },/* Indian Standard */ + { 0, "zp6", tZONE, -6*HOUR }, /* USSR Zone 5 */ + /* { 0, "nst", tZONE, -6.5*HOUR }, */ /* North Sumatra: Conflict */ + /* { 0, "sst", tZONE, -7*HOUR }, */ /* So Sumatra, USSR 6: Conflict */ + { 0, "wast", tZONE, -7*HOUR }, /* West Australian Standard */ + { 0, "wadt", tDAYZONE, -7*HOUR }, /* West Australian Daylight */ + { 0, "jt", tZONE, -7*HOUR-30*MINUTE },/* Java (3pm in Cronusland!)*/ + { 0, "cct", tZONE, -8*HOUR }, /* China Coast, USSR Zone 7 */ + { 0, "jst", tZONE, -9*HOUR }, /* Japan Std, USSR Zone 8 */ + { 0, "cast", tZONE, -9*HOUR-30*MINUTE },/* Ctrl Australian Std */ + { 0, "cadt", tDAYZONE, -9*HOUR-30*MINUTE },/* Ctrl Australian Daylt */ + { 0, "east", tZONE, -10*HOUR }, /* Eastern Australian Std */ + { 0, "eadt", tDAYZONE, -10*HOUR }, /* Eastern Australian Daylt */ + { 0, "gst", tZONE, -10*HOUR }, /* Guam Std, USSR Zone 9 */ + { 0, "nzt", tZONE, -12*HOUR }, /* New Zealand */ + { 0, "nzst", tZONE, -12*HOUR }, /* New Zealand Standard */ + { 0, "nzdt", tDAYZONE, -12*HOUR }, /* New Zealand Daylight */ + { 0, "idle", tZONE, -12*HOUR }, /* Intl Date Line East */ + + { 0, "dst", tDST, 0 }, + + /* Time units. */ + { 4, "years", tMONTH_UNIT, 12 }, + { 5, "months", tMONTH_UNIT, 1 }, + { 9, "fortnights", tSEC_UNIT, 14 * DAY }, + { 4, "weeks", tSEC_UNIT, 7 * DAY }, + { 3, "days", tSEC_UNIT, DAY }, + { 4, "hours", tSEC_UNIT, HOUR }, + { 3, "minutes", tSEC_UNIT, MINUTE }, + { 3, "seconds", tSEC_UNIT, 1 }, + + /* Relative-time words. */ + { 0, "tomorrow", tSEC_UNIT, DAY }, + { 0, "yesterday", tSEC_UNIT, -DAY }, + { 0, "today", tSEC_UNIT, 0 }, + { 0, "now", tSEC_UNIT, 0 }, + { 0, "last", tUNUMBER, -1 }, + { 0, "this", tSEC_UNIT, 0 }, + { 0, "next", tUNUMBER, 2 }, + { 0, "first", tUNUMBER, 1 }, + { 0, "1st", tUNUMBER, 1 }, +/* { 0, "second", tUNUMBER, 2 }, */ + { 0, "2nd", tUNUMBER, 2 }, + { 0, "third", tUNUMBER, 3 }, + { 0, "3rd", tUNUMBER, 3 }, + { 0, "fourth", tUNUMBER, 4 }, + { 0, "4th", tUNUMBER, 4 }, + { 0, "fifth", tUNUMBER, 5 }, + { 0, "5th", tUNUMBER, 5 }, + { 0, "sixth", tUNUMBER, 6 }, + { 0, "seventh", tUNUMBER, 7 }, + { 0, "eighth", tUNUMBER, 8 }, + { 0, "ninth", tUNUMBER, 9 }, + { 0, "tenth", tUNUMBER, 10 }, + { 0, "eleventh", tUNUMBER, 11 }, + { 0, "twelfth", tUNUMBER, 12 }, + { 0, "ago", tAGO, 1 }, + + /* Military timezones. */ + { 0, "a", tZONE, 1*HOUR }, + { 0, "b", tZONE, 2*HOUR }, + { 0, "c", tZONE, 3*HOUR }, + { 0, "d", tZONE, 4*HOUR }, + { 0, "e", tZONE, 5*HOUR }, + { 0, "f", tZONE, 6*HOUR }, + { 0, "g", tZONE, 7*HOUR }, + { 0, "h", tZONE, 8*HOUR }, + { 0, "i", tZONE, 9*HOUR }, + { 0, "k", tZONE, 10*HOUR }, + { 0, "l", tZONE, 11*HOUR }, + { 0, "m", tZONE, 12*HOUR }, + { 0, "n", tZONE, -1*HOUR }, + { 0, "o", tZONE, -2*HOUR }, + { 0, "p", tZONE, -3*HOUR }, + { 0, "q", tZONE, -4*HOUR }, + { 0, "r", tZONE, -5*HOUR }, + { 0, "s", tZONE, -6*HOUR }, + { 0, "t", tZONE, -7*HOUR }, + { 0, "u", tZONE, -8*HOUR }, + { 0, "v", tZONE, -9*HOUR }, + { 0, "w", tZONE, -10*HOUR }, + { 0, "x", tZONE, -11*HOUR }, + { 0, "y", tZONE, -12*HOUR }, + { 0, "z", tZONE, 0*HOUR }, + + /* End of table. */ + { 0, NULL, 0, 0 } +}; + +/* + * Convert hour/minute/second to count of seconds. + */ +static time_t +ToSeconds(time_t Hours, time_t Minutes, time_t Seconds) +{ + if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) + return -1; + if (Hours < 0 || Hours > 23) + return -1; + return Hours * HOUR + Minutes * MINUTE + Seconds; +} + + +/* + * Year is either: + * = A number from 0 to 99, which means a year from 1970 to 2069, or + * = The actual year (>=100). + */ +static time_t +Convert(time_t Month, time_t Day, time_t Year, + time_t Hours, time_t Minutes, time_t Seconds, + time_t Timezone, enum DSTMODE DSTmode) +{ + static int DaysInMonth[12] = { + 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + time_t tod; + time_t Julian; + int i; + + if (Year < 69) + Year += 2000; + else if (Year < 100) + Year += 1900; + DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) + ? 29 : 28; + /* Checking for 2038 bogusly assumes that time_t is 32 bits. But + I'm too lazy to try to check for time_t overflow in another way. */ + if (Year < EPOCH || Year > 2038 + || Month < 1 || Month > 12 + /* Lint fluff: "conversion from long may lose accuracy" */ + || Day < 1 || Day > DaysInMonth[(int)--Month]) + return -1; + + Julian = Day - 1; + for (i = 0; i < Month; i++) + Julian += DaysInMonth[i]; + for (i = EPOCH; i < Year; i++) + Julian += 365 + (i % 4 == 0); + Julian *= DAY; + Julian += Timezone; + if ((tod = ToSeconds(Hours, Minutes, Seconds)) < 0) + return -1; + Julian += tod; + if (DSTmode == DSTon + || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst)) + Julian -= HOUR; + return Julian; +} + + +static time_t +DSTcorrect(time_t Start, time_t Future) +{ + time_t StartDay; + time_t FutureDay; + + StartDay = (localtime(&Start)->tm_hour + 1) % 24; + FutureDay = (localtime(&Future)->tm_hour + 1) % 24; + return (Future - Start) + (StartDay - FutureDay) * HOUR; +} + + +static time_t +RelativeDate(time_t Start, time_t zone, int dstmode, + time_t DayOrdinal, time_t DayNumber) +{ + struct tm *tm; + time_t t, now; + + t = Start - zone; + tm = gmtime(&t); + now = Start; + now += DAY * ((DayNumber - tm->tm_wday + 7) % 7); + now += 7 * DAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); + if (dstmode == DSTmaybe) + return DSTcorrect(Start, now); + return now - Start; +} + + +static time_t +RelativeMonth(time_t Start, time_t Timezone, time_t RelMonth) +{ + struct tm *tm; + time_t Month; + time_t Year; + + if (RelMonth == 0) + return 0; + tm = localtime(&Start); + Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth; + Year = Month / 12; + Month = Month % 12 + 1; + return DSTcorrect(Start, + Convert(Month, (time_t)tm->tm_mday, Year, + (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec, + Timezone, DSTmaybe)); +} + +/* + * Tokenizer. + */ +static int +nexttoken(char **in, time_t *value) +{ + char c; + char buff[64]; + + for ( ; ; ) { + while (isspace((unsigned char)**in)) + ++*in; + + /* Skip parenthesized comments. */ + if (**in == '(') { + int Count = 0; + do { + c = *(*in)++; + if (c == '\0') + return c; + if (c == '(') + Count++; + else if (c == ')') + Count--; + } while (Count > 0); + continue; + } + + /* Try the next token in the word table first. */ + /* This allows us to match "2nd", for example. */ + { + char *src = *in; + const struct LEXICON *tp; + unsigned i = 0; + + /* Force to lowercase and strip '.' characters. */ + while (*src != '\0' + && (isalnum((unsigned char)*src) || *src == '.') + && i < sizeof(buff)-1) { + if (*src != '.') { + if (isupper((unsigned char)*src)) + buff[i++] = tolower((unsigned char)*src); + else + buff[i++] = *src; + } + src++; + } + buff[i++] = '\0'; + + /* + * Find the first match. If the word can be + * abbreviated, make sure we match at least + * the minimum abbreviation. + */ + for (tp = TimeWords; tp->name; tp++) { + size_t abbrev = tp->abbrev; + if (abbrev == 0) + abbrev = strlen(tp->name); + if (strlen(buff) >= abbrev + && strncmp(tp->name, buff, strlen(buff)) + == 0) { + /* Skip over token. */ + *in = src; + /* Return the match. */ + *value = tp->value; + return tp->type; + } + } + } + + /* + * Not in the word table, maybe it's a number. Note: + * Because '-' and '+' have other special meanings, I + * don't deal with signed numbers here. + */ + if (isdigit((unsigned char)(c = **in))) { + for (*value = 0; isdigit((unsigned char)(c = *(*in)++)); ) + *value = 10 * *value + c - '0'; + (*in)--; + return (tUNUMBER); + } + + return *(*in)++; + } +} + +#define TM_YEAR_ORIGIN 1900 + +/* Yield A - B, measured in seconds. */ +static long +difftm (struct tm *a, struct tm *b) +{ + int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); + int by = b->tm_year + (TM_YEAR_ORIGIN - 1); + int days = ( + /* difference in day of year */ + a->tm_yday - b->tm_yday + /* + intervening leap days */ + + ((ay >> 2) - (by >> 2)) + - (ay/100 - by/100) + + ((ay/100 >> 2) - (by/100 >> 2)) + /* + difference in years * 365 */ + + (long)(ay-by) * 365 + ); + return (days * DAY + (a->tm_hour - b->tm_hour) * HOUR + + (a->tm_min - b->tm_min) * MINUTE + + (a->tm_sec - b->tm_sec)); +} + +/* + * + * The public function. + * + * TODO: tokens[] array should be dynamically sized. + */ +time_t +get_date(time_t now, char *p) +{ + struct token tokens[256]; + struct gdstate _gds; + struct token *lasttoken; + struct gdstate *gds; + struct tm local, *tm; + struct tm gmt, *gmt_ptr; + time_t Start; + time_t tod; + long tzone; + + /* Clear out the parsed token array. */ + memset(tokens, 0, sizeof(tokens)); + /* Initialize the parser state. */ + memset(&_gds, 0, sizeof(_gds)); + gds = &_gds; + + /* Look up the current time. */ + memset(&local, 0, sizeof(local)); + tm = localtime (&now); + if (tm == NULL) + return -1; + local = *tm; + + /* Look up UTC if we can and use that to determine the current + * timezone offset. */ + memset(&gmt, 0, sizeof(gmt)); + gmt_ptr = gmtime (&now); + if (gmt_ptr != NULL) { + /* Copy, in case localtime and gmtime use the same buffer. */ + gmt = *gmt_ptr; + } + if (gmt_ptr != NULL) + tzone = difftm (&gmt, &local); + else + /* This system doesn't understand timezones; fake it. */ + tzone = 0; + if(local.tm_isdst) + tzone += HOUR; + + /* Tokenize the input string. */ + lasttoken = tokens; + while ((lasttoken->token = nexttoken(&p, &lasttoken->value)) != 0) { + ++lasttoken; + if (lasttoken > tokens + 255) + return -1; + } + gds->tokenp = tokens; + + /* Match phrases until we run out of input tokens. */ + while (gds->tokenp < lasttoken) { + if (!phrase(gds)) + return -1; + } + + /* Use current local timezone if none was specified. */ + if (!gds->HaveZone) { + gds->Timezone = tzone; + gds->DSTmode = DSTmaybe; + } + + /* If a timezone was specified, use that for generating the default + * time components instead of the local timezone. */ + if (gds->HaveZone && gmt_ptr != NULL) { + now -= gds->Timezone; + gmt_ptr = gmtime (&now); + if (gmt_ptr != NULL) + local = *gmt_ptr; + now += gds->Timezone; + } + + if (!gds->HaveYear) + gds->Year = local.tm_year + 1900; + if (!gds->HaveMonth) + gds->Month = local.tm_mon + 1; + if (!gds->HaveDay) + gds->Day = local.tm_mday; + /* Note: No default for hour/min/sec; a specifier that just + * gives date always refers to 00:00 on that date. */ + + /* If we saw more than one time, timezone, weekday, year, month, + * or day, then give up. */ + if (gds->HaveTime > 1 || gds->HaveZone > 1 || gds->HaveWeekDay > 1 + || gds->HaveYear > 1 || gds->HaveMonth > 1 || gds->HaveDay > 1) + return -1; + + /* Compute an absolute time based on whatever absolute information + * we collected. */ + if (gds->HaveYear || gds->HaveMonth || gds->HaveDay + || gds->HaveTime || gds->HaveWeekDay) { + Start = Convert(gds->Month, gds->Day, gds->Year, + gds->Hour, gds->Minutes, gds->Seconds, + gds->Timezone, gds->DSTmode); + if (Start < 0) + return -1; + } else { + Start = now; + if (!gds->HaveRel) + Start -= local.tm_hour * HOUR + local.tm_min * MINUTE + + local.tm_sec; + } + + /* Add the relative offset. */ + Start += gds->RelSeconds; + Start += RelativeMonth(Start, gds->Timezone, gds->RelMonth); + + /* Adjust for day-of-week offsets. */ + if (gds->HaveWeekDay + && !(gds->HaveYear || gds->HaveMonth || gds->HaveDay)) { + tod = RelativeDate(Start, gds->Timezone, + gds->DSTmode, gds->DayOrdinal, gds->DayNumber); + Start += tod; + } + + /* -1 is an error indicator, so return 0 instead of -1 if + * that's the actual time. */ + return Start == -1 ? 0 : Start; +} + + +#if defined(TEST) + +/* ARGSUSED */ +int +main(int argc, char **argv) +{ + time_t d; + + while (*++argv != NULL) { + (void)printf("Input: %s\n", *argv); + d = get_date(*argv); + if (d == -1) + (void)printf("Bad format - couldn't convert.\n"); + else + (void)printf("Output: %s\n", ctime(&d)); + } + exit(0); + /* NOTREACHED */ +} +#endif /* defined(TEST) */ diff --git a/usr.bin/tar/getdate.y b/usr.bin/tar/getdate.y deleted file mode 100644 index 28b402e..0000000 --- a/usr.bin/tar/getdate.y +++ /dev/null @@ -1,815 +0,0 @@ -%{ -/* - * March 2005: Further modified and simplified by Tim Kientzle: - * Eliminate minutes-based calculations (just do everything in - * seconds), have lexer only recognize unsigned integers (handle '+' - * and '-' characters in grammar), combine tables into one table with - * explicit abbreviation notes, do am/pm adjustments in the grammar - * (eliminate some state variables and post-processing). Among other - * things, these changes eliminated two shift/reduce conflicts. (Went - * from 10 to 8.) - * All of Tim Kientzle's changes to this file are public domain. - */ - -/* -** 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; -** -** This grammar has 10 shift/reduce conflicts. -** -** This code is in the public domain and has no copyright. -*/ -/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */ -/* SUPPRESS 288 on yyerrlab *//* Label unused */ - -#ifdef __FreeBSD__ -#include <sys/cdefs.h> -__FBSDID("$FreeBSD$"); -#endif - -#include <ctype.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -#ifdef _MSC_VER -#define __STDC__ /* for a bug of bison 2.1 on Windows */ -#endif - -#define yyparse getdate_yyparse -#define yylex getdate_yylex -#define yyerror getdate_yyerror - -static int yyparse(void); -static int yylex(void); -static int yyerror(const char *); - -time_t get_date(char *); - -#define EPOCH 1970 -#define HOUR(x) ((time_t)(x) * 60) -#define SECSPERDAY (24L * 60L * 60L) - -/* -** Daylight-savings mode: on, off, or not yet known. -*/ -typedef enum _DSTMODE { - DSTon, DSToff, DSTmaybe -} DSTMODE; - -/* -** Meridian: am or pm. -*/ -enum { tAM, tPM }; - -/* -** Global variables. We could get rid of most of these by using a good -** union as the yacc stack. (This routine was originally written before -** yacc had the %union construct.) Maybe someday; right now we only use -** the %union very rarely. -*/ -static char *yyInput; - -static DSTMODE yyDSTmode; -static time_t yyDayOrdinal; -static time_t yyDayNumber; -static int yyHaveDate; -static int yyHaveDay; -static int yyHaveRel; -static int yyHaveTime; -static int yyHaveZone; -static time_t yyTimezone; -static time_t yyDay; -static time_t yyHour; -static time_t yyMinutes; -static time_t yyMonth; -static time_t yySeconds; -static time_t yyYear; -static time_t yyRelMonth; -static time_t yyRelSeconds; - -%} - -%union { - time_t Number; -} - -%token tAGO tDAY tDAYZONE tAMPM tMONTH tMONTH_UNIT tSEC_UNIT tUNUMBER -%token tZONE tDST - -%type <Number> tDAY tDAYZONE tMONTH tMONTH_UNIT -%type <Number> tSEC_UNIT tUNUMBER tZONE tAMPM - -%% - -spec : /* NULL */ - | spec item - ; - -item : time { yyHaveTime++; } - | zone { yyHaveZone++; } - | date { yyHaveDate++; } - | day { yyHaveDay++; } - | rel { yyHaveRel++; } - | number - ; - -time : tUNUMBER tAMPM { - /* "7am" */ - yyHour = $1; - if (yyHour == 12) - yyHour = 0; - yyMinutes = 0; - yySeconds = 0; - if ($2 == tPM) - yyHour += 12; - } - | bare_time { - /* "7:12:18" "19:17" */ - } - | bare_time tAMPM { - /* "7:12pm", "12:20:13am" */ - if (yyHour == 12) - yyHour = 0; - if ($2 == tPM) - yyHour += 12; - } - | bare_time '+' tUNUMBER { - /* "7:14+0700" */ - yyDSTmode = DSToff; - yyTimezone = - ($3 % 100 + ($3 / 100) * 60); - } - | bare_time '-' tUNUMBER { - /* "19:14:12-0530" */ - yyDSTmode = DSToff; - yyTimezone = + ($3 % 100 + ($3 / 100) * 60); - } - ; - -bare_time : tUNUMBER ':' tUNUMBER { - yyHour = $1; - yyMinutes = $3; - yySeconds = 0; - } - | tUNUMBER ':' tUNUMBER ':' tUNUMBER { - yyHour = $1; - yyMinutes = $3; - yySeconds = $5; - } - ; - -zone : tZONE { - yyTimezone = $1; - yyDSTmode = DSToff; - } - | tDAYZONE { - yyTimezone = $1; - yyDSTmode = DSTon; - } - | tZONE tDST { - yyTimezone = $1; - yyDSTmode = DSTon; - } - ; - -day : tDAY { - yyDayOrdinal = 1; - yyDayNumber = $1; - } - | tDAY ',' { - /* "tue," "wednesday," */ - yyDayOrdinal = 1; - yyDayNumber = $1; - } - | tUNUMBER tDAY { - /* "second tues" "3 wed" */ - yyDayOrdinal = $1; - yyDayNumber = $2; - } - ; - -date : tUNUMBER '/' tUNUMBER { - /* "1/15" */ - yyMonth = $1; - yyDay = $3; - } - | tUNUMBER '/' tUNUMBER '/' tUNUMBER { - if ($1 >= 13) { - /* First number is big: 2004/01/29, 99/02/17 */ - yyYear = $1; - yyMonth = $3; - yyDay = $5; - } else if (($5 >= 13) || ($3 >= 13)) { - /* Last number is big: 01/07/98 */ - /* Middle number is big: 01/29/04 */ - yyMonth = $1; - yyDay = $3; - yyYear = $5; - } else { - /* No significant clues: 02/03/04 */ - yyMonth = $1; - yyDay = $3; - yyYear = $5; - } - } - | tUNUMBER '-' tUNUMBER '-' tUNUMBER { - /* ISO 8601 format. yyyy-mm-dd. */ - yyYear = $1; - yyMonth = $3; - yyDay = $5; - } - | tUNUMBER '-' tMONTH '-' tUNUMBER { - if ($1 > 31) { - /* e.g. 1992-Jun-17 */ - yyYear = $1; - yyMonth = $3; - yyDay = $5; - } else { - /* e.g. 17-JUN-1992. */ - yyDay = $1; - yyMonth = $3; - yyYear = $5; - } - } - | tMONTH tUNUMBER { - /* "May 3" */ - yyMonth = $1; - yyDay = $2; - } - | tMONTH tUNUMBER ',' tUNUMBER { - /* "June 17, 2001" */ - yyMonth = $1; - yyDay = $2; - yyYear = $4; - } - | tUNUMBER tMONTH { - /* "12 Sept" */ - yyDay = $1; - yyMonth = $2; - } - | tUNUMBER tMONTH tUNUMBER { - /* "12 Sept 1997" */ - yyDay = $1; - yyMonth = $2; - yyYear = $3; - } - ; - -rel : relunit tAGO { - yyRelSeconds = -yyRelSeconds; - yyRelMonth = -yyRelMonth; - } - | relunit - ; - -relunit : '-' tUNUMBER tSEC_UNIT { - /* "-3 hours" */ - yyRelSeconds -= $2 * $3; - } - | '+' tUNUMBER tSEC_UNIT { - /* "+1 minute" */ - yyRelSeconds += $2 * $3; - } - | tUNUMBER tSEC_UNIT { - /* "1 day" */ - yyRelSeconds += $1 * $2; - } - | tSEC_UNIT { - /* "hour" */ - yyRelSeconds += $1; - } - | '-' tUNUMBER tMONTH_UNIT { - /* "-3 months" */ - yyRelMonth -= $2 * $3; - } - | '+' tUNUMBER tMONTH_UNIT { - /* "+5 years" */ - yyRelMonth += $2 * $3; - } - | tUNUMBER tMONTH_UNIT { - /* "2 years" */ - yyRelMonth += $1 * $2; - } - | tMONTH_UNIT { - /* "6 months" */ - yyRelMonth += $1; - } - ; - -number : tUNUMBER { - if (yyHaveTime && yyHaveDate && !yyHaveRel) - yyYear = $1; - else { - if($1>10000) { - /* "20040301" */ - yyHaveDate++; - yyDay= ($1)%100; - yyMonth= ($1/100)%100; - yyYear = $1/10000; - } - else { - /* "513" is same as "5:13" */ - yyHaveTime++; - if ($1 < 100) { - yyHour = $1; - yyMinutes = 0; - } - else { - yyHour = $1 / 100; - yyMinutes = $1 % 100; - } - yySeconds = 0; - } - } - } - ; - - -%% - -static struct TABLE { - size_t abbrev; - const char *name; - int type; - time_t value; -} const TimeWords[] = { - /* am/pm */ - { 0, "am", tAMPM, tAM }, - { 0, "pm", tAMPM, tPM }, - - /* Month names. */ - { 3, "january", tMONTH, 1 }, - { 3, "february", tMONTH, 2 }, - { 3, "march", tMONTH, 3 }, - { 3, "april", tMONTH, 4 }, - { 3, "may", tMONTH, 5 }, - { 3, "june", tMONTH, 6 }, - { 3, "july", tMONTH, 7 }, - { 3, "august", tMONTH, 8 }, - { 3, "september", tMONTH, 9 }, - { 3, "october", tMONTH, 10 }, - { 3, "november", tMONTH, 11 }, - { 3, "december", tMONTH, 12 }, - - /* Days of the week. */ - { 2, "sunday", tDAY, 0 }, - { 3, "monday", tDAY, 1 }, - { 2, "tuesday", tDAY, 2 }, - { 3, "wednesday", tDAY, 3 }, - { 2, "thursday", tDAY, 4 }, - { 2, "friday", tDAY, 5 }, - { 2, "saturday", tDAY, 6 }, - - /* Timezones: Offsets are in minutes. */ - { 0, "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ - { 0, "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ - { 0, "utc", tZONE, HOUR( 0) }, - { 0, "wet", tZONE, HOUR( 0) }, /* Western European */ - { 0, "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ - { 0, "wat", tZONE, HOUR( 1) }, /* West Africa */ - { 0, "at", tZONE, HOUR( 2) }, /* Azores */ - /* { 0, "bst", tZONE, HOUR( 3) }, */ /* Brazil Standard: Conflict */ - /* { 0, "gst", tZONE, HOUR( 3) }, */ /* Greenland Standard: Conflict*/ - { 0, "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */ - { 0, "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */ - { 0, "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */ - { 0, "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ - { 0, "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ - { 0, "est", tZONE, HOUR( 5) }, /* Eastern Standard */ - { 0, "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ - { 0, "cst", tZONE, HOUR( 6) }, /* Central Standard */ - { 0, "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ - { 0, "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ - { 0, "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ - { 0, "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ - { 0, "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ - { 0, "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ - { 0, "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ - { 0, "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ - { 0, "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ - { 0, "cat", tZONE, HOUR(10) }, /* Central Alaska */ - { 0, "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ - { 0, "nt", tZONE, HOUR(11) }, /* Nome */ - { 0, "idlw", tZONE, HOUR(12) }, /* Intl Date Line West */ - { 0, "cet", tZONE, -HOUR(1) }, /* Central European */ - { 0, "met", tZONE, -HOUR(1) }, /* Middle European */ - { 0, "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */ - { 0, "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */ - { 0, "swt", tZONE, -HOUR(1) }, /* Swedish Winter */ - { 0, "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */ - { 0, "fwt", tZONE, -HOUR(1) }, /* French Winter */ - { 0, "fst", tDAYZONE, -HOUR(1) }, /* French Summer */ - { 0, "eet", tZONE, -HOUR(2) }, /* Eastern Eur, USSR Zone 1 */ - { 0, "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */ - { 0, "it", tZONE, -HOUR(3)-30 },/* Iran */ - { 0, "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */ - { 0, "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */ - { 0, "ist", tZONE, -HOUR(5)-30 },/* Indian Standard */ - { 0, "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */ - /* { 0, "nst", tZONE, -HOUR(6.5) }, */ /* North Sumatra: Conflict */ - /* { 0, "sst", tZONE, -HOUR(7) }, */ /* So Sumatra, USSR 6: Conflict */ - { 0, "wast", tZONE, -HOUR(7) }, /* West Australian Standard */ - { 0, "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */ - { 0, "jt", tZONE, -HOUR(7)-30 },/* Java (3pm in Cronusland!)*/ - { 0, "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */ - { 0, "jst", tZONE, -HOUR(9) }, /* Japan Std, USSR Zone 8 */ - { 0, "cast", tZONE, -HOUR(9)-30 },/* Central Australian Std */ - { 0, "cadt", tDAYZONE, -HOUR(9)-30 },/* Central Australian Daylt */ - { 0, "east", tZONE, -HOUR(10) }, /* Eastern Australian Std */ - { 0, "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylt */ - { 0, "gst", tZONE, -HOUR(10) }, /* Guam Std, USSR Zone 9 */ - { 0, "nzt", tZONE, -HOUR(12) }, /* New Zealand */ - { 0, "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ - { 0, "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ - { 0, "idle", tZONE, -HOUR(12) }, /* Intl Date Line East */ - - { 0, "dst", tDST, 0 }, - - /* Time units. */ - { 4, "years", tMONTH_UNIT, 12 }, - { 5, "months", tMONTH_UNIT, 1 }, - { 9, "fortnights", tSEC_UNIT, 14 * 24 * 60 * 60 }, - { 4, "weeks", tSEC_UNIT, 7 * 24 * 60 * 60 }, - { 3, "days", tSEC_UNIT, 1 * 24 * 60 * 60 }, - { 4, "hours", tSEC_UNIT, 60 * 60 }, - { 3, "minutes", tSEC_UNIT, 60 }, - { 3, "seconds", tSEC_UNIT, 1 }, - - /* Relative-time words. */ - { 0, "tomorrow", tSEC_UNIT, 1 * 24 * 60 * 60 }, - { 0, "yesterday", tSEC_UNIT, -1 * 24 * 60 * 60 }, - { 0, "today", tSEC_UNIT, 0 }, - { 0, "now", tSEC_UNIT, 0 }, - { 0, "last", tUNUMBER, -1 }, - { 0, "this", tSEC_UNIT, 0 }, - { 0, "next", tUNUMBER, 2 }, - { 0, "first", tUNUMBER, 1 }, - { 0, "1st", tUNUMBER, 1 }, -/* { 0, "second", tUNUMBER, 2 }, */ - { 0, "2nd", tUNUMBER, 2 }, - { 0, "third", tUNUMBER, 3 }, - { 0, "3rd", tUNUMBER, 3 }, - { 0, "fourth", tUNUMBER, 4 }, - { 0, "4th", tUNUMBER, 4 }, - { 0, "fifth", tUNUMBER, 5 }, - { 0, "5th", tUNUMBER, 5 }, - { 0, "sixth", tUNUMBER, 6 }, - { 0, "seventh", tUNUMBER, 7 }, - { 0, "eighth", tUNUMBER, 8 }, - { 0, "ninth", tUNUMBER, 9 }, - { 0, "tenth", tUNUMBER, 10 }, - { 0, "eleventh", tUNUMBER, 11 }, - { 0, "twelfth", tUNUMBER, 12 }, - { 0, "ago", tAGO, 1 }, - - /* Military timezones. */ - { 0, "a", tZONE, HOUR( 1) }, - { 0, "b", tZONE, HOUR( 2) }, - { 0, "c", tZONE, HOUR( 3) }, - { 0, "d", tZONE, HOUR( 4) }, - { 0, "e", tZONE, HOUR( 5) }, - { 0, "f", tZONE, HOUR( 6) }, - { 0, "g", tZONE, HOUR( 7) }, - { 0, "h", tZONE, HOUR( 8) }, - { 0, "i", tZONE, HOUR( 9) }, - { 0, "k", tZONE, HOUR( 10) }, - { 0, "l", tZONE, HOUR( 11) }, - { 0, "m", tZONE, HOUR( 12) }, - { 0, "n", tZONE, HOUR(- 1) }, - { 0, "o", tZONE, HOUR(- 2) }, - { 0, "p", tZONE, HOUR(- 3) }, - { 0, "q", tZONE, HOUR(- 4) }, - { 0, "r", tZONE, HOUR(- 5) }, - { 0, "s", tZONE, HOUR(- 6) }, - { 0, "t", tZONE, HOUR(- 7) }, - { 0, "u", tZONE, HOUR(- 8) }, - { 0, "v", tZONE, HOUR(- 9) }, - { 0, "w", tZONE, HOUR(-10) }, - { 0, "x", tZONE, HOUR(-11) }, - { 0, "y", tZONE, HOUR(-12) }, - { 0, "z", tZONE, HOUR( 0) }, - - /* End of table. */ - { 0, NULL, 0, 0 } -}; - - - - -/* ARGSUSED */ -static int -yyerror(const char *s) -{ - (void)s; - return 0; -} - -static time_t -ToSeconds(time_t Hours, time_t Minutes, time_t Seconds) -{ - if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) - return -1; - if (Hours < 0 || Hours > 23) - return -1; - return (Hours * 60L + Minutes) * 60L + Seconds; -} - - -/* Year is either - * A number from 0 to 99, which means a year from 1970 to 2069, or - * The actual year (>=100). */ -static time_t -Convert(time_t Month, time_t Day, time_t Year, - time_t Hours, time_t Minutes, time_t Seconds, DSTMODE DSTmode) -{ - static int DaysInMonth[12] = { - 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 - }; - time_t tod; - time_t Julian; - int i; - - if (Year < 69) - Year += 2000; - else if (Year < 100) - Year += 1900; - DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) - ? 29 : 28; - /* Checking for 2038 bogusly assumes that time_t is 32 bits. But - I'm too lazy to try to check for time_t overflow in another way. */ - if (Year < EPOCH || Year > 2038 - || Month < 1 || Month > 12 - /* Lint fluff: "conversion from long may lose accuracy" */ - || Day < 1 || Day > DaysInMonth[(int)--Month]) - return -1; - - Julian = Day - 1; - for (i = 0; i < Month; i++) - Julian += DaysInMonth[i]; - for (i = EPOCH; i < Year; i++) - Julian += 365 + (i % 4 == 0); - Julian *= SECSPERDAY; - Julian += yyTimezone * 60L; - if ((tod = ToSeconds(Hours, Minutes, Seconds)) < 0) - return -1; - Julian += tod; - if (DSTmode == DSTon - || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst)) - Julian -= 60 * 60; - return Julian; -} - - -static time_t -DSTcorrect(time_t Start, time_t Future) -{ - time_t StartDay; - time_t FutureDay; - - StartDay = (localtime(&Start)->tm_hour + 1) % 24; - FutureDay = (localtime(&Future)->tm_hour + 1) % 24; - return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; -} - - -static time_t -RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber) -{ - struct tm *tm; - time_t now; - - now = Start; - tm = localtime(&now); - now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7); - now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); - return DSTcorrect(Start, now); -} - - -static time_t -RelativeMonth(time_t Start, time_t RelMonth) -{ - struct tm *tm; - time_t Month; - time_t Year; - - if (RelMonth == 0) - return 0; - tm = localtime(&Start); - Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth; - Year = Month / 12; - Month = Month % 12 + 1; - return DSTcorrect(Start, - Convert(Month, (time_t)tm->tm_mday, Year, - (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec, - DSTmaybe)); -} - -static int -yylex(void) -{ - char c; - char buff[64]; - - for ( ; ; ) { - while (isspace((unsigned char)*yyInput)) - yyInput++; - - /* Skip parenthesized comments. */ - if (*yyInput == '(') { - int Count = 0; - do { - c = *yyInput++; - if (c == '\0') - return c; - if (c == '(') - Count++; - else if (c == ')') - Count--; - } while (Count > 0); - continue; - } - - /* Try the next token in the word table first. */ - /* This allows us to match "2nd", for example. */ - { - char *src = yyInput; - const struct TABLE *tp; - unsigned i = 0; - - /* Force to lowercase and strip '.' characters. */ - while (*src != '\0' - && (isalnum((unsigned char)*src) || *src == '.') - && i < sizeof(buff)-1) { - if (*src != '.') { - if (isupper((unsigned char)*src)) - buff[i++] = tolower((unsigned char)*src); - else - buff[i++] = *src; - } - src++; - } - buff[i++] = '\0'; - - /* - * Find the first match. If the word can be - * abbreviated, make sure we match at least - * the minimum abbreviation. - */ - for (tp = TimeWords; tp->name; tp++) { - size_t abbrev = tp->abbrev; - if (abbrev == 0) - abbrev = strlen(tp->name); - if (strlen(buff) >= abbrev - && strncmp(tp->name, buff, strlen(buff)) - == 0) { - /* Skip over token. */ - yyInput = src; - /* Return the match. */ - yylval.Number = tp->value; - return tp->type; - } - } - } - - /* - * Not in the word table, maybe it's a number. Note: - * Because '-' and '+' have other special meanings, I - * don't deal with signed numbers here. - */ - if (isdigit((unsigned char)(c = *yyInput))) { - for (yylval.Number = 0; isdigit((unsigned char)(c = *yyInput++)); ) - yylval.Number = 10 * yylval.Number + c - '0'; - yyInput--; - return (tUNUMBER); - } - - return (*yyInput++); - } -} - -#define TM_YEAR_ORIGIN 1900 - -/* Yield A - B, measured in seconds. */ -static long -difftm (struct tm *a, struct tm *b) -{ - int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); - int by = b->tm_year + (TM_YEAR_ORIGIN - 1); - int days = ( - /* difference in day of year */ - a->tm_yday - b->tm_yday - /* + intervening leap days */ - + ((ay >> 2) - (by >> 2)) - - (ay/100 - by/100) - + ((ay/100 >> 2) - (by/100 >> 2)) - /* + difference in years * 365 */ - + (long)(ay-by) * 365 - ); - return (60*(60*(24*days + (a->tm_hour - b->tm_hour)) - + (a->tm_min - b->tm_min)) - + (a->tm_sec - b->tm_sec)); -} - -time_t -get_date(char *p) -{ - struct tm *tm; - struct tm gmt, *gmt_ptr; - time_t Start; - time_t tod; - time_t nowtime; - long tzone; - - memset(&gmt, 0, sizeof(gmt)); - yyInput = p; - - (void)time (&nowtime); - - gmt_ptr = gmtime (&nowtime); - if (gmt_ptr != NULL) { - /* Copy, in case localtime and gmtime use the same buffer. */ - gmt = *gmt_ptr; - } - - if (! (tm = localtime (&nowtime))) - return -1; - - if (gmt_ptr != NULL) - tzone = difftm (&gmt, tm) / 60; - else - /* This system doesn't understand timezones; fake it. */ - tzone = 0; - if(tm->tm_isdst) - tzone += 60; - - yyYear = tm->tm_year + 1900; - yyMonth = tm->tm_mon + 1; - yyDay = tm->tm_mday; - yyTimezone = tzone; - yyDSTmode = DSTmaybe; - yyHour = 0; - yyMinutes = 0; - yySeconds = 0; - yyRelSeconds = 0; - yyRelMonth = 0; - yyHaveDate = 0; - yyHaveDay = 0; - yyHaveRel = 0; - yyHaveTime = 0; - yyHaveZone = 0; - - if (yyparse() - || yyHaveTime > 1 || yyHaveZone > 1 - || yyHaveDate > 1 || yyHaveDay > 1) - return -1; - - if (yyHaveDate || yyHaveTime || yyHaveDay) { - Start = Convert(yyMonth, yyDay, yyYear, - yyHour, yyMinutes, yySeconds, yyDSTmode); - if (Start < 0) - return -1; - } else { - Start = nowtime; - if (!yyHaveRel) - Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; - } - - Start += yyRelSeconds; - Start += RelativeMonth(Start, yyRelMonth); - - if (yyHaveDay && !yyHaveDate) { - tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber); - Start += tod; - } - - /* Have to do *something* with a legitimate -1 so it's - * distinguishable from the error return value. (Alternately - * could set errno on error.) */ - return Start == -1 ? 0 : Start; -} - - -#if defined(TEST) - -/* ARGSUSED */ -int -main(int argc, char **argv) -{ - time_t d; - - while (*++argv != NULL) { - (void)printf("Input: %s\n", *argv); - d = get_date(*argv); - if (d == -1) - (void)printf("Bad format - couldn't convert.\n"); - else - (void)printf("Output: %s\n", ctime(&d)); - } - exit(0); - /* NOTREACHED */ -} -#endif /* defined(TEST) */ diff --git a/usr.bin/tar/test/Makefile b/usr.bin/tar/test/Makefile index 7337e24..2f6f056 100644 --- a/usr.bin/tar/test/Makefile +++ b/usr.bin/tar/test/Makefile @@ -6,7 +6,7 @@ TAR_SRCDIR=${.CURDIR}/.. # Some tar sources are pulled in for white-box tests TAR_SRCS= \ - ../getdate.y + ../getdate.c TESTS= \ test_0.c \ diff --git a/usr.bin/tar/test/test_getdate.c b/usr.bin/tar/test/test_getdate.c index 7ed447b..cd6d55a 100644 --- a/usr.bin/tar/test/test_getdate.c +++ b/usr.bin/tar/test/test_getdate.c @@ -25,14 +25,56 @@ #include "test.h" __FBSDID("$FreeBSD$"); +#include <time.h> + /* * Verify that the getdate() function works. */ -time_t get_date(const char *); +time_t get_date(time_t, const char *); DEFINE_TEST(test_getdate) { - assertEqualInt(0, get_date("Jan 1, 1970 UTC")); + time_t now = time(NULL); + + assertEqualInt(get_date(now, "Jan 1, 1970 UTC"), 0); + assertEqualInt(get_date(now, "7:12:18-0530 4 May 1983"), 420900138); + assertEqualInt(get_date(now, "2004/01/29 513 mest"), 1075345980); + assertEqualInt(get_date(now, "99/02/17 7pm utc"), 919278000); + assertEqualInt(get_date(now, "02/17/99 7:11am est"), 919253460); + /* It's important that we handle ctime() format. */ + assertEqualInt(get_date(now, "Sun Feb 22 17:38:26 PST 2009"), + 1235353106); + /* Basic relative offsets. */ + /* If we use the actual current time as the reference, then + * these tests break around DST changes, so it's actually + * important to use a specific reference time here. */ + assertEqualInt(get_date(0, "tomorrow"), 24 * 60 * 60); + assertEqualInt(get_date(0, "yesterday"), - 24 * 60 * 60); + assertEqualInt(get_date(0, "now + 1 hour"), 60 * 60); + assertEqualInt(get_date(0, "now + 1 hour + 1 minute"), 60 * 60 + 60); + /* Repeat the above for a different start time. */ + now = 1231113600; /* Jan 5, 2009 00:00 UTC */ + assertEqualInt(get_date(0, "Jan 5, 2009 00:00 UTC"), now); + assertEqualInt(get_date(now, "tomorrow"), now + 24 * 60 * 60); + assertEqualInt(get_date(now, "yesterday"), now - 24 * 60 * 60); + assertEqualInt(get_date(now, "now + 1 hour"), now + 60 * 60); + assertEqualInt(get_date(now, "now + 1 hour + 1 minute"), + now + 60 * 60 + 60); + assertEqualInt(get_date(now, "tomorrow 5:16am UTC"), + now + 24 * 60 * 60 + 5 * 60 * 60 + 16 * 60); + assertEqualInt(get_date(now, "UTC 5:16am tomorrow"), + now + 24 * 60 * 60 + 5 * 60 * 60 + 16 * 60); + + /* Jan 5, 2009 was a Monday. */ + assertEqualInt(get_date(now, "monday UTC"), now); + assertEqualInt(get_date(now, "sunday UTC"), now + 6 * 24 * 60 * 60); + assertEqualInt(get_date(now, "tuesday UTC"), now + 24 * 60 * 60); + /* "next tuesday" is one week after "tuesday" */ + assertEqualInt(get_date(now, "UTC next tuesday"), + now + 8 * 24 * 60 * 60); + /* "last tuesday" is one week before "tuesday" */ + assertEqualInt(get_date(now, "last tuesday UTC"), + now - 6 * 24 * 60 * 60); /* TODO: Lots more tests here. */ } |