diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c new file mode 100644 index e737e72..ba923d0 *** a/src/backend/utils/adt/date.c --- b/src/backend/utils/adt/date.c *************** date_in(PG_FUNCTION_ARGS) *** 123,133 **** char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; char workbuf[MAXDATELEN + 1]; dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) ! dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp); if (dterr != 0) DateTimeParseError(dterr, str, "date"); --- 123,135 ---- char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; char workbuf[MAXDATELEN + 1]; + Interval interval, + *offset = &interval; dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) ! dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp, offset); if (dterr != 0) DateTimeParseError(dterr, str, "date"); *************** date_in(PG_FUNCTION_ARGS) *** 166,171 **** --- 168,193 ---- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("date out of range: \"%s\"", str))); + if (offset->month != 0 || offset->day != 0 || offset->time != 0) + { + Timestamp timestamp; + + if (tm2timestamp(tm, fsec, NULL, ×tamp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: \"%s\"", str))); + + timestamp = DatumGetTimestamp( + DirectFunctionCall2(timestamp_pl_interval, + TimestampGetDatum(timestamp), + PointerGetDatum(offset))); + + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; PG_RETURN_DATEADT(date); diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c new file mode 100644 index 3d320cc..a4808a0 *** a/src/backend/utils/adt/datetime.c --- b/src/backend/utils/adt/datetime.c *************** static const datetkn datetktbl[] = { *** 148,153 **** --- 148,154 ---- {"mar", MONTH, 3}, {"march", MONTH, 3}, {"may", MONTH, 5}, + {MINUS, RESERV, DTK_MINUS}, /* "minus" an interval */ {"mm", UNITS, DTK_MINUTE}, /* "minute" for ISO input */ {"mon", DOW, 1}, {"monday", DOW, 1}, *************** static const datetkn datetktbl[] = { *** 157,162 **** --- 158,164 ---- {"oct", MONTH, 10}, {"october", MONTH, 10}, {"on", IGNORE_DTF, 0}, /* "on" (throwaway) */ + {PLUS, RESERV, DTK_PLUS}, /* "plus" an interval */ {"pm", AMPM, PM}, {"s", UNITS, DTK_SECOND}, /* "seconds" for ISO input */ {"sat", DOW, 6}, *************** ParseDateTime(const char *timestr, char *** 784,790 **** */ int DecodeDateTime(char **field, int *ftype, int nf, ! int *dtype, struct pg_tm * tm, fsec_t *fsec, int *tzp) { int fmask = 0, tmask, --- 786,793 ---- */ int DecodeDateTime(char **field, int *ftype, int nf, ! int *dtype, struct pg_tm * tm, fsec_t *fsec, int *tzp, ! Interval *offset) { int fmask = 0, tmask, *************** DecodeDateTime(char **field, int *ftype, *** 799,804 **** --- 802,809 ---- bool is2digits = FALSE; bool bc = FALSE; pg_tz *namedTz = NULL; + int ifield = 0; /* interval field index after "plus" or "minus" + * to add or subtract an interval */ /* * We'll insist on at least all of the date fields, but initialize the *************** DecodeDateTime(char **field, int *ftype, *** 814,819 **** --- 819,832 ---- if (tzp != NULL) *tzp = 0; + /* Zero any interval passed in too */ + if (offset != NULL) + { + offset->month = 0; + offset->day = 0; + offset->time = 0; + } + for (i = 0; i < nf; i++) { switch (ftype[i]) *************** DecodeDateTime(char **field, int *ftype, *** 1235,1240 **** --- 1248,1265 ---- *tzp = 0; break; + case DTK_PLUS: + case DTK_MINUS: + /* + * All the remaining fields should form an + * interval to be added or subtracted from the + * datetime so far. We exit the main loop now + * and process this at the end. + */ + ifield = i + 1; + i = nf; + break; + default: *dtype = val; } *************** DecodeDateTime(char **field, int *ftype, *** 1381,1386 **** --- 1406,1421 ---- /* do additional checking for full date specs... */ if (*dtype == DTK_DATE) { + /* + * If the datetime starts with a "plus" or a "minus" and followed by + * an interval, treat it as relative to now. + */ + if (ifield == 1) + { + fmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ)); + GetCurrentTimeUsec(tm, fsec, tzp); + } + if ((fmask & DTK_DATE_M) != DTK_DATE_M) { if ((fmask & DTK_TIME_M) == DTK_TIME_M) *************** DecodeDateTime(char **field, int *ftype, *** 1415,1420 **** --- 1450,1506 ---- } } + if (*dtype == DTK_DATE || *dtype == DTK_EPOCH) + { + /* Return any interval to be added or subtracted */ + if (ifield > 0) + { + int idtype; + struct pg_tm tt, + *itm = &tt; + fsec_t ifsec; + + dterr = DecodeInterval(field + ifield, ftype + ifield, + nf - ifield, INTERVAL_FULL_RANGE, + &idtype, itm, &ifsec); + + /* if that thinks it's a bad format, try ISO8601 style */ + if (dterr == DTERR_BAD_FORMAT) + { + /* + * join the interval fields back into a single string by + * replacing the '\0' delimiters with spaces. This works + * because ParseDateTime uses a single buffer to decode the + * fields. + */ + for (i = ifield; i < nf - 1; i++) + field[i][strlen(field[i])] = ' '; + + dterr = DecodeISO8601Interval(field[ifield], &idtype, + itm, &ifsec); + } + if (dterr) + return dterr; + if (idtype != DTK_DELTA) + return DTERR_BAD_FORMAT; + + /* if "minus", negate the interval */ + if (val == DTK_MINUS) + { + ifsec = -ifsec; + itm->tm_sec = -itm->tm_sec; + itm->tm_min = -itm->tm_min; + itm->tm_hour = -itm->tm_hour; + itm->tm_mday = -itm->tm_mday; + itm->tm_mon = -itm->tm_mon; + itm->tm_year = -itm->tm_year; + } + + if (offset == NULL || tm2interval(itm, ifsec, offset) != 0) + return DTERR_BAD_FORMAT; + } + } + return 0; } diff --git a/src/backend/utils/adt/nabstime.c b/src/backend/utils/adt/nabstime.c new file mode 100644 index 6771e78..f027edd *** a/src/backend/utils/adt/nabstime.c --- b/src/backend/utils/adt/nabstime.c *************** abstimein(PG_FUNCTION_ARGS) *** 235,241 **** dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) ! dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz); if (dterr != 0) DateTimeParseError(dterr, str, "abstime"); --- 235,241 ---- dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) ! dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, NULL); if (dterr != 0) DateTimeParseError(dterr, str, "abstime"); diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c new file mode 100644 index 45e7002..6bddfa5 *** a/src/backend/utils/adt/timestamp.c --- b/src/backend/utils/adt/timestamp.c *************** timestamp_in(PG_FUNCTION_ARGS) *** 154,164 **** char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; char workbuf[MAXDATELEN + MAXDATEFIELDS]; dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) ! dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz); if (dterr != 0) DateTimeParseError(dterr, str, "timestamp"); --- 154,166 ---- char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; char workbuf[MAXDATELEN + MAXDATEFIELDS]; + Interval interval, + *offset = &interval; dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) ! dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, offset); if (dterr != 0) DateTimeParseError(dterr, str, "timestamp"); *************** timestamp_in(PG_FUNCTION_ARGS) *** 169,178 **** --- 171,190 ---- ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range: \"%s\"", str))); + if (offset->month != 0 || offset->day != 0 || offset->time != 0) + result = DatumGetTimestamp( + DirectFunctionCall2(timestamp_pl_interval, + TimestampGetDatum(result), + PointerGetDatum(offset))); break; case DTK_EPOCH: result = SetEpochTimestamp(); + if (offset->month != 0 || offset->day != 0 || offset->time != 0) + result = DatumGetTimestamp( + DirectFunctionCall2(timestamp_pl_interval, + TimestampGetDatum(result), + PointerGetDatum(offset))); break; case DTK_LATE: *************** timestamptz_in(PG_FUNCTION_ARGS) *** 418,428 **** char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; char workbuf[MAXDATELEN + MAXDATEFIELDS]; dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) ! dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz); if (dterr != 0) DateTimeParseError(dterr, str, "timestamp with time zone"); --- 430,442 ---- char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; char workbuf[MAXDATELEN + MAXDATEFIELDS]; + Interval interval, + *offset = &interval; dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf); if (dterr == 0) ! dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, offset); if (dterr != 0) DateTimeParseError(dterr, str, "timestamp with time zone"); *************** timestamptz_in(PG_FUNCTION_ARGS) *** 433,442 **** --- 447,466 ---- ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range: \"%s\"", str))); + if (offset->month != 0 || offset->day != 0 || offset->time != 0) + result = DatumGetTimestampTz( + DirectFunctionCall2(timestamptz_pl_interval, + TimestampTzGetDatum(result), + PointerGetDatum(offset))); break; case DTK_EPOCH: result = SetEpochTimestamp(); + if (offset->month != 0 || offset->day != 0 || offset->time != 0) + result = DatumGetTimestampTz( + DirectFunctionCall2(timestamptz_pl_interval, + TimestampTzGetDatum(result), + PointerGetDatum(offset))); break; case DTK_LATE: diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h new file mode 100644 index 2880304..c19f872 *** a/src/include/utils/datetime.h --- b/src/include/utils/datetime.h *************** struct tzEntry; *** 45,50 **** --- 45,52 ---- #define TODAY "today" #define TOMORROW "tomorrow" #define YESTERDAY "yesterday" + #define PLUS "plus" + #define MINUS "minus" #define ZULU "zulu" #define DMICROSEC "usecond" *************** struct tzEntry; *** 178,183 **** --- 180,188 ---- #define DTK_ISOYEAR 36 #define DTK_ISODOW 37 + #define DTK_PLUS 38 + #define DTK_MINUS 39 + /* * Bit mask definitions for time parsing. *************** struct tzEntry; *** 190,198 **** #define DTK_DATE_M (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)) #define DTK_TIME_M (DTK_M(HOUR) | DTK_M(MINUTE) | DTK_ALL_SECS_M) ! #define MAXDATELEN 63 /* maximum possible length of an input date * string (not counting tr. null) */ ! #define MAXDATEFIELDS 25 /* maximum possible number of fields in a date * string */ #define TOKMAXLEN 10 /* only this many chars are stored in * datetktbl */ --- 195,203 ---- #define DTK_DATE_M (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)) #define DTK_TIME_M (DTK_M(HOUR) | DTK_M(MINUTE) | DTK_ALL_SECS_M) ! #define MAXDATELEN 512 /* maximum possible length of an input date * string (not counting tr. null) */ ! #define MAXDATEFIELDS 50 /* maximum possible number of fields in a date * string */ #define TOKMAXLEN 10 /* only this many chars are stored in * datetktbl */ *************** extern int date2j(int year, int month, i *** 299,307 **** extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen, char **field, int *ftype, int maxfields, int *numfields); ! extern int DecodeDateTime(char **field, int *ftype, ! int nf, int *dtype, ! struct pg_tm * tm, fsec_t *fsec, int *tzp); extern int DecodeTimeOnly(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm, fsec_t *fsec, int *tzp); --- 304,312 ---- extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen, char **field, int *ftype, int maxfields, int *numfields); ! extern int DecodeDateTime(char **field, int *ftype, int nf, ! int *dtype, struct pg_tm * tm, fsec_t *fsec, int *tzp, ! Interval *offset); extern int DecodeTimeOnly(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm, fsec_t *fsec, int *tzp);