1 #include "postgres_fe.h"
6 #error -ffast-math is known to break this code
11 #include "pgtypes_error.h"
12 #include "pgtypes_interval.h"
14 /* TrimTrailingZeros()
15 * ... resulting from printing numbers with full precision.
18 TrimTrailingZeros(char *str)
20 int len = strlen(str);
22 /* chop off trailing zeros... but leave at least 2 fractional digits */
23 while (*(str + len - 1) == '0' && *(str + len - 3) != '.')
31 * Decode time string which includes delimiters.
32 * Only check the lower limit on hours, since this same code
33 * can be used to represent time spans.
36 DecodeTime(char *str, int fmask, int *tmask, struct tm * tm, fsec_t *fsec)
42 tm->tm_hour = strtol(str, &cp, 10);
46 tm->tm_min = strtol(str, &cp, 10);
57 tm->tm_sec = strtol(str, &cp, 10);
62 #ifdef HAVE_INT64_TIMESTAMP
63 char fstr[MAXDATELEN + 1];
66 * OK, we have at most six digits to work with. Let's
67 * construct a string and then do the conversion to an
70 strncpy(fstr, (cp + 1), 7);
71 strcpy(fstr + strlen(fstr), "000000");
73 *fsec = strtol(fstr, &cp, 10);
76 *fsec = strtod(str, &cp);
85 /* do a sanity check */
86 #ifdef HAVE_INT64_TIMESTAMP
87 if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 ||
88 tm->tm_sec < 0 || tm->tm_sec > 59 || *fsec >= USECS_PER_SEC)
91 if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 ||
92 tm->tm_sec < 0 || tm->tm_sec > 59 || *fsec >= 1)
100 * Interpret previously parsed fields for general time interval.
101 * Return 0 if decoded and -1 if problems.
103 * Allow "date" field DTK_DATE since this could be just
104 * an unsigned floating point number. - thomas 1997-11-16
106 * Allow ISO-style time span, with implicit units on number of days
107 * preceding an hh:mm:ss field. - thomas 1998-04-30
110 DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec)
112 int is_before = FALSE;
133 /* read through list backwards to pick up units before values */
134 for (i = nf - 1; i >= 0; i--)
139 if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0)
147 * Timezone is a token with a leading sign character and
148 * otherwise the same as a non-signed time field
152 * A single signed number ends up here, but will be
153 * rejected by DecodeTime(). So, work this out to drop
154 * through to DTK_NUMBER, which *can* tolerate this.
157 while (*cp != '\0' && *cp != ':' && *cp != '.')
159 if (*cp == ':' && DecodeTime((field[i] + 1), fmask, &tmask, tm, fsec) == 0)
161 if (*field[i] == '-')
163 /* flip the sign on all fields */
164 tm->tm_hour = -tm->tm_hour;
165 tm->tm_min = -tm->tm_min;
166 tm->tm_sec = -tm->tm_sec;
171 * Set the next type to be a day, if units are not
172 * specified. This handles the case of '1 +02:03'
173 * since we are reading right to left.
179 else if (type == IGNORE_DTF)
184 * Got a decimal point? Then assume some sort of
185 * seconds specification
189 else if (*cp == '\0')
192 * Only a signed integer? Then must assume a
193 * timezone-like usage
202 val = strtol(field[i], &cp, 10);
204 if (type == IGNORE_DTF)
209 fval = strtod(cp, &cp);
216 else if (*cp == '\0')
221 tmask = 0; /* DTK_M(type); */
226 #ifdef HAVE_INT64_TIMESTAMP
229 *fsec += (val + fval) * 1e-6;
234 #ifdef HAVE_INT64_TIMESTAMP
235 *fsec += (val + fval) * 1000;
237 *fsec += (val + fval) * 1e-3;
243 #ifdef HAVE_INT64_TIMESTAMP
244 *fsec += fval * 1000000;
248 tmask = DTK_M(SECOND);
260 #ifdef HAVE_INT64_TIMESTAMP
261 *fsec += ((fval - sec) * 1000000);
263 *fsec += (fval - sec);
266 tmask = DTK_M(MINUTE);
278 #ifdef HAVE_INT64_TIMESTAMP
279 *fsec += (fval - sec) * 1000000;
281 *fsec += (fval - sec);
293 fval *= SECS_PER_DAY;
296 #ifdef HAVE_INT64_TIMESTAMP
297 *fsec += (fval - sec) * 1000000;
299 *fsec += (fval - sec);
302 tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
306 tm->tm_mday += val * 7;
311 fval *= 7 * SECS_PER_DAY;
314 #ifdef HAVE_INT64_TIMESTAMP
315 *fsec += (fval - sec) * 1000000;
317 *fsec += (fval - sec);
320 tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
329 fval *= 30 * SECS_PER_DAY;
332 #ifdef HAVE_INT64_TIMESTAMP
333 *fsec += (fval - sec) * 1000000;
335 *fsec += (fval - sec);
338 tmask = DTK_M(MONTH);
344 tm->tm_mon += fval * 12;
345 tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
349 tm->tm_year += val * 10;
351 tm->tm_mon += fval * 120;
352 tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
356 tm->tm_year += val * 100;
358 tm->tm_mon += fval * 1200;
359 tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
363 tm->tm_year += val * 1000;
365 tm->tm_mon += fval * 12000;
366 tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
376 type = DecodeUnits(i, field[i], &val);
377 if (type == IGNORE_DTF)
380 tmask = 0; /* DTK_M(type); */
393 tmask = (DTK_DATE_M || DTK_TIME_M);
415 #ifdef HAVE_INT64_TIMESTAMP
416 sec = (*fsec / USECS_PER_SEC);
417 *fsec -= (sec * USECS_PER_SEC);
419 TMODULO(*fsec, sec, 1e0);
427 tm->tm_sec = -(tm->tm_sec);
428 tm->tm_min = -(tm->tm_min);
429 tm->tm_hour = -(tm->tm_hour);
430 tm->tm_mday = -(tm->tm_mday);
431 tm->tm_mon = -(tm->tm_mon);
432 tm->tm_year = -(tm->tm_year);
435 /* ensure that at least one time field has been found */
436 return (fmask != 0) ? 0 : -1;
437 } /* DecodeInterval() */
440 * Interpret time structure as a delta time and convert to string.
442 * Support "traditional Postgres" and ISO-8601 styles.
443 * Actually, afaik ISO does not address time interval formatting,
444 * but this looks similar to the spec for absolute date/time.
445 * - thomas 1998-04-30
448 EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
450 int is_before = FALSE;
451 int is_nonzero = FALSE;
455 * The sign of year and month are guaranteed to match, since they are
456 * stored internally as "month". But we'll need to check for is_before
457 * and is_nonzero when determining the signs of hour/minute/seconds
462 /* compatible with ISO date formats */
464 if (tm->tm_year != 0)
466 sprintf(cp, "%d year%s",
467 tm->tm_year, (tm->tm_year != 1) ? "s" : "");
469 is_before = (tm->tm_year < 0);
475 sprintf(cp, "%s%s%d mon%s", is_nonzero ? " " : "",
476 (is_before && tm->tm_mon > 0) ? "+" : "",
477 tm->tm_mon, (tm->tm_mon != 1) ? "s" : "");
479 is_before = (tm->tm_mon < 0);
483 if (tm->tm_mday != 0)
485 sprintf(cp, "%s%s%d day%s", is_nonzero ? " " : "",
486 (is_before && tm->tm_mday > 0) ? "+" : "",
487 tm->tm_mday, (tm->tm_mday != 1) ? "s" : "");
489 is_before = (tm->tm_mday < 0);
492 if (!is_nonzero || tm->tm_hour != 0 || tm->tm_min != 0 ||
493 tm->tm_sec != 0 || fsec != 0)
495 int minus = tm->tm_hour < 0 || tm->tm_min < 0 ||
496 tm->tm_sec < 0 || fsec < 0;
498 sprintf(cp, "%s%s%02d:%02d", (is_nonzero ? " " : ""),
499 (minus ? "-" : (is_before ? "+" : "")),
500 abs(tm->tm_hour), abs(tm->tm_min));
502 /* Mark as "non-zero" since the fields are now filled in */
505 /* fractional seconds? */
508 #ifdef HAVE_INT64_TIMESTAMP
509 sprintf(cp, ":%02d", abs(tm->tm_sec));
511 sprintf(cp, ".%06d", (fsec >= 0) ? fsec : -(fsec));
514 sprintf(cp, ":%013.10f", fabs(fsec));
516 TrimTrailingZeros(cp);
520 /* otherwise, integer seconds only? */
521 else if (tm->tm_sec != 0)
523 sprintf(cp, ":%02d", abs(tm->tm_sec));
530 case USE_POSTGRES_DATES:
535 if (tm->tm_year != 0)
537 int year = tm->tm_year;
542 sprintf(cp, "%d year%s", year,
543 (year != 1) ? "s" : "");
545 is_before = (tm->tm_year < 0);
551 int mon = tm->tm_mon;
553 if (is_before || (!is_nonzero && tm->tm_mon < 0))
556 sprintf(cp, "%s%d mon%s", is_nonzero ? " " : "", mon,
557 (mon != 1) ? "s" : "");
560 is_before = (tm->tm_mon < 0);
564 if (tm->tm_mday != 0)
566 int day = tm->tm_mday;
568 if (is_before || (!is_nonzero && tm->tm_mday < 0))
571 sprintf(cp, "%s%d day%s", is_nonzero ? " " : "", day,
572 (day != 1) ? "s" : "");
575 is_before = (tm->tm_mday < 0);
578 if (tm->tm_hour != 0)
580 int hour = tm->tm_hour;
582 if (is_before || (!is_nonzero && tm->tm_hour < 0))
585 sprintf(cp, "%s%d hour%s", is_nonzero ? " " : "", hour,
586 (hour != 1) ? "s" : "");
589 is_before = (tm->tm_hour < 0);
595 int min = tm->tm_min;
597 if (is_before || (!is_nonzero && tm->tm_min < 0))
600 sprintf(cp, "%s%d min%s", is_nonzero ? " " : "", min,
601 (min != 1) ? "s" : "");
604 is_before = (tm->tm_min < 0);
608 /* fractional seconds? */
611 #ifdef HAVE_INT64_TIMESTAMP
612 if (is_before || (!is_nonzero && tm->tm_sec < 0))
613 tm->tm_sec = -tm->tm_sec;
614 sprintf(cp, "%s%d.%02d secs", is_nonzero ? " " : "",
615 tm->tm_sec, ((int) fsec) / 10000);
618 is_before = (fsec < 0);
624 if (is_before || (!is_nonzero && fsec < 0))
627 sprintf(cp, "%s%.2f secs", is_nonzero ? " " : "", sec);
630 is_before = (fsec < 0);
634 /* otherwise, integer seconds only? */
636 else if (tm->tm_sec != 0)
638 int sec = tm->tm_sec;
640 if (is_before || (!is_nonzero && tm->tm_sec < 0))
643 sprintf(cp, "%s%d sec%s", is_nonzero ? " " : "", sec,
644 (sec != 1) ? "s" : "");
647 is_before = (tm->tm_sec < 0);
653 /* identically zero? then put in a unitless zero... */
660 if (is_before && (style != USE_ISO_DATES))
667 } /* EncodeInterval() */
670 * Convert a interval data type to a tm structure.
673 interval2tm(interval span, struct tm * tm, fsec_t *fsec)
675 #ifdef HAVE_INT64_TIMESTAMP
684 tm->tm_year = span.month / 12;
685 tm->tm_mon = span.month % 12;
696 #ifdef HAVE_INT64_TIMESTAMP
697 tm->tm_mday = (time / USECS_PER_DAY);
698 time -= (tm->tm_mday * USECS_PER_DAY);
699 tm->tm_hour = (time / USECS_PER_HOUR);
700 time -= (tm->tm_hour * USECS_PER_HOUR);
701 tm->tm_min = (time / USECS_PER_MINUTE);
702 time -= (tm->tm_min * USECS_PER_MINUTE);
703 tm->tm_sec = (time / USECS_PER_SEC);
704 *fsec = (time - (tm->tm_sec * USECS_PER_SEC));
706 TMODULO(time, tm->tm_mday, (double)SECS_PER_DAY);
707 TMODULO(time, tm->tm_hour, 3600e0);
708 TMODULO(time, tm->tm_min, 60e0);
709 TMODULO(time, tm->tm_sec, 1e0);
714 } /* interval2tm() */
717 tm2interval(struct tm * tm, fsec_t fsec, interval *span)
719 span->month = tm->tm_year * 12 + tm->tm_mon;
720 #ifdef HAVE_INT64_TIMESTAMP
721 span->time = (((((((tm->tm_mday * INT64CONST(24)) +
722 tm->tm_hour) * INT64CONST(60)) +
723 tm->tm_min) * INT64CONST(60)) +
724 tm->tm_sec) * USECS_PER_SEC) + fsec;
726 span->time = (((((tm->tm_mday * 24.0) +
727 tm->tm_hour) * 60.0) +
728 tm->tm_min) * 60.0) +
730 span->time = JROUND(span->time + fsec);
734 } /* tm2interval() */
737 PGTYPESinterval_from_asc(char *str, char **endptr)
739 interval *result = NULL;
745 char *field[MAXDATEFIELDS];
746 int ftype[MAXDATEFIELDS];
747 char lowstr[MAXDATELEN + MAXDATEFIELDS];
749 char **ptr = (endptr != NULL) ? endptr : &realptr;
759 if (strlen(str) >= sizeof(lowstr))
761 errno = PGTYPES_INTVL_BAD_INTERVAL;
765 if (ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf, ptr) != 0 ||
766 DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0)
768 errno = PGTYPES_INTVL_BAD_INTERVAL;
772 result = (interval *) pgtypes_alloc(sizeof(interval));
776 if (dtype != DTK_DELTA)
778 errno = PGTYPES_INTVL_BAD_INTERVAL;
783 if (tm2interval(tm, fsec, result) != 0)
785 errno = PGTYPES_INTVL_BAD_INTERVAL;
794 PGTYPESinterval_to_asc(interval *span)
799 char buf[MAXDATELEN + 1];
802 if (interval2tm(*span, tm, &fsec) != 0)
804 errno = PGTYPES_INTVL_BAD_INTERVAL;
808 if (EncodeInterval(tm, fsec, DateStyle, buf) != 0)
810 errno = PGTYPES_INTVL_BAD_INTERVAL;
814 return pgtypes_strdup(buf);
818 PGTYPESinterval_copy(interval *intvlsrc, interval *intrcldest)
820 intrcldest->time = intvlsrc->time;
821 intrcldest->month = intvlsrc->month;