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