Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit591e088

Browse files
committed
Fix portability issues in datetime parsing.
datetime.c's parsing logic has assumed that strtod() will accepta string that looks like ".", which it does in glibc, but not onsome less-common platforms such as AIX. The result of this wasthat datetime fields like "123." would be accepted on some platformsbut not others; which is a sufficiently odd case that it's not thatsurprising we've heard no field complaints. But commite39f990extended that assumption to new places, and happened to add a testcase that exposed the platform dependency. Remove this dependencyby special-casing situations without any digits after the decimalpoint.(Again, this is in part a pre-existing bug but I don't feel acompulsion to back-patch.)Also, rearrangee39f990's changes in formatting.c to avoid aCoverity complaint that we were copying an uninitialized field.Discussion:https://postgr.es/m/1592893.1648969747@sss.pgh.pa.us
1 parentf3c15cb commit591e088

File tree

4 files changed

+132
-36
lines changed

4 files changed

+132
-36
lines changed

‎src/backend/utils/adt/datetime.c

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -668,19 +668,50 @@ AdjustYears(int64 val, int scale,
668668
}
669669

670670

671-
/* Fetch a fractional-second value with suitable error checking */
671+
/*
672+
* Parse the fractional part of a number (decimal point and optional digits,
673+
* followed by end of string). Returns the fractional value into *frac.
674+
*
675+
* Returns 0 if successful, DTERR code if bogus input detected.
676+
*/
677+
staticint
678+
ParseFraction(char*cp,double*frac)
679+
{
680+
/* Caller should always pass the start of the fraction part */
681+
Assert(*cp=='.');
682+
683+
/*
684+
* We want to allow just "." with no digits, but some versions of strtod
685+
* will report EINVAL for that, so special-case it.
686+
*/
687+
if (cp[1]=='\0')
688+
{
689+
*frac=0;
690+
}
691+
else
692+
{
693+
errno=0;
694+
*frac=strtod(cp,&cp);
695+
/* check for parse failure */
696+
if (*cp!='\0'||errno!=0)
697+
returnDTERR_BAD_FORMAT;
698+
}
699+
return0;
700+
}
701+
702+
/*
703+
* Fetch a fractional-second value with suitable error checking.
704+
* Same as ParseFraction except we convert the result to integer microseconds.
705+
*/
672706
staticint
673707
ParseFractionalSecond(char*cp,fsec_t*fsec)
674708
{
675709
doublefrac;
710+
intdterr;
676711

677-
/* Caller should always pass the start of the fraction part */
678-
Assert(*cp=='.');
679-
errno=0;
680-
frac=strtod(cp,&cp);
681-
/* check for parse failure */
682-
if (*cp!='\0'||errno!=0)
683-
returnDTERR_BAD_FORMAT;
712+
dterr=ParseFraction(cp,&frac);
713+
if (dterr)
714+
returndterr;
684715
*fsec=rint(frac*1000000);
685716
return0;
686717
}
@@ -1248,10 +1279,9 @@ DecodeDateTime(char **field, int *ftype, int nf,
12481279
{
12491280
doubletime;
12501281

1251-
errno=0;
1252-
time=strtod(cp,&cp);
1253-
if (*cp!='\0'||errno!=0)
1254-
returnDTERR_BAD_FORMAT;
1282+
dterr=ParseFraction(cp,&time);
1283+
if (dterr)
1284+
returndterr;
12551285
time *=USECS_PER_DAY;
12561286
dt2time(time,
12571287
&tm->tm_hour,&tm->tm_min,
@@ -2146,10 +2176,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
21462176
{
21472177
doubletime;
21482178

2149-
errno=0;
2150-
time=strtod(cp,&cp);
2151-
if (*cp!='\0'||errno!=0)
2152-
returnDTERR_BAD_FORMAT;
2179+
dterr=ParseFraction(cp,&time);
2180+
if (dterr)
2181+
returndterr;
21532182
time *=USECS_PER_DAY;
21542183
dt2time(time,
21552184
&tm->tm_hour,&tm->tm_min,
@@ -3035,13 +3064,21 @@ DecodeNumberField(int len, char *str, int fmask,
30353064
* Can we use ParseFractionalSecond here? Not clear whether trailing
30363065
* junk should be rejected ...
30373066
*/
3038-
doublefrac;
3067+
if (cp[1]=='\0')
3068+
{
3069+
/* avoid assuming that strtod will accept "." */
3070+
*fsec=0;
3071+
}
3072+
else
3073+
{
3074+
doublefrac;
30393075

3040-
errno=0;
3041-
frac=strtod(cp,NULL);
3042-
if (errno!=0)
3043-
returnDTERR_BAD_FORMAT;
3044-
*fsec=rint(frac*1000000);
3076+
errno=0;
3077+
frac=strtod(cp,NULL);
3078+
if (errno!=0)
3079+
returnDTERR_BAD_FORMAT;
3080+
*fsec=rint(frac*1000000);
3081+
}
30453082
/* Now truncate off the fraction for further processing */
30463083
*cp='\0';
30473084
len=strlen(str);
@@ -3467,11 +3504,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
34673504
}
34683505
elseif (*cp=='.')
34693506
{
3470-
errno=0;
3471-
fval=strtod(cp,&cp);
3472-
if (*cp!='\0'||errno!=0)
3473-
returnDTERR_BAD_FORMAT;
3474-
3507+
dterr=ParseFraction(cp,&fval);
3508+
if (dterr)
3509+
returndterr;
34753510
if (*field[i]=='-')
34763511
fval=-fval;
34773512
}
@@ -3650,6 +3685,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
36503685
* Helper functions to avoid duplicated code in DecodeISO8601Interval.
36513686
*
36523687
* Parse a decimal value and break it into integer and fractional parts.
3688+
* Set *endptr to end+1 of the parsed substring.
36533689
* Returns 0 or DTERR code.
36543690
*/
36553691
staticint
@@ -3676,7 +3712,20 @@ ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
36763712

36773713
/* Parse fractional part if there is any */
36783714
if (**endptr=='.')
3679-
*fpart=strtod(*endptr,endptr)*sign;
3715+
{
3716+
/*
3717+
* As in ParseFraction, some versions of strtod insist on seeing some
3718+
* digits after '.', but some don't. We want to allow zero digits
3719+
* after '.' as long as there were some before it.
3720+
*/
3721+
if (isdigit((unsignedchar)*(*endptr+1)))
3722+
*fpart=strtod(*endptr,endptr)*sign;
3723+
else
3724+
{
3725+
(*endptr)++;/* advance over '.' */
3726+
str++;/* so next test will fail if no digits */
3727+
}
3728+
}
36803729

36813730
/* did we not see anything that looks like a number? */
36823731
if (*endptr==str||errno!=0)

‎src/backend/utils/adt/formatting.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4134,11 +4134,13 @@ timestamp_to_char(PG_FUNCTION_ARGS)
41344134
ereport(ERROR,
41354135
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
41364136
errmsg("timestamp out of range")));
4137-
COPY_tm(tm,&tt);
41384137

4139-
thisdate=date2j(tm->tm_year,tm->tm_mon,tm->tm_mday);
4140-
tm->tm_wday= (thisdate+1) %7;
4141-
tm->tm_yday=thisdate-date2j(tm->tm_year,1,1)+1;
4138+
/* calculate wday and yday, because timestamp2tm doesn't */
4139+
thisdate=date2j(tt.tm_year,tt.tm_mon,tt.tm_mday);
4140+
tt.tm_wday= (thisdate+1) %7;
4141+
tt.tm_yday=thisdate-date2j(tt.tm_year,1,1)+1;
4142+
4143+
COPY_tm(tm,&tt);
41424144

41434145
if (!(res=datetime_to_char_body(&tmtc,fmt, false,PG_GET_COLLATION())))
41444146
PG_RETURN_NULL();
@@ -4168,11 +4170,13 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
41684170
ereport(ERROR,
41694171
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
41704172
errmsg("timestamp out of range")));
4171-
COPY_tm(tm,&tt);
41724173

4173-
thisdate=date2j(tm->tm_year,tm->tm_mon,tm->tm_mday);
4174-
tm->tm_wday= (thisdate+1) %7;
4175-
tm->tm_yday=thisdate-date2j(tm->tm_year,1,1)+1;
4174+
/* calculate wday and yday, because timestamp2tm doesn't */
4175+
thisdate=date2j(tt.tm_year,tt.tm_mon,tt.tm_mday);
4176+
tt.tm_wday= (thisdate+1) %7;
4177+
tt.tm_yday=thisdate-date2j(tt.tm_year,1,1)+1;
4178+
4179+
COPY_tm(tm,&tt);
41764180

41774181
if (!(res=datetime_to_char_body(&tmtc,fmt, false,PG_GET_COLLATION())))
41784182
PG_RETURN_NULL();

‎src/test/regress/expected/interval.out

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,41 @@ select interval 'P0002' AS "year only",
908908
2 years | 2 years 10 mons | 2 years 10 mons 15 days | 2 years 00:00:01 | 2 years 10 mons 00:00:01 | 2 years 10 mons 15 days 00:00:01 | 10:00:00 | 10:30:00
909909
(1 row)
910910

911+
-- Check handling of fractional fields in ISO8601 format.
912+
select interval 'P1Y0M3DT4H5M6S';
913+
interval
914+
------------------------
915+
1 year 3 days 04:05:06
916+
(1 row)
917+
918+
select interval 'P1.0Y0M3DT4H5M6S';
919+
interval
920+
------------------------
921+
1 year 3 days 04:05:06
922+
(1 row)
923+
924+
select interval 'P1.1Y0M3DT4H5M6S';
925+
interval
926+
------------------------------
927+
1 year 1 mon 3 days 04:05:06
928+
(1 row)
929+
930+
select interval 'P1.Y0M3DT4H5M6S';
931+
interval
932+
------------------------
933+
1 year 3 days 04:05:06
934+
(1 row)
935+
936+
select interval 'P.1Y0M3DT4H5M6S';
937+
interval
938+
-----------------------
939+
1 mon 3 days 04:05:06
940+
(1 row)
941+
942+
select interval 'P.Y0M3DT4H5M6S'; -- error
943+
ERROR: invalid input syntax for type interval: "P.Y0M3DT4H5M6S"
944+
LINE 1: select interval 'P.Y0M3DT4H5M6S';
945+
^
911946
-- test a couple rounding cases that changed since 8.3 w/ HAVE_INT64_TIMESTAMP.
912947
SET IntervalStyle to postgres_verbose;
913948
select interval '-10 mons -3 days +03:55:06.70';

‎src/test/regress/sql/interval.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,14 @@ select interval 'P0002' AS "year only",
312312
interval'PT10'AS"hour only",
313313
interval'PT10:30'AS"hour minute";
314314

315+
-- Check handling of fractional fields in ISO8601 format.
316+
select interval'P1Y0M3DT4H5M6S';
317+
select interval'P1.0Y0M3DT4H5M6S';
318+
select interval'P1.1Y0M3DT4H5M6S';
319+
select interval'P1.Y0M3DT4H5M6S';
320+
select interval'P.1Y0M3DT4H5M6S';
321+
select interval'P.Y0M3DT4H5M6S';-- error
322+
315323
-- test a couple rounding cases that changed since 8.3 w/ HAVE_INT64_TIMESTAMP.
316324
SET IntervalStyle to postgres_verbose;
317325
select interval'-10 mons -3 days +03:55:06.70';

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp