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