From: Tim Kientzle Date: Tue, 3 Mar 2009 03:55:54 +0000 (-0500) Subject: Fix "tomorrow UTC" and similar phrases by not setting the default X-Git-Tag: v2.7.0~208 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3540b45a7c98ff4cc43252edf749205ea8d5ab1d;p=thirdparty%2Flibarchive.git Fix "tomorrow UTC" and similar phrases by not setting the default time elements until after we've parsed the string. Then we can use the parsed timezone (if any) to set up the default time elements. In particular, this makes this code a lot easier to test, since otherwise local DST keeps intruding. SVN-Revision: 720 --- diff --git a/tar/getdate.c b/tar/getdate.c index 5854f4817..8df1e26fb 100644 --- a/tar/getdate.c +++ b/tar/getdate.c @@ -64,8 +64,10 @@ struct gdstate { /* 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 HaveDate; /* year/month/day information */ - int HaveDay; /* Day of week */ + 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 */ @@ -147,6 +149,7 @@ timephrase(struct gdstate *gds) 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); @@ -155,6 +158,7 @@ timephrase(struct gdstate *gds) 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); @@ -207,7 +211,9 @@ datephrase(struct gdstate *gds) && gds->tokenp[2].token == tUNUMBER && gds->tokenp[3].token == '/' && gds->tokenp[4].token == tUNUMBER) { - gds->HaveDate++; + 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; @@ -233,7 +239,8 @@ datephrase(struct gdstate *gds) && gds->tokenp[1].token == '/' && gds->tokenp[2].token == tUNUMBER) { /* "1/15" */ - gds->HaveDate++; + gds->HaveMonth++; + gds->HaveDay++; gds->Month = gds->tokenp[0].value; gds->Day = gds->tokenp[2].value; gds->tokenp += 3; @@ -246,7 +253,9 @@ datephrase(struct gdstate *gds) && gds->tokenp[3].token == '-' && gds->tokenp[4].token == tUNUMBER) { /* ISO 8601 format. yyyy-mm-dd. */ - gds->HaveDate++; + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; gds->Year = gds->tokenp[0].value; gds->Month = gds->tokenp[2].value; gds->Day = gds->tokenp[4].value; @@ -259,7 +268,9 @@ datephrase(struct gdstate *gds) && gds->tokenp[2].token == tMONTH && gds->tokenp[3].token == '-' && gds->tokenp[4].token == tUNUMBER) { - gds->HaveDate++; + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; if (gds->tokenp[0].value > 31) { /* e.g. 1992-Jun-17 */ gds->Year = gds->tokenp[0].value; @@ -280,7 +291,9 @@ datephrase(struct gdstate *gds) && gds->tokenp[2].token == ',' && gds->tokenp[3].token == tUNUMBER) { /* "June 17, 2001" */ - gds->HaveDate++; + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; gds->Month = gds->tokenp[0].value; gds->Day = gds->tokenp[1].value; gds->Year = gds->tokenp[3].value; @@ -291,7 +304,8 @@ datephrase(struct gdstate *gds) if (gds->tokenp[0].token == tMONTH && gds->tokenp[1].token == tUNUMBER) { /* "May 3" */ - gds->HaveDate++; + gds->HaveMonth++; + gds->HaveDay++; gds->Month = gds->tokenp[0].value; gds->Day = gds->tokenp[1].value; gds->tokenp += 2; @@ -302,7 +316,9 @@ datephrase(struct gdstate *gds) && gds->tokenp[1].token == tMONTH && gds->tokenp[2].token == tUNUMBER) { /* "12 Sept 1997" */ - gds->HaveDate++; + gds->HaveYear++; + gds->HaveMonth++; + gds->HaveDay++; gds->Day = gds->tokenp[0].value; gds->Month = gds->tokenp[1].value; gds->Year = gds->tokenp[2].value; @@ -313,7 +329,8 @@ datephrase(struct gdstate *gds) if (gds->tokenp[0].token == tUNUMBER && gds->tokenp[1].token == tMONTH) { /* "12 Sept" */ - gds->HaveDate++; + gds->HaveMonth++; + gds->HaveDay++; gds->Day = gds->tokenp[0].value; gds->Month = gds->tokenp[1].value; gds->tokenp += 2; @@ -406,7 +423,7 @@ dayphrase(struct gdstate *gds) { if (gds->tokenp[0].token == tDAY) { /* "tues", "wednesday," */ - gds->HaveDay++; + gds->HaveWeekDay++; gds->DayOrdinal = 1; gds->DayNumber = gds->tokenp[0].value; gds->tokenp += 1; @@ -417,7 +434,7 @@ dayphrase(struct gdstate *gds) if (gds->tokenp[0].token == tUNUMBER && gds->tokenp[1].token == tDAY) { /* "second tues" "3 wed" */ - gds->HaveDay++; + gds->HaveWeekDay++; gds->DayOrdinal = gds->tokenp[0].value; gds->DayNumber = gds->tokenp[1].value; gds->tokenp += 2; @@ -452,7 +469,8 @@ phrase(struct gdstate *gds) /* Bare numbers sometimes have meaning. */ if (gds->tokenp[0].token == tUNUMBER) { - if (gds->HaveTime && gds->HaveDate && !gds->HaveRel) { + if (gds->HaveTime && !gds->HaveYear && !gds->HaveRel) { + gds->HaveYear++; gds->Year = gds->tokenp[0].value; gds->tokenp += 1; return 1; @@ -460,7 +478,9 @@ phrase(struct gdstate *gds) if(gds->tokenp[0].value > 10000) { /* "20040301" */ - gds->HaveDate++; + 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; @@ -735,16 +755,20 @@ DSTcorrect(time_t Start, time_t Future) static time_t -RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber) +RelativeDate(time_t Start, time_t zone, int dstmode, + time_t DayOrdinal, time_t DayNumber) { struct tm *tm; - time_t now; + time_t t, now; + t = Start - zone; + tm = gmtime(&t); now = Start; - tm = localtime(&now); now += DAY * ((DayNumber - tm->tm_wday + 7) % 7); now += 7 * DAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); - return DSTcorrect(Start, now); + if (dstmode == DSTmaybe) + return DSTcorrect(Start, now); + return now - Start; } @@ -924,13 +948,6 @@ get_date(time_t now, char *p) if(local.tm_isdst) tzone += HOUR; - /* Initialize the year/month/day and timezone fields. */ - gds->Year = local.tm_year + 1900; - gds->Month = local.tm_mon + 1; - gds->Day = local.tm_mday; - gds->Timezone = tzone; - gds->DSTmode = DSTmaybe; - /* Tokenize the input string. */ lasttoken = tokens; while ((lasttoken->token = nexttoken(&p, &lasttoken->value)) != 0) { @@ -946,15 +963,41 @@ get_date(time_t now, char *p) return -1; } - /* If we saw more than one time, timezone, date, or day, then - * give up. */ - if (gds->HaveTime > 1 || gds->HaveZone > 1 - || gds->HaveDate > 1 || gds->HaveDay > 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->HaveDate || gds->HaveTime || gds->HaveDay) { + 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); @@ -972,8 +1015,10 @@ get_date(time_t now, char *p) Start += RelativeMonth(Start, gds->Timezone, gds->RelMonth); /* Adjust for day-of-week offsets. */ - if (gds->HaveDay && !gds->HaveDate) { - tod = RelativeDate(Start, gds->DayOrdinal, gds->DayNumber); + if (gds->HaveWeekDay + && !(gds->HaveYear || gds->HaveMonth || gds->HaveDay)) { + tod = RelativeDate(Start, gds->Timezone, + gds->DSTmode, gds->DayOrdinal, gds->DayNumber); Start += tod; } diff --git a/tar/test/test_getdate.c b/tar/test/test_getdate.c index 7b1c260ac..eac571049 100644 --- a/tar/test/test_getdate.c +++ b/tar/test/test_getdate.c @@ -36,9 +36,6 @@ time_t get_date(time_t, const char *); DEFINE_TEST(test_getdate) { time_t now = time(NULL); - struct tm tm = *localtime(&now); - time_t dayStart = now - - ((tm.tm_hour * 60L + tm.tm_min) * 60L + tm.tm_sec); assertEqualInt(get_date(now, "Jan 1, 1970 UTC"), 0); assertEqualInt(get_date(now, "7:12:18-0530 4 May 1983"), 420900138); @@ -49,23 +46,35 @@ DEFINE_TEST(test_getdate) 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); - /* "tuesday" is the start of the first tuesday today or later */ - assertEqualInt(get_date(now, "tuesday"), - dayStart + ((2 - tm.tm_wday + 7) % 7) * 24 * 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, "next tuesday"), - dayStart + (((2 - tm.tm_wday + 7) % 7) + 7) * 24 * 60 * 60); + 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"), - dayStart + (((2 - tm.tm_wday + 7) % 7) - 7) * 24 * 60 * 60); - assertEqualInt(get_date(now, "tomorrow 5:16am"), - dayStart + 24 * 60 * 60 + 5 * 60 * 60 + 16 * 60); - assertEqualInt(get_date(now, "5:16am tomorrow"), - dayStart + 24 * 60 * 60 + 5 * 60 * 60 + 16 * 60); + assertEqualInt(get_date(now, "last tuesday UTC"), + now - 6 * 24 * 60 * 60); /* TODO: Lots more tests here. */ }