/* 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 */
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);
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].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;
&& 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;
&& 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;
&& 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;
&& 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;
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;
&& 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;
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;
{
if (gds->tokenp[0].token == tDAY) {
/* "tues", "wednesday," */
- gds->HaveDay++;
+ gds->HaveWeekDay++;
gds->DayOrdinal = 1;
gds->DayNumber = gds->tokenp[0].value;
gds->tokenp += 1;
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;
/* 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;
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;
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;
}
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) {
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);
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;
}
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);
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. */
}