Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

+3-8
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

+287-4
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

+3
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)