aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/date.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/date.c')
-rw-r--r--src/backend/utils/adt/date.c308
1 files changed, 275 insertions, 33 deletions
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 6053d0e8a6f..83036e5985e 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -31,6 +31,7 @@
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
+#include "utils/numeric.h"
#include "utils/sortsupport.h"
/*
@@ -1063,6 +1064,182 @@ in_range_date_interval(PG_FUNCTION_ARGS)
}
+/* extract_date()
+ * Extract specified field from date type.
+ */
+Datum
+extract_date(PG_FUNCTION_ARGS)
+{
+ text *units = PG_GETARG_TEXT_PP(0);
+ DateADT date = PG_GETARG_DATEADT(1);
+ int64 intresult;
+ int type,
+ val;
+ char *lowunits;
+ int year,
+ mon,
+ mday;
+
+ lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
+ VARSIZE_ANY_EXHDR(units),
+ false);
+
+ type = DecodeUnits(0, lowunits, &val);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(0, lowunits, &val);
+
+ if (DATE_NOT_FINITE(date) && (type == UNITS || type == RESERV))
+ {
+ switch (val)
+ {
+ /* Oscillating units */
+ case DTK_DAY:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ case DTK_WEEK:
+ case DTK_DOW:
+ case DTK_ISODOW:
+ case DTK_DOY:
+ PG_RETURN_NULL();
+ break;
+
+ /* Monotonically-increasing units */
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_JULIAN:
+ case DTK_ISOYEAR:
+ case DTK_EPOCH:
+ if (DATE_IS_NOBEGIN(date))
+ PG_RETURN_NUMERIC(DatumGetNumeric(DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1))));
+ else
+ PG_RETURN_NUMERIC(DatumGetNumeric(DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1))));
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("date units \"%s\" not supported",
+ lowunits)));
+ }
+ }
+ else if (type == UNITS)
+ {
+ j2date(date + POSTGRES_EPOCH_JDATE, &year, &mon, &mday);
+
+ switch (val)
+ {
+ case DTK_DAY:
+ intresult = mday;
+ break;
+
+ case DTK_MONTH:
+ intresult = mon;
+ break;
+
+ case DTK_QUARTER:
+ intresult = (mon - 1) / 3 + 1;
+ break;
+
+ case DTK_WEEK:
+ intresult = date2isoweek(year, mon, mday);
+ break;
+
+ case DTK_YEAR:
+ if (year > 0)
+ intresult = year;
+ else
+ /* there is no year 0, just 1 BC and 1 AD */
+ intresult = year - 1;
+ break;
+
+ case DTK_DECADE:
+ /* see comments in timestamp_part */
+ if (year >= 0)
+ intresult = year / 10;
+ else
+ intresult = -((8 - (year - 1)) / 10);
+ break;
+
+ case DTK_CENTURY:
+ /* see comments in timestamp_part */
+ if (year > 0)
+ intresult = (year + 99) / 100;
+ else
+ intresult = -((99 - (year - 1)) / 100);
+ break;
+
+ case DTK_MILLENNIUM:
+ /* see comments in timestamp_part */
+ if (year > 0)
+ intresult = (year + 999) / 1000;
+ else
+ intresult = -((999 - (year - 1)) / 1000);
+ break;
+
+ case DTK_JULIAN:
+ intresult = date + POSTGRES_EPOCH_JDATE;
+ break;
+
+ case DTK_ISOYEAR:
+ intresult = date2isoyear(year, mon, mday);
+ /* Adjust BC years */
+ if (intresult <= 0)
+ intresult -= 1;
+ break;
+
+ case DTK_DOW:
+ case DTK_ISODOW:
+ intresult = j2day(date + POSTGRES_EPOCH_JDATE);
+ if (val == DTK_ISODOW && intresult == 0)
+ intresult = 7;
+ break;
+
+ case DTK_DOY:
+ intresult = date2j(year, mon, mday) - date2j(year, 1, 1) + 1;
+ break;
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("date units \"%s\" not supported",
+ lowunits)));
+ intresult = 0;
+ }
+ }
+ else if (type == RESERV)
+ {
+ switch (val)
+ {
+ case DTK_EPOCH:
+ intresult = ((int64) date + POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY;
+ break;
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("date units \"%s\" not supported",
+ lowunits)));
+ intresult = 0;
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("date units \"%s\" not recognized", lowunits)));
+ intresult = 0;
+ }
+
+ PG_RETURN_NUMERIC(int64_to_numeric(intresult));
+}
+
+
/* Add an interval to a date, giving a new date.
* Must handle both positive and negative intervals.
*
@@ -1949,15 +2126,15 @@ in_range_time_interval(PG_FUNCTION_ARGS)
}
-/* time_part()
+/* time_part() and extract_time()
* Extract specified field from time type.
*/
-Datum
-time_part(PG_FUNCTION_ARGS)
+static Datum
+time_part_common(PG_FUNCTION_ARGS, bool retnumeric)
{
text *units = PG_GETARG_TEXT_PP(0);
TimeADT time = PG_GETARG_TIMEADT(1);
- float8 result;
+ int64 intresult;
int type,
val;
char *lowunits;
@@ -1981,23 +2158,37 @@ time_part(PG_FUNCTION_ARGS)
switch (val)
{
case DTK_MICROSEC:
- result = tm->tm_sec * 1000000.0 + fsec;
+ intresult = tm->tm_sec * 1000000 + fsec;
break;
case DTK_MILLISEC:
- result = tm->tm_sec * 1000.0 + fsec / 1000.0;
+ if (retnumeric)
+ /*---
+ * tm->tm_sec * 1000 + fsec / 1000
+ * = (tm->tm_sec * 1'000'000 + fsec) / 1000
+ */
+ PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * 1000000LL + fsec, 3));
+ else
+ PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0);
break;
case DTK_SECOND:
- result = tm->tm_sec + fsec / 1000000.0;
+ if (retnumeric)
+ /*---
+ * tm->tm_sec + fsec / 1'000'000
+ * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
+ */
+ PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * 1000000LL + fsec, 6));
+ else
+ PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0);
break;
case DTK_MINUTE:
- result = tm->tm_min;
+ intresult = tm->tm_min;
break;
case DTK_HOUR:
- result = tm->tm_hour;
+ intresult = tm->tm_hour;
break;
case DTK_TZ:
@@ -2016,12 +2207,15 @@ time_part(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"time\" units \"%s\" not recognized",
lowunits)));
- result = 0;
+ intresult = 0;
}
}
else if (type == RESERV && val == DTK_EPOCH)
{
- result = time / 1000000.0;
+ if (retnumeric)
+ PG_RETURN_NUMERIC(int64_div_fast_to_numeric(time, 6));
+ else
+ PG_RETURN_FLOAT8(time / 1000000.0);
}
else
{
@@ -2029,10 +2223,25 @@ time_part(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"time\" units \"%s\" not recognized",
lowunits)));
- result = 0;
+ intresult = 0;
}
- PG_RETURN_FLOAT8(result);
+ if (retnumeric)
+ PG_RETURN_NUMERIC(int64_to_numeric(intresult));
+ else
+ PG_RETURN_FLOAT8(intresult);
+}
+
+Datum
+time_part(PG_FUNCTION_ARGS)
+{
+ return time_part_common(fcinfo, false);
+}
+
+Datum
+extract_time(PG_FUNCTION_ARGS)
+{
+ return time_part_common(fcinfo, true);
}
@@ -2686,15 +2895,15 @@ datetimetz_timestamptz(PG_FUNCTION_ARGS)
}
-/* timetz_part()
+/* timetz_part() and extract_timetz()
* Extract specified field from time type.
*/
-Datum
-timetz_part(PG_FUNCTION_ARGS)
+static Datum
+timetz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
{
text *units = PG_GETARG_TEXT_PP(0);
TimeTzADT *time = PG_GETARG_TIMETZADT_P(1);
- float8 result;
+ int64 intresult;
int type,
val;
char *lowunits;
@@ -2709,7 +2918,6 @@ timetz_part(PG_FUNCTION_ARGS)
if (type == UNITS)
{
- double dummy;
int tz;
fsec_t fsec;
struct pg_tm tt,
@@ -2720,38 +2928,49 @@ timetz_part(PG_FUNCTION_ARGS)
switch (val)
{
case DTK_TZ:
- result = -tz;
+ intresult = -tz;
break;
case DTK_TZ_MINUTE:
- result = -tz;
- result /= SECS_PER_MINUTE;
- FMODULO(result, dummy, (double) MINS_PER_HOUR);
+ intresult = (-tz / SECS_PER_MINUTE) % MINS_PER_HOUR;
break;
case DTK_TZ_HOUR:
- dummy = -tz;
- FMODULO(dummy, result, (double) SECS_PER_HOUR);
+ intresult = -tz / SECS_PER_HOUR;
break;
case DTK_MICROSEC:
- result = tm->tm_sec * 1000000.0 + fsec;
+ intresult = tm->tm_sec * 1000000 + fsec;
break;
case DTK_MILLISEC:
- result = tm->tm_sec * 1000.0 + fsec / 1000.0;
+ if (retnumeric)
+ /*---
+ * tm->tm_sec * 1000 + fsec / 1000
+ * = (tm->tm_sec * 1'000'000 + fsec) / 1000
+ */
+ PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * 1000000LL + fsec, 3));
+ else
+ PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0);
break;
case DTK_SECOND:
- result = tm->tm_sec + fsec / 1000000.0;
+ if (retnumeric)
+ /*---
+ * tm->tm_sec + fsec / 1'000'000
+ * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
+ */
+ PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * 1000000LL + fsec, 6));
+ else
+ PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0);
break;
case DTK_MINUTE:
- result = tm->tm_min;
+ intresult = tm->tm_min;
break;
case DTK_HOUR:
- result = tm->tm_hour;
+ intresult = tm->tm_hour;
break;
case DTK_DAY:
@@ -2766,12 +2985,19 @@ timetz_part(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"time with time zone\" units \"%s\" not recognized",
lowunits)));
- result = 0;
+ intresult = 0;
}
}
else if (type == RESERV && val == DTK_EPOCH)
{
- result = time->time / 1000000.0 + time->zone;
+ if (retnumeric)
+ /*---
+ * time->time / 1'000'000 + time->zone
+ * = (time->time + time->zone * 1'000'000) / 1'000'000
+ */
+ PG_RETURN_NUMERIC(int64_div_fast_to_numeric(time->time + time->zone * 1000000LL, 6));
+ else
+ PG_RETURN_FLOAT8(time->time / 1000000.0 + time->zone);
}
else
{
@@ -2779,10 +3005,26 @@ timetz_part(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"time with time zone\" units \"%s\" not recognized",
lowunits)));
- result = 0;
+ intresult = 0;
}
- PG_RETURN_FLOAT8(result);
+ if (retnumeric)
+ PG_RETURN_NUMERIC(int64_to_numeric(intresult));
+ else
+ PG_RETURN_FLOAT8(intresult);
+}
+
+
+Datum
+timetz_part(PG_FUNCTION_ARGS)
+{
+ return timetz_part_common(fcinfo, false);
+}
+
+Datum
+extract_timetz(PG_FUNCTION_ARGS)
+{
+ return timetz_part_common(fcinfo, true);
}
/* timetz_zone()