| /* |
| * 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 } |
| }; |
| |
| /* |
| * 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 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] |
| || Hours < 0 || Hours > 23 |
| || Minutes < 0 || Minutes > 59 |
| || Seconds < 0 || Seconds > 59) |
| 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; |
| Julian += Hours * HOUR + Minutes * MINUTE + Seconds; |
| 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) */ |