Skip to content

Commit 66c74f8

Browse files
committed
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
1 parent 1a950f3 commit 66c74f8

File tree

4 files changed

+296
-12
lines changed

4 files changed

+296
-12
lines changed

src/backend/utils/adt/date.c

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@
4141
#endif
4242

4343

44-
static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
45-
static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
46-
static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
47-
48-
4944
/* common code for timetypmodin and timetztypmodin */
5045
static int32
5146
anytime_typmodin(bool istz, ArrayType *ta)
@@ -1203,7 +1198,7 @@ time_in(PG_FUNCTION_ARGS)
12031198
/* tm2time()
12041199
* Convert a tm structure to a time data type.
12051200
*/
1206-
static int
1201+
int
12071202
tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
12081203
{
12091204
*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)
13791374
* have a fundamental tie together but rather a coincidence of
13801375
* implementation. - thomas
13811376
*/
1382-
static void
1377+
void
13831378
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
13841379
{
13851380
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -1957,7 +1952,7 @@ time_part(PG_FUNCTION_ARGS)
19571952
/* tm2timetz()
19581953
* Convert a tm structure to a time data type.
19591954
*/
1960-
static int
1955+
int
19611956
tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
19621957
{
19631958
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *

src/backend/utils/adt/formatting.c

Lines changed: 287 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,11 @@ typedef struct NUMProc
992992
*L_currency_symbol;
993993
} NUMProc;
994994

995+
/* Return flags for DCH_from_char() */
996+
#define DCH_DATED 0x01
997+
#define DCH_TIMED 0x02
998+
#define DCH_ZONED 0x04
999+
9951000
/* ----------
9961001
* Functions
9971002
* ----------
@@ -1025,7 +1030,8 @@ static int from_char_parse_int(int *dest, char **src, FormatNode *node);
10251030
static int seq_search(char *name, const char *const *array, int type, int max, int *len);
10261031
static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
10271032
static void do_to_timestamp(text *date_txt, text *fmt, bool std,
1028-
struct pg_tm *tm, fsec_t *fsec, int *fprec);
1033+
struct pg_tm *tm, fsec_t *fsec, int *fprec,
1034+
uint32 *flags);
10291035
static char *fill_str(char *str, int c, int max);
10301036
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
10311037
static char *int_to_roman(int number);
@@ -3517,6 +3523,109 @@ DCH_prevent_counter_overflow(void)
35173523
}
35183524
}
35193525

3526+
/* Get mask of date/time/zone components present in format nodes. */
3527+
static int
3528+
DCH_datetime_type(FormatNode *node)
3529+
{
3530+
FormatNode *n;
3531+
int flags = 0;
3532+
3533+
for (n = node; n->type != NODE_TYPE_END; n++)
3534+
{
3535+
if (n->type != NODE_TYPE_ACTION)
3536+
continue;
3537+
3538+
switch (n->key->id)
3539+
{
3540+
case DCH_FX:
3541+
break;
3542+
case DCH_A_M:
3543+
case DCH_P_M:
3544+
case DCH_a_m:
3545+
case DCH_p_m:
3546+
case DCH_AM:
3547+
case DCH_PM:
3548+
case DCH_am:
3549+
case DCH_pm:
3550+
case DCH_HH:
3551+
case DCH_HH12:
3552+
case DCH_HH24:
3553+
case DCH_MI:
3554+
case DCH_SS:
3555+
case DCH_MS: /* millisecond */
3556+
case DCH_US: /* microsecond */
3557+
case DCH_FF1:
3558+
case DCH_FF2:
3559+
case DCH_FF3:
3560+
case DCH_FF4:
3561+
case DCH_FF5:
3562+
case DCH_FF6:
3563+
case DCH_SSSS:
3564+
flags |= DCH_TIMED;
3565+
break;
3566+
case DCH_tz:
3567+
case DCH_TZ:
3568+
case DCH_OF:
3569+
ereport(ERROR,
3570+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3571+
errmsg("formatting field \"%s\" is only supported in to_char",
3572+
n->key->name)));
3573+
flags |= DCH_ZONED;
3574+
break;
3575+
case DCH_TZH:
3576+
case DCH_TZM:
3577+
flags |= DCH_ZONED;
3578+
break;
3579+
case DCH_A_D:
3580+
case DCH_B_C:
3581+
case DCH_a_d:
3582+
case DCH_b_c:
3583+
case DCH_AD:
3584+
case DCH_BC:
3585+
case DCH_ad:
3586+
case DCH_bc:
3587+
case DCH_MONTH:
3588+
case DCH_Month:
3589+
case DCH_month:
3590+
case DCH_MON:
3591+
case DCH_Mon:
3592+
case DCH_mon:
3593+
case DCH_MM:
3594+
case DCH_DAY:
3595+
case DCH_Day:
3596+
case DCH_day:
3597+
case DCH_DY:
3598+
case DCH_Dy:
3599+
case DCH_dy:
3600+
case DCH_DDD:
3601+
case DCH_IDDD:
3602+
case DCH_DD:
3603+
case DCH_D:
3604+
case DCH_ID:
3605+
case DCH_WW:
3606+
case DCH_Q:
3607+
case DCH_CC:
3608+
case DCH_Y_YYY:
3609+
case DCH_YYYY:
3610+
case DCH_IYYY:
3611+
case DCH_YYY:
3612+
case DCH_IYY:
3613+
case DCH_YY:
3614+
case DCH_IY:
3615+
case DCH_Y:
3616+
case DCH_I:
3617+
case DCH_RM:
3618+
case DCH_rm:
3619+
case DCH_W:
3620+
case DCH_J:
3621+
flags |= DCH_DATED;
3622+
break;
3623+
}
3624+
}
3625+
3626+
return flags;
3627+
}
3628+
35203629
/* select a DCHCacheEntry to hold the given format picture */
35213630
static DCHCacheEntry *
35223631
DCH_cache_getnew(const char *str, bool std)
@@ -3808,7 +3917,7 @@ to_timestamp(PG_FUNCTION_ARGS)
38083917
fsec_t fsec;
38093918
int fprec;
38103919

3811-
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec);
3920+
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
38123921

38133922
/* Use the specified time zone, if any. */
38143923
if (tm.tm_zone)
@@ -3847,7 +3956,7 @@ to_date(PG_FUNCTION_ARGS)
38473956
struct pg_tm tm;
38483957
fsec_t fsec;
38493958

3850-
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL);
3959+
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
38513960

38523961
/* Prevent overflow in Julian-day routines */
38533962
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3868,6 +3977,176 @@ to_date(PG_FUNCTION_ARGS)
38683977
PG_RETURN_DATEADT(result);
38693978
}
38703979

3980+
/*
3981+
* Convert the 'date_txt' input to a datetime type using argument 'fmt' as a format string.
3982+
* The actual data type (returned in 'typid', 'typmod') is determined by
3983+
* the presence of date/time/zone components in the format string.
3984+
*
3985+
* When timezone component is present, the corresponding offset is set to '*tz'.
3986+
*/
3987+
Datum
3988+
parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
3989+
int32 *typmod, int *tz)
3990+
{
3991+
struct pg_tm tm;
3992+
fsec_t fsec;
3993+
int fprec = 0;
3994+
uint32 flags;
3995+
3996+
do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
3997+
3998+
*typmod = fprec ? fprec : -1; /* fractional part precision */
3999+
4000+
if (flags & DCH_DATED)
4001+
{
4002+
if (flags & DCH_TIMED)
4003+
{
4004+
if (flags & DCH_ZONED)
4005+
{
4006+
TimestampTz result;
4007+
4008+
if (tm.tm_zone)
4009+
{
4010+
int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
4011+
4012+
if (dterr)
4013+
DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz");
4014+
}
4015+
else
4016+
{
4017+
/*
4018+
* Time zone is present in format string, but not in input
4019+
* string. Assuming do_to_timestamp() triggers no error
4020+
* this should be possible only in non-strict case.
4021+
*/
4022+
Assert(!strict);
4023+
4024+
ereport(ERROR,
4025+
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4026+
errmsg("missing time zone in input string for type timestamptz")));
4027+
}
4028+
4029+
if (tm2timestamp(&tm, fsec, tz, &result) != 0)
4030+
ereport(ERROR,
4031+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4032+
errmsg("timestamptz out of range")));
4033+
4034+
AdjustTimestampForTypmod(&result, *typmod);
4035+
4036+
*typid = TIMESTAMPTZOID;
4037+
return TimestampTzGetDatum(result);
4038+
}
4039+
else
4040+
{
4041+
Timestamp result;
4042+
4043+
if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
4044+
ereport(ERROR,
4045+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4046+
errmsg("timestamp out of range")));
4047+
4048+
AdjustTimestampForTypmod(&result, *typmod);
4049+
4050+
*typid = TIMESTAMPOID;
4051+
return TimestampGetDatum(result);
4052+
}
4053+
}
4054+
else
4055+
{
4056+
if (flags & DCH_ZONED)
4057+
{
4058+
ereport(ERROR,
4059+
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4060+
errmsg("datetime format is zoned but not timed")));
4061+
}
4062+
else
4063+
{
4064+
DateADT result;
4065+
4066+
/* Prevent overflow in Julian-day routines */
4067+
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
4068+
ereport(ERROR,
4069+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4070+
errmsg("date out of range: \"%s\"",
4071+
text_to_cstring(date_txt))));
4072+
4073+
result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
4074+
POSTGRES_EPOCH_JDATE;
4075+
4076+
/* Now check for just-out-of-range dates */
4077+
if (!IS_VALID_DATE(result))
4078+
ereport(ERROR,
4079+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4080+
errmsg("date out of range: \"%s\"",
4081+
text_to_cstring(date_txt))));
4082+
4083+
*typid = DATEOID;
4084+
return DateADTGetDatum(result);
4085+
}
4086+
}
4087+
}
4088+
else if (flags & DCH_TIMED)
4089+
{
4090+
if (flags & DCH_ZONED)
4091+
{
4092+
TimeTzADT *result = palloc(sizeof(TimeTzADT));
4093+
4094+
if (tm.tm_zone)
4095+
{
4096+
int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
4097+
4098+
if (dterr)
4099+
DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
4100+
}
4101+
else
4102+
{
4103+
/*
4104+
* Time zone is present in format string, but not in input
4105+
* string. Assuming do_to_timestamp() triggers no error this
4106+
* should be possible only in non-strict case.
4107+
*/
4108+
Assert(!strict);
4109+
4110+
ereport(ERROR,
4111+
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4112+
errmsg("missing time zone in input string for type timetz")));
4113+
}
4114+
4115+
if (tm2timetz(&tm, fsec, *tz, result) != 0)
4116+
ereport(ERROR,
4117+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4118+
errmsg("timetz out of range")));
4119+
4120+
AdjustTimeForTypmod(&result->time, *typmod);
4121+
4122+
*typid = TIMETZOID;
4123+
return TimeTzADTPGetDatum(result);
4124+
}
4125+
else
4126+
{
4127+
TimeADT result;
4128+
4129+
if (tm2time(&tm, fsec, &result) != 0)
4130+
ereport(ERROR,
4131+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4132+
errmsg("time out of range")));
4133+
4134+
AdjustTimeForTypmod(&result, *typmod);
4135+
4136+
*typid = TIMEOID;
4137+
return TimeADTGetDatum(result);
4138+
}
4139+
}
4140+
else
4141+
{
4142+
ereport(ERROR,
4143+
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4144+
errmsg("datetime format is not dated and not timed")));
4145+
}
4146+
4147+
return (Datum) 0;
4148+
}
4149+
38714150
/*
38724151
* do_to_timestamp: shared code for to_timestamp and to_date
38734152
*
@@ -3883,7 +4162,8 @@ to_date(PG_FUNCTION_ARGS)
38834162
*/
38844163
static void
38854164
do_to_timestamp(text *date_txt, text *fmt, bool std,
3886-
struct pg_tm *tm, fsec_t *fsec, int *fprec)
4165+
struct pg_tm *tm, fsec_t *fsec, int *fprec,
4166+
uint32 *flags)
38874167
{
38884168
FormatNode *format;
38894169
TmFromChar tmfc;
@@ -3940,6 +4220,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
39404220

39414221
pfree(fmt_str);
39424222

4223+
if (flags)
4224+
*flags = DCH_datetime_type(format);
4225+
39434226
if (!incache)
39444227
pfree(format);
39454228
}

src/include/utils/date.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
7676
extern TimeADT GetSQLLocalTime(int32 typmod);
7777
extern int time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
7878
extern int timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
79+
extern int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
80+
extern int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
81+
extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
7982

8083
#endif /* DATE_H */

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy