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