aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/timestamp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/timestamp.c')
-rw-r--r--src/backend/utils/adt/timestamp.c264
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.