mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-30 21:01:20 +02:00
Implement parse_datetime() function
This commit adds parse_datetime() function, which implements datetime parsing with extended features demanded by upcoming jsonpath .datetime() method: * Dynamic type identification based on template string, * Support for standard-conforming 'strict' mode, * Timezone offset is returned as separate value. Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov. Revised by me. Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
This commit is contained in:
parent
1a950f37d0
commit
66c74f8b6e
@ -41,11 +41,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
|
|
||||||
static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
|
|
||||||
static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
|
|
||||||
|
|
||||||
|
|
||||||
/* common code for timetypmodin and timetztypmodin */
|
/* common code for timetypmodin and timetztypmodin */
|
||||||
static int32
|
static int32
|
||||||
anytime_typmodin(bool istz, ArrayType *ta)
|
anytime_typmodin(bool istz, ArrayType *ta)
|
||||||
@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
|
|||||||
/* tm2time()
|
/* tm2time()
|
||||||
* Convert a tm structure to a time data type.
|
* Convert a tm structure to a time data type.
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
|
tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
|
||||||
{
|
{
|
||||||
*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
|
*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
|
||||||
@ -1379,7 +1374,7 @@ time_scale(PG_FUNCTION_ARGS)
|
|||||||
* have a fundamental tie together but rather a coincidence of
|
* have a fundamental tie together but rather a coincidence of
|
||||||
* implementation. - thomas
|
* implementation. - thomas
|
||||||
*/
|
*/
|
||||||
static void
|
void
|
||||||
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
|
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
|
||||||
{
|
{
|
||||||
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
|
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
|
||||||
@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
|
|||||||
/* tm2timetz()
|
/* tm2timetz()
|
||||||
* Convert a tm structure to a time data type.
|
* Convert a tm structure to a time data type.
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
|
tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
|
||||||
{
|
{
|
||||||
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
|
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
|
||||||
|
@ -992,6 +992,11 @@ typedef struct NUMProc
|
|||||||
*L_currency_symbol;
|
*L_currency_symbol;
|
||||||
} NUMProc;
|
} NUMProc;
|
||||||
|
|
||||||
|
/* Return flags for DCH_from_char() */
|
||||||
|
#define DCH_DATED 0x01
|
||||||
|
#define DCH_TIMED 0x02
|
||||||
|
#define DCH_ZONED 0x04
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* Functions
|
* Functions
|
||||||
* ----------
|
* ----------
|
||||||
@ -1025,7 +1030,8 @@ static int from_char_parse_int(int *dest, char **src, FormatNode *node);
|
|||||||
static int seq_search(char *name, const char *const *array, int type, int max, int *len);
|
static int seq_search(char *name, const char *const *array, int type, int max, int *len);
|
||||||
static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
|
static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
|
||||||
static void do_to_timestamp(text *date_txt, text *fmt, bool std,
|
static void do_to_timestamp(text *date_txt, text *fmt, bool std,
|
||||||
struct pg_tm *tm, fsec_t *fsec, int *fprec);
|
struct pg_tm *tm, fsec_t *fsec, int *fprec,
|
||||||
|
uint32 *flags);
|
||||||
static char *fill_str(char *str, int c, int max);
|
static char *fill_str(char *str, int c, int max);
|
||||||
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
|
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
|
||||||
static char *int_to_roman(int number);
|
static char *int_to_roman(int number);
|
||||||
@ -3517,6 +3523,109 @@ DCH_prevent_counter_overflow(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get mask of date/time/zone components present in format nodes. */
|
||||||
|
static int
|
||||||
|
DCH_datetime_type(FormatNode *node)
|
||||||
|
{
|
||||||
|
FormatNode *n;
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
for (n = node; n->type != NODE_TYPE_END; n++)
|
||||||
|
{
|
||||||
|
if (n->type != NODE_TYPE_ACTION)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (n->key->id)
|
||||||
|
{
|
||||||
|
case DCH_FX:
|
||||||
|
break;
|
||||||
|
case DCH_A_M:
|
||||||
|
case DCH_P_M:
|
||||||
|
case DCH_a_m:
|
||||||
|
case DCH_p_m:
|
||||||
|
case DCH_AM:
|
||||||
|
case DCH_PM:
|
||||||
|
case DCH_am:
|
||||||
|
case DCH_pm:
|
||||||
|
case DCH_HH:
|
||||||
|
case DCH_HH12:
|
||||||
|
case DCH_HH24:
|
||||||
|
case DCH_MI:
|
||||||
|
case DCH_SS:
|
||||||
|
case DCH_MS: /* millisecond */
|
||||||
|
case DCH_US: /* microsecond */
|
||||||
|
case DCH_FF1:
|
||||||
|
case DCH_FF2:
|
||||||
|
case DCH_FF3:
|
||||||
|
case DCH_FF4:
|
||||||
|
case DCH_FF5:
|
||||||
|
case DCH_FF6:
|
||||||
|
case DCH_SSSS:
|
||||||
|
flags |= DCH_TIMED;
|
||||||
|
break;
|
||||||
|
case DCH_tz:
|
||||||
|
case DCH_TZ:
|
||||||
|
case DCH_OF:
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("formatting field \"%s\" is only supported in to_char",
|
||||||
|
n->key->name)));
|
||||||
|
flags |= DCH_ZONED;
|
||||||
|
break;
|
||||||
|
case DCH_TZH:
|
||||||
|
case DCH_TZM:
|
||||||
|
flags |= DCH_ZONED;
|
||||||
|
break;
|
||||||
|
case DCH_A_D:
|
||||||
|
case DCH_B_C:
|
||||||
|
case DCH_a_d:
|
||||||
|
case DCH_b_c:
|
||||||
|
case DCH_AD:
|
||||||
|
case DCH_BC:
|
||||||
|
case DCH_ad:
|
||||||
|
case DCH_bc:
|
||||||
|
case DCH_MONTH:
|
||||||
|
case DCH_Month:
|
||||||
|
case DCH_month:
|
||||||
|
case DCH_MON:
|
||||||
|
case DCH_Mon:
|
||||||
|
case DCH_mon:
|
||||||
|
case DCH_MM:
|
||||||
|
case DCH_DAY:
|
||||||
|
case DCH_Day:
|
||||||
|
case DCH_day:
|
||||||
|
case DCH_DY:
|
||||||
|
case DCH_Dy:
|
||||||
|
case DCH_dy:
|
||||||
|
case DCH_DDD:
|
||||||
|
case DCH_IDDD:
|
||||||
|
case DCH_DD:
|
||||||
|
case DCH_D:
|
||||||
|
case DCH_ID:
|
||||||
|
case DCH_WW:
|
||||||
|
case DCH_Q:
|
||||||
|
case DCH_CC:
|
||||||
|
case DCH_Y_YYY:
|
||||||
|
case DCH_YYYY:
|
||||||
|
case DCH_IYYY:
|
||||||
|
case DCH_YYY:
|
||||||
|
case DCH_IYY:
|
||||||
|
case DCH_YY:
|
||||||
|
case DCH_IY:
|
||||||
|
case DCH_Y:
|
||||||
|
case DCH_I:
|
||||||
|
case DCH_RM:
|
||||||
|
case DCH_rm:
|
||||||
|
case DCH_W:
|
||||||
|
case DCH_J:
|
||||||
|
flags |= DCH_DATED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
/* select a DCHCacheEntry to hold the given format picture */
|
/* select a DCHCacheEntry to hold the given format picture */
|
||||||
static DCHCacheEntry *
|
static DCHCacheEntry *
|
||||||
DCH_cache_getnew(const char *str, bool std)
|
DCH_cache_getnew(const char *str, bool std)
|
||||||
@ -3808,7 +3917,7 @@ to_timestamp(PG_FUNCTION_ARGS)
|
|||||||
fsec_t fsec;
|
fsec_t fsec;
|
||||||
int fprec;
|
int fprec;
|
||||||
|
|
||||||
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
|
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
|
||||||
|
|
||||||
/* Use the specified time zone, if any. */
|
/* Use the specified time zone, if any. */
|
||||||
if (tm.tm_zone)
|
if (tm.tm_zone)
|
||||||
@ -3847,7 +3956,7 @@ to_date(PG_FUNCTION_ARGS)
|
|||||||
struct pg_tm tm;
|
struct pg_tm tm;
|
||||||
fsec_t fsec;
|
fsec_t fsec;
|
||||||
|
|
||||||
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
|
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
|
||||||
|
|
||||||
/* Prevent overflow in Julian-day routines */
|
/* Prevent overflow in Julian-day routines */
|
||||||
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
|
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
|
||||||
@ -3868,6 +3977,176 @@ to_date(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_DATEADT(result);
|
PG_RETURN_DATEADT(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert the 'date_txt' input to a datetime type using argument 'fmt' as a format string.
|
||||||
|
* The actual data type (returned in 'typid', 'typmod') is determined by
|
||||||
|
* the presence of date/time/zone components in the format string.
|
||||||
|
*
|
||||||
|
* When timezone component is present, the corresponding offset is set to '*tz'.
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
|
||||||
|
int32 *typmod, int *tz)
|
||||||
|
{
|
||||||
|
struct pg_tm tm;
|
||||||
|
fsec_t fsec;
|
||||||
|
int fprec = 0;
|
||||||
|
uint32 flags;
|
||||||
|
|
||||||
|
do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
|
||||||
|
|
||||||
|
*typmod = fprec ? fprec : -1; /* fractional part precision */
|
||||||
|
|
||||||
|
if (flags & DCH_DATED)
|
||||||
|
{
|
||||||
|
if (flags & DCH_TIMED)
|
||||||
|
{
|
||||||
|
if (flags & DCH_ZONED)
|
||||||
|
{
|
||||||
|
TimestampTz result;
|
||||||
|
|
||||||
|
if (tm.tm_zone)
|
||||||
|
{
|
||||||
|
int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
|
||||||
|
|
||||||
|
if (dterr)
|
||||||
|
DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Time zone is present in format string, but not in input
|
||||||
|
* string. Assuming do_to_timestamp() triggers no error
|
||||||
|
* this should be possible only in non-strict case.
|
||||||
|
*/
|
||||||
|
Assert(!strict);
|
||||||
|
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
|
||||||
|
errmsg("missing time zone in input string for type timestamptz")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tm2timestamp(&tm, fsec, tz, &result) != 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("timestamptz out of range")));
|
||||||
|
|
||||||
|
AdjustTimestampForTypmod(&result, *typmod);
|
||||||
|
|
||||||
|
*typid = TIMESTAMPTZOID;
|
||||||
|
return TimestampTzGetDatum(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Timestamp result;
|
||||||
|
|
||||||
|
if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("timestamp out of range")));
|
||||||
|
|
||||||
|
AdjustTimestampForTypmod(&result, *typmod);
|
||||||
|
|
||||||
|
*typid = TIMESTAMPOID;
|
||||||
|
return TimestampGetDatum(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (flags & DCH_ZONED)
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
|
||||||
|
errmsg("datetime format is zoned but not timed")));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DateADT result;
|
||||||
|
|
||||||
|
/* Prevent overflow in Julian-day routines */
|
||||||
|
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: \"%s\"",
|
||||||
|
text_to_cstring(date_txt))));
|
||||||
|
|
||||||
|
result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
|
||||||
|
POSTGRES_EPOCH_JDATE;
|
||||||
|
|
||||||
|
/* Now check for just-out-of-range dates */
|
||||||
|
if (!IS_VALID_DATE(result))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("date out of range: \"%s\"",
|
||||||
|
text_to_cstring(date_txt))));
|
||||||
|
|
||||||
|
*typid = DATEOID;
|
||||||
|
return DateADTGetDatum(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (flags & DCH_TIMED)
|
||||||
|
{
|
||||||
|
if (flags & DCH_ZONED)
|
||||||
|
{
|
||||||
|
TimeTzADT *result = palloc(sizeof(TimeTzADT));
|
||||||
|
|
||||||
|
if (tm.tm_zone)
|
||||||
|
{
|
||||||
|
int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
|
||||||
|
|
||||||
|
if (dterr)
|
||||||
|
DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Time zone is present in format string, but not in input
|
||||||
|
* string. Assuming do_to_timestamp() triggers no error this
|
||||||
|
* should be possible only in non-strict case.
|
||||||
|
*/
|
||||||
|
Assert(!strict);
|
||||||
|
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
|
||||||
|
errmsg("missing time zone in input string for type timetz")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tm2timetz(&tm, fsec, *tz, result) != 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("timetz out of range")));
|
||||||
|
|
||||||
|
AdjustTimeForTypmod(&result->time, *typmod);
|
||||||
|
|
||||||
|
*typid = TIMETZOID;
|
||||||
|
return TimeTzADTPGetDatum(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TimeADT result;
|
||||||
|
|
||||||
|
if (tm2time(&tm, fsec, &result) != 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("time out of range")));
|
||||||
|
|
||||||
|
AdjustTimeForTypmod(&result, *typmod);
|
||||||
|
|
||||||
|
*typid = TIMEOID;
|
||||||
|
return TimeADTGetDatum(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
|
||||||
|
errmsg("datetime format is not dated and not timed")));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Datum) 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* do_to_timestamp: shared code for to_timestamp and to_date
|
* do_to_timestamp: shared code for to_timestamp and to_date
|
||||||
*
|
*
|
||||||
@ -3883,7 +4162,8 @@ to_date(PG_FUNCTION_ARGS)
|
|||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
do_to_timestamp(text *date_txt, text *fmt, bool std,
|
do_to_timestamp(text *date_txt, text *fmt, bool std,
|
||||||
struct pg_tm *tm, fsec_t *fsec, int *fprec)
|
struct pg_tm *tm, fsec_t *fsec, int *fprec,
|
||||||
|
uint32 *flags)
|
||||||
{
|
{
|
||||||
FormatNode *format;
|
FormatNode *format;
|
||||||
TmFromChar tmfc;
|
TmFromChar tmfc;
|
||||||
@ -3940,6 +4220,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
|
|||||||
|
|
||||||
pfree(fmt_str);
|
pfree(fmt_str);
|
||||||
|
|
||||||
|
if (flags)
|
||||||
|
*flags = DCH_datetime_type(format);
|
||||||
|
|
||||||
if (!incache)
|
if (!incache)
|
||||||
pfree(format);
|
pfree(format);
|
||||||
}
|
}
|
||||||
|
@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
|
|||||||
extern TimeADT GetSQLLocalTime(int32 typmod);
|
extern TimeADT GetSQLLocalTime(int32 typmod);
|
||||||
extern int time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
|
extern int time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
|
||||||
extern int timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
|
extern int timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
|
||||||
|
extern int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
|
||||||
|
extern int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
|
||||||
|
extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
|
||||||
|
|
||||||
#endif /* DATE_H */
|
#endif /* DATE_H */
|
||||||
|
@ -26,4 +26,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
|
|||||||
extern char *asc_toupper(const char *buff, size_t nbytes);
|
extern char *asc_toupper(const char *buff, size_t nbytes);
|
||||||
extern char *asc_initcap(const char *buff, size_t nbytes);
|
extern char *asc_initcap(const char *buff, size_t nbytes);
|
||||||
|
|
||||||
|
extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
|
||||||
|
Oid *typid, int32 *typmod, int *tz);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user