1/* src/interfaces/ecpg/pgtypeslib/interval.c */
2
3#include "postgres_fe.h"
4#include <time.h>
5#include <math.h>
6#include <limits.h>
7
8#ifdef __FAST_MATH__
9#error -ffast-math is known to break this code
10#endif
11
12#include "common/string.h"
13
14#include "pgtypeslib_extern.h"
15#include "dt.h"
16#include "pgtypes_error.h"
17#include "pgtypes_interval.h"
18
19/* copy&pasted from .../src/backend/utils/adt/datetime.c
20 * and changesd struct pg_tm to struct tm
21 */
22static void
23AdjustFractSeconds(double frac, struct /* pg_ */ tm *tm, fsec_t *fsec, int scale)
24{
25 int sec;
26
27 if (frac == 0)
28 return;
29 frac *= scale;
30 sec = (int) frac;
31 tm->tm_sec += sec;
32 frac -= sec;
33 *fsec += rint(frac * 1000000);
34}
35
36
37/* copy&pasted from .../src/backend/utils/adt/datetime.c
38 * and changesd struct pg_tm to struct tm
39 */
40static void
41AdjustFractDays(double frac, struct /* pg_ */ tm *tm, fsec_t *fsec, int scale)
42{
43 int extra_days;
44
45 if (frac == 0)
46 return;
47 frac *= scale;
48 extra_days = (int) frac;
49 tm->tm_mday += extra_days;
50 frac -= extra_days;
51 AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
52}
53
54/* copy&pasted from .../src/backend/utils/adt/datetime.c */
55static int
56ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart)
57{
58 double val;
59
60 if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
61 return DTERR_BAD_FORMAT;
62 errno = 0;
63 val = strtod(str, endptr);
64 /* did we not see anything that looks like a double? */
65 if (*endptr == str || errno != 0)
66 return DTERR_BAD_FORMAT;
67 /* watch out for overflow */
68 if (val < INT_MIN || val > INT_MAX)
69 return DTERR_FIELD_OVERFLOW;
70 /* be very sure we truncate towards zero (cf dtrunc()) */
71 if (val >= 0)
72 *ipart = (int) floor(val);
73 else
74 *ipart = (int) -floor(-val);
75 *fpart = val - *ipart;
76 return 0;
77}
78
79/* copy&pasted from .../src/backend/utils/adt/datetime.c */
80static int
81ISO8601IntegerWidth(const char *fieldstart)
82{
83 /* We might have had a leading '-' */
84 if (*fieldstart == '-')
85 fieldstart++;
86 return strspn(fieldstart, "0123456789");
87}
88
89
90/* copy&pasted from .../src/backend/utils/adt/datetime.c
91 * and changesd struct pg_tm to struct tm
92 */
93static inline void
94ClearPgTm(struct /* pg_ */ tm *tm, fsec_t *fsec)
95{
96 tm->tm_year = 0;
97 tm->tm_mon = 0;
98 tm->tm_mday = 0;
99 tm->tm_hour = 0;
100 tm->tm_min = 0;
101 tm->tm_sec = 0;
102 *fsec = 0;
103}
104
105/* copy&pasted from .../src/backend/utils/adt/datetime.c
106 *
107 * * changesd struct pg_tm to struct tm
108 *
109 * * Made the function static
110 */
111static int
112DecodeISO8601Interval(char *str,
113 int *dtype, struct /* pg_ */ tm *tm, fsec_t *fsec)
114{
115 bool datepart = true;
116 bool havefield = false;
117
118 *dtype = DTK_DELTA;
119 ClearPgTm(tm, fsec);
120
121 if (strlen(str) < 2 || str[0] != 'P')
122 return DTERR_BAD_FORMAT;
123
124 str++;
125 while (*str)
126 {
127 char *fieldstart;
128 int val;
129 double fval;
130 char unit;
131 int dterr;
132
133 if (*str == 'T') /* T indicates the beginning of the time part */
134 {
135 datepart = false;
136 havefield = false;
137 str++;
138 continue;
139 }
140
141 fieldstart = str;
142 dterr = ParseISO8601Number(str, &str, &val, &fval);
143 if (dterr)
144 return dterr;
145
146 /*
147 * Note: we could step off the end of the string here. Code below
148 * *must* exit the loop if unit == '\0'.
149 */
150 unit = *str++;
151
152 if (datepart)
153 {
154 switch (unit) /* before T: Y M W D */
155 {
156 case 'Y':
157 tm->tm_year += val;
158 tm->tm_mon += (fval * 12);
159 break;
160 case 'M':
161 tm->tm_mon += val;
162 AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
163 break;
164 case 'W':
165 tm->tm_mday += val * 7;
166 AdjustFractDays(fval, tm, fsec, 7);
167 break;
168 case 'D':
169 tm->tm_mday += val;
170 AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
171 break;
172 case 'T': /* ISO 8601 4.4.3.3 Alternative Format / Basic */
173 case '\0':
174 if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
175 {
176 tm->tm_year += val / 10000;
177 tm->tm_mon += (val / 100) % 100;
178 tm->tm_mday += val % 100;
179 AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
180 if (unit == '\0')
181 return 0;
182 datepart = false;
183 havefield = false;
184 continue;
185 }
186 /* Else fall through to extended alternative format */
187 /* FALLTHROUGH */
188 case '-': /* ISO 8601 4.4.3.3 Alternative Format,
189 * Extended */
190 if (havefield)
191 return DTERR_BAD_FORMAT;
192
193 tm->tm_year += val;
194 tm->tm_mon += (fval * 12);
195 if (unit == '\0')
196 return 0;
197 if (unit == 'T')
198 {
199 datepart = false;
200 havefield = false;
201 continue;
202 }
203
204 dterr = ParseISO8601Number(str, &str, &val, &fval);
205 if (dterr)
206 return dterr;
207 tm->tm_mon += val;
208 AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
209 if (*str == '\0')
210 return 0;
211 if (*str == 'T')
212 {
213 datepart = false;
214 havefield = false;
215 continue;
216 }
217 if (*str != '-')
218 return DTERR_BAD_FORMAT;
219 str++;
220
221 dterr = ParseISO8601Number(str, &str, &val, &fval);
222 if (dterr)
223 return dterr;
224 tm->tm_mday += val;
225 AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
226 if (*str == '\0')
227 return 0;
228 if (*str == 'T')
229 {
230 datepart = false;
231 havefield = false;
232 continue;
233 }
234 return DTERR_BAD_FORMAT;
235 default:
236 /* not a valid date unit suffix */
237 return DTERR_BAD_FORMAT;
238 }
239 }
240 else
241 {
242 switch (unit) /* after T: H M S */
243 {
244 case 'H':
245 tm->tm_hour += val;
246 AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
247 break;
248 case 'M':
249 tm->tm_min += val;
250 AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
251 break;
252 case 'S':
253 tm->tm_sec += val;
254 AdjustFractSeconds(fval, tm, fsec, 1);
255 break;
256 case '\0': /* ISO 8601 4.4.3.3 Alternative Format */
257 if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
258 {
259 tm->tm_hour += val / 10000;
260 tm->tm_min += (val / 100) % 100;
261 tm->tm_sec += val % 100;
262 AdjustFractSeconds(fval, tm, fsec, 1);
263 return 0;
264 }
265 /* Else fall through to extended alternative format */
266 /* FALLTHROUGH */
267 case ':': /* ISO 8601 4.4.3.3 Alternative Format,
268 * Extended */
269 if (havefield)
270 return DTERR_BAD_FORMAT;
271
272 tm->tm_hour += val;
273 AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
274 if (unit == '\0')
275 return 0;
276
277 dterr = ParseISO8601Number(str, &str, &val, &fval);
278 if (dterr)
279 return dterr;
280 tm->tm_min += val;
281 AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
282 if (*str == '\0')
283 return 0;
284 if (*str != ':')
285 return DTERR_BAD_FORMAT;
286 str++;
287
288 dterr = ParseISO8601Number(str, &str, &val, &fval);
289 if (dterr)
290 return dterr;
291 tm->tm_sec += val;
292 AdjustFractSeconds(fval, tm, fsec, 1);
293 if (*str == '\0')
294 return 0;
295 return DTERR_BAD_FORMAT;
296
297 default:
298 /* not a valid time unit suffix */
299 return DTERR_BAD_FORMAT;
300 }
301 }
302
303 havefield = true;
304 }
305
306 return 0;
307}
308
309
310
311/* copy&pasted from .../src/backend/utils/adt/datetime.c
312 * with 3 exceptions
313 *
314 * * changesd struct pg_tm to struct tm
315 *
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;
320 *
321 * * ECPG seems not to have a global IntervalStyle
322 * so added
323 * int IntervalStyle = INTSTYLE_POSTGRES;
324 */
325int
326DecodeInterval(char **field, int *ftype, int nf, /* int range, */
327 int *dtype, struct /* pg_ */ tm *tm, fsec_t *fsec)
328{
329 int IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
330 int range = INTERVAL_FULL_RANGE;
331 bool is_before = false;
332 char *cp;
333 int fmask = 0,
334 tmask,
335 type;
336 int i;
337 int dterr;
338 int val;
339 double fval;
340
341 *dtype = DTK_DELTA;
342 type = IGNORE_DTF;
343 ClearPgTm(tm, fsec);
344
345 /* read through list backwards to pick up units before values */
346 for (i = nf - 1; i >= 0; i--)
347 {
348 switch (ftype[i])
349 {
350 case DTK_TIME:
351 dterr = DecodeTime(field[i], /* range, */
352 &tmask, tm, fsec);
353 if (dterr)
354 return dterr;
355 type = DTK_DAY;
356 break;
357
358 case DTK_TZ:
359
360 /*
361 * Timezone is a token with a leading sign character and at
362 * least one digit; there could be ':', '.', '-' embedded in
363 * it as well.
364 */
365 Assert(*field[i] == '-' || *field[i] == '+');
366
367 /*
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.
371 */
372 if (strchr(field[i] + 1, ':') != NULL &&
373 DecodeTime(field[i] + 1, /* INTERVAL_FULL_RANGE, */
374 &tmask, tm, fsec) == 0)
375 {
376 if (*field[i] == '-')
377 {
378 /* flip the sign on all fields */
379 tm->tm_hour = -tm->tm_hour;
380 tm->tm_min = -tm->tm_min;
381 tm->tm_sec = -tm->tm_sec;
382 *fsec = -(*fsec);
383 }
384
385 /*
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.
389 */
390 type = DTK_DAY;
391 tmask = DTK_M(TZ);
392 break;
393 }
394 /* FALL THROUGH */
395
396 case DTK_DATE:
397 case DTK_NUMBER:
398 if (type == IGNORE_DTF)
399 {
400 /* use typmod to decide what rightmost field is */
401 switch (range)
402 {
403 case INTERVAL_MASK(YEAR):
404 type = DTK_YEAR;
405 break;
406 case INTERVAL_MASK(MONTH):
407 case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
408 type = DTK_MONTH;
409 break;
410 case INTERVAL_MASK(DAY):
411 type = DTK_DAY;
412 break;
413 case INTERVAL_MASK(HOUR):
414 case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
415 case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
416 case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
417 type = DTK_HOUR;
418 break;
419 case INTERVAL_MASK(MINUTE):
420 case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
421 type = DTK_MINUTE;
422 break;
423 case INTERVAL_MASK(SECOND):
424 case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
425 case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
426 type = DTK_SECOND;
427 break;
428 default:
429 type = DTK_SECOND;
430 break;
431 }
432 }
433
434 errno = 0;
435 val = strtoint(field[i], &cp, 10);
436 if (errno == ERANGE)
437 return DTERR_FIELD_OVERFLOW;
438
439 if (*cp == '-')
440 {
441 /* SQL "years-months" syntax */
442 int val2;
443
444 val2 = strtoint(cp + 1, &cp, 10);
445 if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR)
446 return DTERR_FIELD_OVERFLOW;
447 if (*cp != '\0')
448 return DTERR_BAD_FORMAT;
449 type = DTK_MONTH;
450 if (*field[i] == '-')
451 val2 = -val2;
452 val = val * MONTHS_PER_YEAR + val2;
453 fval = 0;
454 }
455 else if (*cp == '.')
456 {
457 errno = 0;
458 fval = strtod(cp, &cp);
459 if (*cp != '\0' || errno != 0)
460 return DTERR_BAD_FORMAT;
461
462 if (*field[i] == '-')
463 fval = -fval;
464 }
465 else if (*cp == '\0')
466 fval = 0;
467 else
468 return DTERR_BAD_FORMAT;
469
470 tmask = 0; /* DTK_M(type); */
471
472 switch (type)
473 {
474 case DTK_MICROSEC:
475 *fsec += rint(val + fval);
476 tmask = DTK_M(MICROSECOND);
477 break;
478
479 case DTK_MILLISEC:
480 *fsec += rint((val + fval) * 1000);
481 tmask = DTK_M(MILLISECOND);
482 break;
483
484 case DTK_SECOND:
485 tm->tm_sec += val;
486 *fsec += rint(fval * 1000000);
487
488 /*
489 * If any subseconds were specified, consider this
490 * microsecond and millisecond input as well.
491 */
492 if (fval == 0)
493 tmask = DTK_M(SECOND);
494 else
495 tmask = DTK_ALL_SECS_M;
496 break;
497
498 case DTK_MINUTE:
499 tm->tm_min += val;
500 AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
501 tmask = DTK_M(MINUTE);
502 break;
503
504 case DTK_HOUR:
505 tm->tm_hour += val;
506 AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
507 tmask = DTK_M(HOUR);
508 type = DTK_DAY;
509 break;
510
511 case DTK_DAY:
512 tm->tm_mday += val;
513 AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
514 tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
515 break;
516
517 case DTK_WEEK:
518 tm->tm_mday += val * 7;
519 AdjustFractDays(fval, tm, fsec, 7);
520 tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
521 break;
522
523 case DTK_MONTH:
524 tm->tm_mon += val;
525 AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
526 tmask = DTK_M(MONTH);
527 break;
528
529 case DTK_YEAR:
530 tm->tm_year += val;
531 if (fval != 0)
532 tm->tm_mon += fval * MONTHS_PER_YEAR;
533 tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
534 break;
535
536 case DTK_DECADE:
537 tm->tm_year += val * 10;
538 if (fval != 0)
539 tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
540 tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
541 break;
542
543 case DTK_CENTURY:
544 tm->tm_year += val * 100;
545 if (fval != 0)
546 tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
547 tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
548 break;
549
550 case DTK_MILLENNIUM:
551 tm->tm_year += val * 1000;
552 if (fval != 0)
553 tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
554 tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
555 break;
556
557 default:
558 return DTERR_BAD_FORMAT;
559 }
560 break;
561
562 case DTK_STRING:
563 case DTK_SPECIAL:
564 type = DecodeUnits(i, field[i], &val);
565 if (type == IGNORE_DTF)
566 continue;
567
568 tmask = 0; /* DTK_M(type); */
569 switch (type)
570 {
571 case UNITS:
572 type = val;
573 break;
574
575 case AGO:
576 is_before = true;
577 type = val;
578 break;
579
580 case RESERV:
581 tmask = (DTK_DATE_M | DTK_TIME_M);
582 *dtype = val;
583 break;
584
585 default:
586 return DTERR_BAD_FORMAT;
587 }
588 break;
589
590 default:
591 return DTERR_BAD_FORMAT;
592 }
593
594 if (tmask & fmask)
595 return DTERR_BAD_FORMAT;
596 fmask |= tmask;
597 }
598
599 /* ensure that at least one time field has been found */
600 if (fmask == 0)
601 return DTERR_BAD_FORMAT;
602
603 /* ensure fractional seconds are fractional */
604 if (*fsec != 0)
605 {
606 int sec;
607
608 sec = *fsec / USECS_PER_SEC;
609 *fsec -= sec * USECS_PER_SEC;
610 tm->tm_sec += sec;
611 }
612
613 /*----------
614 * The SQL standard defines the interval literal
615 * '-1 1:00:00'
616 * to mean "negative 1 days and negative 1 hours", while Postgres
617 * traditionally treats this as meaning "negative 1 days and positive
618 * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign
619 * to all fields if there are no other explicit signs.
620 *
621 * We leave the signs alone if there are additional explicit signs.
622 * This protects us against misinterpreting postgres-style dump output,
623 * since the postgres-style output code has always put an explicit sign on
624 * all fields following a negative field. But note that SQL-spec output
625 * is ambiguous and can be misinterpreted on load! (So it's best practice
626 * to dump in postgres style, not SQL style.)
627 *----------
628 */
629 if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
630 {
631 /* Check for additional explicit signs */
632 bool more_signs = false;
633
634 for (i = 1; i < nf; i++)
635 {
636 if (*field[i] == '-' || *field[i] == '+')
637 {
638 more_signs = true;
639 break;
640 }
641 }
642
643 if (!more_signs)
644 {
645 /*
646 * Rather than re-determining which field was field[0], just force
647 * 'em all negative.
648 */
649 if (*fsec > 0)
650 *fsec = -(*fsec);
651 if (tm->tm_sec > 0)
652 tm->tm_sec = -tm->tm_sec;
653 if (tm->tm_min > 0)
654 tm->tm_min = -tm->tm_min;
655 if (tm->tm_hour > 0)
656 tm->tm_hour = -tm->tm_hour;
657 if (tm->tm_mday > 0)
658 tm->tm_mday = -tm->tm_mday;
659 if (tm->tm_mon > 0)
660 tm->tm_mon = -tm->tm_mon;
661 if (tm->tm_year > 0)
662 tm->tm_year = -tm->tm_year;
663 }
664 }
665
666 /* finally, AGO negates everything */
667 if (is_before)
668 {
669 *fsec = -(*fsec);
670 tm->tm_sec = -tm->tm_sec;
671 tm->tm_min = -tm->tm_min;
672 tm->tm_hour = -tm->tm_hour;
673 tm->tm_mday = -tm->tm_mday;
674 tm->tm_mon = -tm->tm_mon;
675 tm->tm_year = -tm->tm_year;
676 }
677
678 return 0;
679}
680
681
682/* copy&pasted from .../src/backend/utils/adt/datetime.c */
683static char *
684AddVerboseIntPart(char *cp, int value, const char *units,
685 bool *is_zero, bool *is_before)
686{
687 if (value == 0)
688 return cp;
689 /* first nonzero value sets is_before */
690 if (*is_zero)
691 {
692 *is_before = (value < 0);
693 value = abs(value);
694 }
695 else if (*is_before)
696 value = -value;
697 sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
698 *is_zero = false;
699 return cp + strlen(cp);
700}
701
702/* copy&pasted from .../src/backend/utils/adt/datetime.c */
703static char *
704AddPostgresIntPart(char *cp, int value, const char *units,
705 bool *is_zero, bool *is_before)
706{
707 if (value == 0)
708 return cp;
709 sprintf(cp, "%s%s%d %s%s",
710 (!*is_zero) ? " " : "",
711 (*is_before && value > 0) ? "+" : "",
712 value,
713 units,
714 (value != 1) ? "s" : "");
715
716 /*
717 * Each nonzero field sets is_before for (only) the next one. This is a
718 * tad bizarre but it's how it worked before...
719 */
720 *is_before = (value < 0);
721 *is_zero = false;
722 return cp + strlen(cp);
723}
724
725/* copy&pasted from .../src/backend/utils/adt/datetime.c */
726static char *
727AddISO8601IntPart(char *cp, int value, char units)
728{
729 if (value == 0)
730 return cp;
731 sprintf(cp, "%d%c", value, units);
732 return cp + strlen(cp);
733}
734
735/* copy&pasted from .../src/backend/utils/adt/datetime.c */
736static void
737AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
738{
739 if (fsec == 0)
740 {
741 if (fillzeros)
742 sprintf(cp, "%02d", abs(sec));
743 else
744 sprintf(cp, "%d", abs(sec));
745 }
746 else
747 {
748 if (fillzeros)
749 sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec));
750 else
751 sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec));
752 TrimTrailingZeros(cp);
753 }
754}
755
756
757/* copy&pasted from .../src/backend/utils/adt/datetime.c
758 *
759 * Change pg_tm to tm
760 */
761
762void
763EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
764{
765 char *cp = str;
766 int year = tm->tm_year;
767 int mon = tm->tm_mon;
768 int mday = tm->tm_mday;
769 int hour = tm->tm_hour;
770 int min = tm->tm_min;
771 int sec = tm->tm_sec;
772 bool is_before = false;
773 bool is_zero = true;
774
775 /*
776 * The sign of year and month are guaranteed to match, since they are
777 * stored internally as "month". But we'll need to check for is_before and
778 * is_zero when determining the signs of day and hour/minute/seconds
779 * fields.
780 */
781 switch (style)
782 {
783 /* SQL Standard interval format */
784 case INTSTYLE_SQL_STANDARD:
785 {
786 bool has_negative = year < 0 || mon < 0 ||
787 mday < 0 || hour < 0 ||
788 min < 0 || sec < 0 || fsec < 0;
789 bool has_positive = year > 0 || mon > 0 ||
790 mday > 0 || hour > 0 ||
791 min > 0 || sec > 0 || fsec > 0;
792 bool has_year_month = year != 0 || mon != 0;
793 bool has_day_time = mday != 0 || hour != 0 ||
794 min != 0 || sec != 0 || fsec != 0;
795 bool has_day = mday != 0;
796 bool sql_standard_value = !(has_negative && has_positive) &&
797 !(has_year_month && has_day_time);
798
799 /*
800 * SQL Standard wants only 1 "<sign>" preceding the whole
801 * interval ... but can't do that if mixed signs.
802 */
803 if (has_negative && sql_standard_value)
804 {
805 *cp++ = '-';
806 year = -year;
807 mon = -mon;
808 mday = -mday;
809 hour = -hour;
810 min = -min;
811 sec = -sec;
812 fsec = -fsec;
813 }
814
815 if (!has_negative && !has_positive)
816 {
817 sprintf(cp, "0");
818 }
819 else if (!sql_standard_value)
820 {
821 /*
822 * For non sql-standard interval values, force outputting
823 * the signs to avoid ambiguities with intervals with
824 * mixed sign components.
825 */
826 char year_sign = (year < 0 || mon < 0) ? '-' : '+';
827 char day_sign = (mday < 0) ? '-' : '+';
828 char sec_sign = (hour < 0 || min < 0 ||
829 sec < 0 || fsec < 0) ? '-' : '+';
830
831 sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
832 year_sign, abs(year), abs(mon),
833 day_sign, abs(mday),
834 sec_sign, abs(hour), abs(min));
835 cp += strlen(cp);
836 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
837 }
838 else if (has_year_month)
839 {
840 sprintf(cp, "%d-%d", year, mon);
841 }
842 else if (has_day)
843 {
844 sprintf(cp, "%d %d:%02d:", mday, hour, min);
845 cp += strlen(cp);
846 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
847 }
848 else
849 {
850 sprintf(cp, "%d:%02d:", hour, min);
851 cp += strlen(cp);
852 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
853 }
854 }
855 break;
856
857 /* ISO 8601 "time-intervals by duration only" */
858 case INTSTYLE_ISO_8601:
859 /* special-case zero to avoid printing nothing */
860 if (year == 0 && mon == 0 && mday == 0 &&
861 hour == 0 && min == 0 && sec == 0 && fsec == 0)
862 {
863 sprintf(cp, "PT0S");
864 break;
865 }
866 *cp++ = 'P';
867 cp = AddISO8601IntPart(cp, year, 'Y');
868 cp = AddISO8601IntPart(cp, mon, 'M');
869 cp = AddISO8601IntPart(cp, mday, 'D');
870 if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
871 *cp++ = 'T';
872 cp = AddISO8601IntPart(cp, hour, 'H');
873 cp = AddISO8601IntPart(cp, min, 'M');
874 if (sec != 0 || fsec != 0)
875 {
876 if (sec < 0 || fsec < 0)
877 *cp++ = '-';
878 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
879 cp += strlen(cp);
880 *cp++ = 'S';
881 *cp = '\0';
882 }
883 break;
884
885 /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
886 case INTSTYLE_POSTGRES:
887 cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before);
888 cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
889 cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
890 if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
891 {
892 bool minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
893
894 sprintf(cp, "%s%s%02d:%02d:",
895 is_zero ? "" : " ",
896 (minus ? "-" : (is_before ? "+" : "")),
897 abs(hour), abs(min));
898 cp += strlen(cp);
899 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
900 }
901 break;
902
903 /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */
904 case INTSTYLE_POSTGRES_VERBOSE:
905 default:
906 strcpy(cp, "@");
907 cp++;
908 cp = AddVerboseIntPart(cp, year, "year", &is_zero, &is_before);
909 cp = AddVerboseIntPart(cp, mon, "mon", &is_zero, &is_before);
910 cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
911 cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
912 cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before);
913 if (sec != 0 || fsec != 0)
914 {
915 *cp++ = ' ';
916 if (sec < 0 || (sec == 0 && fsec < 0))
917 {
918 if (is_zero)
919 is_before = true;
920 else if (!is_before)
921 *cp++ = '-';
922 }
923 else if (is_before)
924 *cp++ = '-';
925 AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
926 cp += strlen(cp);
927 sprintf(cp, " sec%s",
928 (abs(sec) != 1 || fsec != 0) ? "s" : "");
929 is_zero = false;
930 }
931 /* identically zero? then put in a unitless zero... */
932 if (is_zero)
933 strcat(cp, " 0");
934 if (is_before)
935 strcat(cp, " ago");
936 break;
937 }
938}
939
940
941/* interval2tm()
942 * Convert an interval data type to a tm structure.
943 */
944static int
945interval2tm(interval span, struct tm *tm, fsec_t *fsec)
946{
947 int64 time;
948
949 if (span.month != 0)
950 {
951 tm->tm_year = span.month / MONTHS_PER_YEAR;
952 tm->tm_mon = span.month % MONTHS_PER_YEAR;
953
954 }
955 else
956 {
957 tm->tm_year = 0;
958 tm->tm_mon = 0;
959 }
960
961 time = span.time;
962
963 tm->tm_mday = time / USECS_PER_DAY;
964 time -= tm->tm_mday * USECS_PER_DAY;
965 tm->tm_hour = time / USECS_PER_HOUR;
966 time -= tm->tm_hour * USECS_PER_HOUR;
967 tm->tm_min = time / USECS_PER_MINUTE;
968 time -= tm->tm_min * USECS_PER_MINUTE;
969 tm->tm_sec = time / USECS_PER_SEC;
970 *fsec = time - (tm->tm_sec * USECS_PER_SEC);
971
972 return 0;
973} /* interval2tm() */
974
975static int
976tm2interval(struct tm *tm, fsec_t fsec, interval * span)
977{
978 if ((double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon > INT_MAX ||
979 (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon < INT_MIN)
980 return -1;
981 span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
982 span->time = (((((((tm->tm_mday * INT64CONST(24)) +
983 tm->tm_hour) * INT64CONST(60)) +
984 tm->tm_min) * INT64CONST(60)) +
985 tm->tm_sec) * USECS_PER_SEC) + fsec;
986
987 return 0;
988} /* tm2interval() */
989
990interval *
991PGTYPESinterval_new(void)
992{
993 interval *result;
994
995 result = (interval *) pgtypes_alloc(sizeof(interval));
996 /* result can be NULL if we run out of memory */
997 return result;
998}
999
1000void
1001PGTYPESinterval_free(interval * intvl)
1002{
1003 free(intvl);
1004}
1005
1006interval *
1007PGTYPESinterval_from_asc(char *str, char **endptr)
1008{
1009 interval *result = NULL;
1010 fsec_t fsec;
1011 struct tm tt,
1012 *tm = &tt;
1013 int dtype;
1014 int nf;
1015 char *field[MAXDATEFIELDS];
1016 int ftype[MAXDATEFIELDS];
1017 char lowstr[MAXDATELEN + MAXDATEFIELDS];
1018 char *realptr;
1019 char **ptr = (endptr != NULL) ? endptr : &realptr;
1020
1021 tm->tm_year = 0;
1022 tm->tm_mon = 0;
1023 tm->tm_mday = 0;
1024 tm->tm_hour = 0;
1025 tm->tm_min = 0;
1026 tm->tm_sec = 0;
1027 fsec = 0;
1028
1029 if (strlen(str) > MAXDATELEN)
1030 {
1031 errno = PGTYPES_INTVL_BAD_INTERVAL;
1032 return NULL;
1033 }
1034
1035 if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
1036 (DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0 &&
1037 DecodeISO8601Interval(str, &dtype, tm, &fsec) != 0))
1038 {
1039 errno = PGTYPES_INTVL_BAD_INTERVAL;
1040 return NULL;
1041 }
1042
1043 result = (interval *) pgtypes_alloc(sizeof(interval));
1044 if (!result)
1045 return NULL;
1046
1047 if (dtype != DTK_DELTA)
1048 {
1049 errno = PGTYPES_INTVL_BAD_INTERVAL;
1050 free(result);
1051 return NULL;
1052 }
1053
1054 if (tm2interval(tm, fsec, result) != 0)
1055 {
1056 errno = PGTYPES_INTVL_BAD_INTERVAL;
1057 free(result);
1058 return NULL;
1059 }
1060
1061 errno = 0;
1062 return result;
1063}
1064
1065char *
1066PGTYPESinterval_to_asc(interval * span)
1067{
1068 struct tm tt,
1069 *tm = &tt;
1070 fsec_t fsec;
1071 char buf[MAXDATELEN + 1];
1072 int IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
1073
1074 if (interval2tm(*span, tm, &fsec) != 0)
1075 {
1076 errno = PGTYPES_INTVL_BAD_INTERVAL;
1077 return NULL;
1078 }
1079
1080 EncodeInterval(tm, fsec, IntervalStyle, buf);
1081
1082 return pgtypes_strdup(buf);
1083}
1084
1085int
1086PGTYPESinterval_copy(interval * intvlsrc, interval * intvldest)
1087{
1088 intvldest->time = intvlsrc->time;
1089 intvldest->month = intvlsrc->month;
1090
1091 return 0;
1092}
1093