relative-timestamps.patch

application/octet-stream

Filename: relative-timestamps.patch
Type: application/octet-stream
Part: 0
Message: Re: Inputting relative datetimes
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, &timestamp) != 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);