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:
@ -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.
|
||||
|
Reference in New Issue
Block a user