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