1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-16 06:01:02 +03:00

Constructors for interval, timestamp, timestamptz

Author: Pavel Stěhule, editorialized somewhat by Álvaro Herrera
Reviewed-by: Tomáš Vondra, Marko Tiikkaja
With input from Fabrízio de Royes Mello, Jim Nasby
This commit is contained in:
Alvaro Herrera
2014-03-04 15:09:43 -03:00
parent af2543e884
commit 84df54b22e
14 changed files with 590 additions and 4 deletions

View File

@ -483,6 +483,234 @@ timestamptz_in(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(result);
}
/*
* Try to parse a timezone specification, and return its timezone offset value
* if it's acceptable. Otherwise, an error is thrown.
*/
static int
parse_sane_timezone(struct pg_tm *tm, text *zone)
{
char tzname[TZ_STRLEN_MAX + 1];
int rt;
int tz;
text_to_cstring_buffer(zone, tzname, sizeof(tzname));
/*
* Look up the requested timezone. First we try to interpret it as a
* numeric timezone specification; if DecodeTimezone decides it doesn't
* like the format, we look in the date token table (to handle cases like
* "EST"), and if that also fails, we look in the timezone database (to
* handle cases like "America/New_York"). (This matches the order in
* which timestamp input checks the cases; it's important because the
* timezone database unwisely uses a few zone names that are identical to
* offset abbreviations.)
*
* Note pg_tzset happily parses numeric input that DecodeTimezone would
* reject. To avoid having it accept input that would otherwise be seen
* as invalid, it's enough to disallow having a digit in the first
* position of our input string.
*/
if (isdigit(*tzname))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid input syntax for numeric time zone: \"%s\"",
tzname),
errhint("Numeric time zones must have \"-\" or \"+\" as first character.")));
rt = DecodeTimezone(tzname, &tz);
if (rt != 0)
{
char *lowzone;
int type,
val;
if (rt == DTERR_TZDISP_OVERFLOW)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("numeric time zone \"%s\" out of range", tzname)));
else if (rt != DTERR_BAD_FORMAT)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized", tzname)));
lowzone = downcase_truncate_identifier(tzname,
strlen(tzname),
false);
type = DecodeSpecial(0, lowzone, &val);
if (type == TZ || type == DTZ)
tz = val * MINS_PER_HOUR;
else
{
pg_tz *tzp;
tzp = pg_tzset(tzname);
if (tzp)
tz = DetermineTimeZoneOffset(tm, tzp);
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("time zone \"%s\" not recognized", tzname)));
}
}
return tz;
}
/*
* make_timestamp_internal
* workhorse for make_timestamp and make_timestamptz
*/
static Timestamp
make_timestamp_internal(int year, int month, int day,
int hour, int min, double sec)
{
struct pg_tm tm;
TimeOffset date;
TimeOffset time;
int dterr;
Timestamp result;
tm.tm_year = year;
tm.tm_mon = month;
tm.tm_mday = day;
/*
* Note: we'll reject zero or negative year values. Perhaps negatives
* should be allowed to represent BC years?
*/
dterr = ValidateDate(DTK_DATE_M, false, false, false, &tm);
if (dterr != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
errmsg("date field value out of range: %d-%02d-%02d",
year, month, day)));
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: %d-%02d-%02d",
year, month, day)));
date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
/* This should match the checks in DecodeTimeOnly */
if (hour < 0 || min < 0 || min > MINS_PER_HOUR - 1 ||
sec < 0 || sec > SECS_PER_MINUTE ||
hour > HOURS_PER_DAY ||
/* test for > 24:00:00 */
(hour == HOURS_PER_DAY && (min > 0 || sec > 0)))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
errmsg("time field value out of range: %d:%02d:%02g",
hour, min, sec)));
/* This should match tm2time */
#ifdef HAVE_INT64_TIMESTAMP
time = (((hour * MINS_PER_HOUR + min) * SECS_PER_MINUTE)
* USECS_PER_SEC) + rint(sec * USECS_PER_SEC);
result = date * USECS_PER_DAY + time;
/* check for major overflow */
if ((result - time) / USECS_PER_DAY != date)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g",
year, month, day,
hour, min, sec)));
/* check for just-barely overflow (okay except time-of-day wraps) */
/* caution: we want to allow 1999-12-31 24:00:00 */
if ((result < 0 && date > 0) ||
(result > 0 && date < -1))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g",
year, month, day,
hour, min, sec)));
#else
time = ((hour * MINS_PER_HOUR + min) * SECS_PER_MINUTE) + sec;
result = date * SECS_PER_DAY + time;
#endif
return result;
}
/*
* make_timestamp() - timestamp constructor
*/
Datum
make_timestamp(PG_FUNCTION_ARGS)
{
int32 year = PG_GETARG_INT32(0);
int32 month = PG_GETARG_INT32(1);
int32 mday = PG_GETARG_INT32(2);
int32 hour = PG_GETARG_INT32(3);
int32 min = PG_GETARG_INT32(4);
float8 sec = PG_GETARG_FLOAT8(5);
Timestamp result;
result = make_timestamp_internal(year, month, mday,
hour, min, sec);
PG_RETURN_TIMESTAMP(result);
}
/*
* make_timestamptz() - timestamp with time zone constructor
*/
Datum
make_timestamptz(PG_FUNCTION_ARGS)
{
int32 year = PG_GETARG_INT32(0);
int32 month = PG_GETARG_INT32(1);
int32 mday = PG_GETARG_INT32(2);
int32 hour = PG_GETARG_INT32(3);
int32 min = PG_GETARG_INT32(4);
float8 sec = PG_GETARG_FLOAT8(5);
Timestamp result;
result = make_timestamp_internal(year, month, mday,
hour, min, sec);
PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(result));
}
/*
* Construct a timestamp with time zone.
* As above, but the time zone is specified as seventh argument.
*/
Datum
make_timestamptz_at_timezone(PG_FUNCTION_ARGS)
{
int32 year = PG_GETARG_INT32(0);
int32 month = PG_GETARG_INT32(1);
int32 mday = PG_GETARG_INT32(2);
int32 hour = PG_GETARG_INT32(3);
int32 min = PG_GETARG_INT32(4);
float8 sec = PG_GETARG_FLOAT8(5);
text *zone = PG_GETARG_TEXT_PP(6);
Timestamp timestamp;
struct pg_tm tt;
int tz;
fsec_t fsec;
timestamp = make_timestamp_internal(year, month, mday,
hour, min, sec);
if (timestamp2tm(timestamp, NULL, &tt, &fsec, NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
tz = parse_sane_timezone(&tt, zone);
PG_RETURN_TIMESTAMPTZ((TimestampTz) dt2local(timestamp, -tz));
}
/* timestamptz_out()
* Convert a timestamp to external form.
*/
@ -1220,6 +1448,42 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod)
}
}
/*
* make_interval - numeric Interval constructor
*/
Datum
make_interval(PG_FUNCTION_ARGS)
{
int32 years = PG_GETARG_INT32(0);
int32 months = PG_GETARG_INT32(1);
int32 weeks = PG_GETARG_INT32(2);
int32 days = PG_GETARG_INT32(3);
int32 hours = PG_GETARG_INT32(4);
int32 mins = PG_GETARG_INT32(5);
double secs = PG_GETARG_FLOAT8(6);
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
result->month = years * MONTHS_PER_YEAR + months;
result->day = weeks * 7 + days;
#ifdef HAVE_INT64_TIMESTAMP
result->time = ((((hours * INT64CONST(60)) +
mins) * INT64CONST(60)) +
secs) * USECS_PER_SEC;
#else
result->time = (((hours * (double) MINS_PER_HOUR) +
mins) * (double) SECS_PER_MINUTE) +
secs;
#endif
#ifdef NOT_USED
/* this is a no-op for negative typmods */
AdjustIntervalForTypmod(result, -1);
#endif
PG_RETURN_INTERVAL_P(result);
}
/* EncodeSpecialTimestamp()
* Convert reserved timestamp data type to string.