1/* src/interfaces/ecpg/pgtypeslib/interval.c */
10#error -ffast-math is known to break this code
19/* copy&pasted from .../src/backend/utils/adt/datetime.c
20 * and changed struct pg_tm to struct tm
33 *fsec += rint(frac * 1000000);
37/* copy&pasted from .../src/backend/utils/adt/datetime.c
38 * and changed struct pg_tm to struct tm
48 extra_days = (int) frac;
54/* copy&pasted from .../src/backend/utils/adt/datetime.c */
60 if (!(isdigit((
unsigned char) *
str) || *
str ==
'-' || *
str ==
'.'))
64 /* did we not see anything that looks like a double? */
65 if (*endptr ==
str || errno != 0)
67 /* watch out for overflow */
68 if (val < INT_MIN || val > INT_MAX)
70 /* be very sure we truncate towards zero (cf dtrunc()) */
72 *ipart = (int) floor(
val);
74 *ipart = (int) -floor(-
val);
75 *fpart =
val - *ipart;
79/* copy&pasted from .../src/backend/utils/adt/datetime.c */
83 /* We might have had a leading '-' */
84 if (*fieldstart ==
'-')
86 return strspn(fieldstart,
"0123456789");
90/* copy&pasted from .../src/backend/utils/adt/datetime.c
91 * and changed struct pg_tm to struct tm
105/* copy&pasted from .../src/backend/utils/adt/datetime.c
107 * * changed struct pg_tm to struct tm
109 * * Made the function static
113 int *dtype,
struct /* pg_ */ tm *
tm,
fsec_t *fsec)
115 bool datepart =
true;
116 bool havefield =
false;
121 if (strlen(
str) < 2 ||
str[0] !=
'P')
133 if (*
str ==
'T')
/* T indicates the beginning of the time part */
147 * Note: we could step off the end of the string here. Code below
148 * *must* exit the loop if unit == '0円'.
154 switch (unit)
/* before T: Y M W D */
172 case 'T':
/* ISO 8601 4.4.3.3 Alternative Format / Basic */
186 /* Else fall through to extended alternative format */
188 case '-':
/* ISO 8601 4.4.3.3 Alternative Format,
236 /* not a valid date unit suffix */
242 switch (unit)
/* after T: H M S */
256 case '0円':
/* ISO 8601 4.4.3.3 Alternative Format */
265 /* Else fall through to extended alternative format */
267 case ':':
/* ISO 8601 4.4.3.3 Alternative Format,
298 /* not a valid time unit suffix */
311/* copy&pasted from .../src/backend/utils/adt/datetime.c
314 * * changed struct pg_tm to struct tm
316 * * ECPG code called this without a 'range' parameter
317 * removed 'int range' from the argument list and
318 * places where DecodeTime is called; and added
319 * int range = INTERVAL_FULL_RANGE;
321 * * ECPG seems not to have a global IntervalStyle
323 * int IntervalStyle = INTSTYLE_POSTGRES;
327 int *dtype,
struct /* pg_ */ tm *
tm,
fsec_t *fsec)
331 bool is_before =
false;
345 /* read through list backwards to pick up units before values */
346 for (
i = nf - 1;
i >= 0;
i--)
361 * Timezone is a token with a leading sign character and at
362 * least one digit; there could be ':', '.', '-' embedded in
365 Assert(*field[
i] ==
'-' || *field[
i] ==
'+');
368 * Try for hh:mm or hh:mm:ss. If not, fall through to
369 * DTK_NUMBER case, which can handle signed float numbers and
370 * signed year-month values.
372 if (strchr(field[
i] + 1,
':') != NULL &&
373 DecodeTime(field[
i] + 1,
/* INTERVAL_FULL_RANGE, */
374 &tmask,
tm, fsec) == 0)
376 if (*field[
i] ==
'-')
378 /* flip the sign on all fields */
386 * Set the next type to be a day, if units are not
387 * specified. This handles the case of '1 +02:03' since we
388 * are reading right to left.
400 /* use typmod to decide what rightmost field is */
441 /* SQL "years-months" syntax */
450 if (*field[
i] ==
'-')
458 fval = strtod(cp, &cp);
459 if (*cp !=
'0円' || errno != 0)
462 if (*field[
i] ==
'-')
465 else if (*cp ==
'0円')
470 tmask = 0;
/* DTK_M(type); */
475 *fsec += rint(
val + fval);
480 *fsec += rint((
val + fval) * 1000);
486 *fsec += rint(fval * 1000000);
489 * If any subseconds were specified, consider this
490 * microsecond and millisecond input as well.
564 tmask = 0;
/* DTK_M(type); */
595 /* ensure that at least one time field has been found */
599 /* ensure fractional seconds are fractional */
610 * The SQL standard defines the interval literal
612 * to mean "negative 1 days and negative 1 hours", while Postgres
613 * traditionally treats this as meaning "negative 1 days and positive
614 * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign
615 * to all fields if there are no other explicit signs.
617 * We leave the signs alone if there are additional explicit signs.
618 * This protects us against misinterpreting postgres-style dump output,
619 * since the postgres-style output code has always put an explicit sign on
620 * all fields following a negative field. But note that SQL-spec output
621 * is ambiguous and can be misinterpreted on load! (So it's best practice
622 * to dump in postgres style, not SQL style.)
627 /* Check for additional explicit signs */
628 bool more_signs =
false;
630 for (
i = 1;
i < nf;
i++)
632 if (*field[
i] ==
'-' || *field[
i] ==
'+')
642 * Rather than re-determining which field was field[0], just force
662 /* finally, AGO negates everything */
678/* copy&pasted from .../src/backend/utils/adt/datetime.c */
681 bool *is_zero,
bool *is_before)
685 /* first nonzero value sets is_before */
688 *is_before = (
value < 0);
695 return cp + strlen(cp);
698/* copy&pasted from .../src/backend/utils/adt/datetime.c */
701 bool *is_zero,
bool *is_before)
706 (!*is_zero) ?
" " :
"",
707 (*is_before &&
value > 0) ?
"+" :
"",
710 (
value != 1) ?
"s" :
"");
713 * Each nonzero field sets is_before for (only) the next one. This is a
714 * tad bizarre but it's how it worked before...
716 *is_before = (
value < 0);
718 return cp + strlen(cp);
721/* copy&pasted from .../src/backend/utils/adt/datetime.c */
728 return cp + strlen(cp);
731/* copy&pasted from .../src/backend/utils/adt/datetime.c */
745 sprintf(cp,
"%02d.%0*d", abs(sec), precision, abs(fsec));
747 sprintf(cp,
"%d.%0*d", abs(sec), precision, abs(fsec));
753/* copy&pasted from .../src/backend/utils/adt/datetime.c
768 bool is_before =
false;
772 * The sign of year and month are guaranteed to match, since they are
773 * stored internally as "month". But we'll need to check for is_before and
774 * is_zero when determining the signs of day and hour/minute/seconds
779 /* SQL Standard interval format */
782 bool has_negative = year < 0 || mon < 0 ||
783 mday < 0 || hour < 0 ||
784 min < 0 || sec < 0 || fsec < 0;
785 bool has_positive = year > 0 || mon > 0 ||
786 mday > 0 || hour > 0 ||
787 min > 0 || sec > 0 || fsec > 0;
788 bool has_year_month = year != 0 || mon != 0;
789 bool has_day_time = mday != 0 || hour != 0 ||
790 min != 0 || sec != 0 || fsec != 0;
791 bool has_day = mday != 0;
792 bool sql_standard_value = !(has_negative && has_positive) &&
793 !(has_year_month && has_day_time);
796 * SQL Standard wants only 1 "<sign>" preceding the whole
797 * interval ... but can't do that if mixed signs.
799 if (has_negative && sql_standard_value)
811 if (!has_negative && !has_positive)
815 else if (!sql_standard_value)
818 * For non sql-standard interval values, force outputting
819 * the signs to avoid ambiguities with intervals with
820 * mixed sign components.
822 char year_sign = (year < 0 || mon < 0) ?
'-' :
'+';
823 char day_sign = (mday < 0) ?
'-' :
'+';
824 char sec_sign = (hour < 0 || min < 0 ||
825 sec < 0 || fsec < 0) ?
'-' :
'+';
827 sprintf(cp,
"%c%d-%d %c%d %c%d:%02d:",
828 year_sign, abs(year), abs(mon),
830 sec_sign, abs(hour), abs(min));
834 else if (has_year_month)
836 sprintf(cp,
"%d-%d", year, mon);
840 sprintf(cp,
"%d %d:%02d:", mday, hour, min);
846 sprintf(cp,
"%d:%02d:", hour, min);
853 /* ISO 8601 "time-intervals by duration only" */
855 /* special-case zero to avoid printing nothing */
856 if (year == 0 && mon == 0 && mday == 0 &&
857 hour == 0 && min == 0 && sec == 0 && fsec == 0)
866 if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
870 if (sec != 0 || fsec != 0)
872 if (sec < 0 || fsec < 0)
881 /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
886 if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
888 bool minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
892 (minus ?
"-" : (is_before ?
"+" :
"")),
893 abs(hour), abs(min));
899 /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */
909 if (sec != 0 || fsec != 0)
912 if (sec < 0 || (sec == 0 && fsec < 0))
923 /* We output "ago", not negatives, so use abs(). */
925 (abs(sec) != 1 || fsec != 0) ?
"s" :
"");
928 /* identically zero? then put in a unitless zero... */
939 * Convert an interval data type to a tm structure.
992 /* result can be NULL if we run out of memory */
1015 char **ptr = (endptr != NULL) ? endptr : &realptr;
int DecodeUnits(int field, const char *lowtoken, int *val)
int ParseDateTime(const char *timestr, char *workbuf, size_t buflen, char **field, int *ftype, int maxfields, int *numfields)
static int DecodeTime(char *str, int fmask, int range, int *tmask, struct pg_tm *tm, fsec_t *fsec)
#define MAX_INTERVAL_PRECISION
void TrimTrailingZeros(char *str)
Assert(PointerIsAligned(start, uint64))
#define DTERR_FIELD_OVERFLOW
char * pgtypes_strdup(const char *str)
char * pgtypes_alloc(long size)
interval * PGTYPESinterval_new(void)
void PGTYPESinterval_free(interval *intvl)
static char * AddISO8601IntPart(char *cp, int value, char units)
static void AdjustFractDays(double frac, struct tm *tm, fsec_t *fsec, int scale)
static int DecodeISO8601Interval(char *str, int *dtype, struct tm *tm, fsec_t *fsec)
int PGTYPESinterval_copy(interval *intvlsrc, interval *intvldest)
static int interval2tm(interval span, struct tm *tm, fsec_t *fsec)
static int tm2interval(struct tm *tm, fsec_t fsec, interval *span)
void EncodeInterval(struct tm *tm, fsec_t fsec, int style, char *str)
static int ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart)
static void ClearPgTm(struct tm *tm, fsec_t *fsec)
static char * AddPostgresIntPart(char *cp, int value, const char *units, bool *is_zero, bool *is_before)
static void AdjustFractSeconds(double frac, struct tm *tm, fsec_t *fsec, int scale)
static void AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
int DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm *tm, fsec_t *fsec)
interval * PGTYPESinterval_from_asc(char *str, char **endptr)
static char * AddVerboseIntPart(char *cp, int value, const char *units, bool *is_zero, bool *is_before)
char * PGTYPESinterval_to_asc(interval *span)
static int ISO8601IntegerWidth(const char *fieldstart)
#define INTSTYLE_SQL_STANDARD
#define INTSTYLE_POSTGRES_VERBOSE
#define INTSTYLE_ISO_8601
#define INTSTYLE_POSTGRES
#define PGTYPES_INTVL_BAD_INTERVAL
static struct cvec * range(struct vars *v, chr a, chr b, int cases)
int strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
#define INTERVAL_FULL_RANGE