diff options
Diffstat (limited to 'src/backend/utils/adt/timestamp.c')
-rw-r--r-- | src/backend/utils/adt/timestamp.c | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index cf6982b95dd..f21bbae3696 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -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. |