1 /*
2  * src/interfaces/ecpg/pgtypeslib/timestamp.c
3  */
4 #include "postgres_fe.h"
5 
6 #include <time.h>
7 #include <float.h>
8 #include <limits.h>
9 #include <math.h>
10 
11 #ifdef __FAST_MATH__
12 #error -ffast-math is known to break this code
13 #endif
14 
15 #include "extern.h"
16 #include "dt.h"
17 #include "pgtypes_timestamp.h"
18 #include "pgtypes_date.h"
19 
20 
21 static int64
time2t(const int hour,const int min,const int sec,const fsec_t fsec)22 time2t(const int hour, const int min, const int sec, const fsec_t fsec)
23 {
24 	return (((((hour * MINS_PER_HOUR) + min) * SECS_PER_MINUTE) + sec) * USECS_PER_SEC) + fsec;
25 }								/* time2t() */
26 
27 static timestamp
dt2local(timestamp dt,int tz)28 dt2local(timestamp dt, int tz)
29 {
30 	dt -= (tz * USECS_PER_SEC);
31 	return dt;
32 }								/* dt2local() */
33 
34 /* tm2timestamp()
35  * Convert a tm structure to a timestamp data type.
36  * Note that year is _not_ 1900-based, but is an explicit full value.
37  * Also, month is one-based, _not_ zero-based.
38  *
39  * Returns -1 on failure (overflow).
40  */
41 int
tm2timestamp(struct tm * tm,fsec_t fsec,int * tzp,timestamp * result)42 tm2timestamp(struct tm *tm, fsec_t fsec, int *tzp, timestamp * result)
43 {
44 	int			dDate;
45 	int64		time;
46 
47 	/* Prevent overflow in Julian-day routines */
48 	if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
49 		return -1;
50 
51 	dDate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1);
52 	time = time2t(tm->tm_hour, tm->tm_min, tm->tm_sec, fsec);
53 	*result = (dDate * USECS_PER_DAY) + time;
54 	/* check for major overflow */
55 	if ((*result - time) / USECS_PER_DAY != dDate)
56 		return -1;
57 	/* check for just-barely overflow (okay except time-of-day wraps) */
58 	/* caution: we want to allow 1999-12-31 24:00:00 */
59 	if ((*result < 0 && dDate > 0) ||
60 		(*result > 0 && dDate < -1))
61 		return -1;
62 	if (tzp != NULL)
63 		*result = dt2local(*result, -(*tzp));
64 
65 	/* final range check catches just-out-of-range timestamps */
66 	if (!IS_VALID_TIMESTAMP(*result))
67 		return -1;
68 
69 	return 0;
70 }								/* tm2timestamp() */
71 
72 static timestamp
SetEpochTimestamp(void)73 SetEpochTimestamp(void)
74 {
75 	int64		noresult = 0;
76 	timestamp	dt;
77 	struct tm	tt,
78 			   *tm = &tt;
79 
80 	if (GetEpochTime(tm) < 0)
81 		return noresult;
82 
83 	tm2timestamp(tm, 0, NULL, &dt);
84 	return dt;
85 }								/* SetEpochTimestamp() */
86 
87 /* timestamp2tm()
88  * Convert timestamp data type to POSIX time structure.
89  * Note that year is _not_ 1900-based, but is an explicit full value.
90  * Also, month is one-based, _not_ zero-based.
91  * Returns:
92  *	 0 on success
93  *	-1 on out of range
94  *
95  * For dates within the system-supported time_t range, convert to the
96  *	local time zone. If out of this range, leave as GMT. - tgl 97/05/27
97  */
98 static int
timestamp2tm(timestamp dt,int * tzp,struct tm * tm,fsec_t * fsec,const char ** tzn)99 timestamp2tm(timestamp dt, int *tzp, struct tm *tm, fsec_t *fsec, const char **tzn)
100 {
101 	int64		dDate,
102 				date0;
103 	int64		time;
104 #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
105 	time_t		utime;
106 	struct tm  *tx;
107 #endif
108 
109 	date0 = date2j(2000, 1, 1);
110 
111 	time = dt;
112 	TMODULO(time, dDate, USECS_PER_DAY);
113 
114 	if (time < INT64CONST(0))
115 	{
116 		time += USECS_PER_DAY;
117 		dDate -= 1;
118 	}
119 
120 	/* add offset to go from J2000 back to standard Julian date */
121 	dDate += date0;
122 
123 	/* Julian day routine does not work for negative Julian days */
124 	if (dDate < 0 || dDate > (timestamp) INT_MAX)
125 		return -1;
126 
127 	j2date((int) dDate, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
128 	dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
129 
130 	if (tzp != NULL)
131 	{
132 		/*
133 		 * Does this fall within the capabilities of the localtime()
134 		 * interface? Then use this to rotate to the local time zone.
135 		 */
136 		if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
137 		{
138 #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
139 
140 			utime = dt / USECS_PER_SEC +
141 				((date0 - date2j(1970, 1, 1)) * INT64CONST(86400));
142 
143 			tx = localtime(&utime);
144 			tm->tm_year = tx->tm_year + 1900;
145 			tm->tm_mon = tx->tm_mon + 1;
146 			tm->tm_mday = tx->tm_mday;
147 			tm->tm_hour = tx->tm_hour;
148 			tm->tm_min = tx->tm_min;
149 			tm->tm_isdst = tx->tm_isdst;
150 
151 #if defined(HAVE_TM_ZONE)
152 			tm->tm_gmtoff = tx->tm_gmtoff;
153 			tm->tm_zone = tx->tm_zone;
154 
155 			*tzp = -tm->tm_gmtoff;	/* tm_gmtoff is Sun/DEC-ism */
156 			if (tzn != NULL)
157 				*tzn = tm->tm_zone;
158 #elif defined(HAVE_INT_TIMEZONE)
159 			*tzp = (tm->tm_isdst > 0) ? TIMEZONE_GLOBAL - SECS_PER_HOUR : TIMEZONE_GLOBAL;
160 			if (tzn != NULL)
161 				*tzn = TZNAME_GLOBAL[(tm->tm_isdst > 0)];
162 #endif
163 #else							/* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
164 			*tzp = 0;
165 			/* Mark this as *no* time zone available */
166 			tm->tm_isdst = -1;
167 			if (tzn != NULL)
168 				*tzn = NULL;
169 #endif
170 		}
171 		else
172 		{
173 			*tzp = 0;
174 			/* Mark this as *no* time zone available */
175 			tm->tm_isdst = -1;
176 			if (tzn != NULL)
177 				*tzn = NULL;
178 		}
179 	}
180 	else
181 	{
182 		tm->tm_isdst = -1;
183 		if (tzn != NULL)
184 			*tzn = NULL;
185 	}
186 
187 	tm->tm_yday = dDate - date2j(tm->tm_year, 1, 1) + 1;
188 
189 	return 0;
190 }								/* timestamp2tm() */
191 
192 /* EncodeSpecialTimestamp()
193  *	* Convert reserved timestamp data type to string.
194  *	 */
195 static void
EncodeSpecialTimestamp(timestamp dt,char * str)196 EncodeSpecialTimestamp(timestamp dt, char *str)
197 {
198 	if (TIMESTAMP_IS_NOBEGIN(dt))
199 		strcpy(str, EARLY);
200 	else if (TIMESTAMP_IS_NOEND(dt))
201 		strcpy(str, LATE);
202 	else
203 		abort();				/* shouldn't happen */
204 }
205 
206 timestamp
PGTYPEStimestamp_from_asc(char * str,char ** endptr)207 PGTYPEStimestamp_from_asc(char *str, char **endptr)
208 {
209 	timestamp	result;
210 	int64		noresult = 0;
211 	fsec_t		fsec;
212 	struct tm	tt,
213 			   *tm = &tt;
214 	int			dtype;
215 	int			nf;
216 	char	   *field[MAXDATEFIELDS];
217 	int			ftype[MAXDATEFIELDS];
218 	char		lowstr[MAXDATELEN + MAXDATEFIELDS];
219 	char	   *realptr;
220 	char	  **ptr = (endptr != NULL) ? endptr : &realptr;
221 
222 	if (strlen(str) > MAXDATELEN)
223 	{
224 		errno = PGTYPES_TS_BAD_TIMESTAMP;
225 		return noresult;
226 	}
227 
228 	if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
229 		DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, 0) != 0)
230 	{
231 		errno = PGTYPES_TS_BAD_TIMESTAMP;
232 		return noresult;
233 	}
234 
235 	switch (dtype)
236 	{
237 		case DTK_DATE:
238 			if (tm2timestamp(tm, fsec, NULL, &result) != 0)
239 			{
240 				errno = PGTYPES_TS_BAD_TIMESTAMP;
241 				return noresult;
242 			}
243 			break;
244 
245 		case DTK_EPOCH:
246 			result = SetEpochTimestamp();
247 			break;
248 
249 		case DTK_LATE:
250 			TIMESTAMP_NOEND(result);
251 			break;
252 
253 		case DTK_EARLY:
254 			TIMESTAMP_NOBEGIN(result);
255 			break;
256 
257 		case DTK_INVALID:
258 			errno = PGTYPES_TS_BAD_TIMESTAMP;
259 			return noresult;
260 
261 		default:
262 			errno = PGTYPES_TS_BAD_TIMESTAMP;
263 			return noresult;
264 	}
265 
266 	/* AdjustTimestampForTypmod(&result, typmod); */
267 
268 	/*
269 	 * Since it's difficult to test for noresult, make sure errno is 0 if no
270 	 * error occurred.
271 	 */
272 	errno = 0;
273 	return result;
274 }
275 
276 char *
PGTYPEStimestamp_to_asc(timestamp tstamp)277 PGTYPEStimestamp_to_asc(timestamp tstamp)
278 {
279 	struct tm	tt,
280 			   *tm = &tt;
281 	char		buf[MAXDATELEN + 1];
282 	fsec_t		fsec;
283 	int			DateStyle = 1;	/* this defaults to ISO_DATES, shall we make
284 								 * it an option? */
285 
286 	if (TIMESTAMP_NOT_FINITE(tstamp))
287 		EncodeSpecialTimestamp(tstamp, buf);
288 	else if (timestamp2tm(tstamp, NULL, tm, &fsec, NULL) == 0)
289 		EncodeDateTime(tm, fsec, false, 0, NULL, DateStyle, buf, 0);
290 	else
291 	{
292 		errno = PGTYPES_TS_BAD_TIMESTAMP;
293 		return NULL;
294 	}
295 	return pgtypes_strdup(buf);
296 }
297 
298 void
PGTYPEStimestamp_current(timestamp * ts)299 PGTYPEStimestamp_current(timestamp * ts)
300 {
301 	struct tm	tm;
302 
303 	GetCurrentDateTime(&tm);
304 	if (errno == 0)
305 		tm2timestamp(&tm, 0, NULL, ts);
306 	return;
307 }
308 
309 static int
dttofmtasc_replace(timestamp * ts,date dDate,int dow,struct tm * tm,char * output,int * pstr_len,const char * fmtstr)310 dttofmtasc_replace(timestamp * ts, date dDate, int dow, struct tm *tm,
311 				   char *output, int *pstr_len, const char *fmtstr)
312 {
313 	union un_fmt_comb replace_val;
314 	int			replace_type;
315 	int			i;
316 	const char *p = fmtstr;
317 	char	   *q = output;
318 
319 	while (*p)
320 	{
321 		if (*p == '%')
322 		{
323 			p++;
324 			/* fix compiler warning */
325 			replace_type = PGTYPES_TYPE_NOTHING;
326 			switch (*p)
327 			{
328 					/* the abbreviated name of the day in the week */
329 					/* XXX should be locale aware */
330 				case 'a':
331 					replace_val.str_val = pgtypes_date_weekdays_short[dow];
332 					replace_type = PGTYPES_TYPE_STRING_CONSTANT;
333 					break;
334 					/* the full name of the day in the week */
335 					/* XXX should be locale aware */
336 				case 'A':
337 					replace_val.str_val = days[dow];
338 					replace_type = PGTYPES_TYPE_STRING_CONSTANT;
339 					break;
340 					/* the abbreviated name of the month */
341 					/* XXX should be locale aware */
342 				case 'b':
343 				case 'h':
344 					replace_val.str_val = months[tm->tm_mon - 1];
345 					replace_type = PGTYPES_TYPE_STRING_CONSTANT;
346 					break;
347 					/* the full name of the month */
348 					/* XXX should be locale aware */
349 				case 'B':
350 					replace_val.str_val = pgtypes_date_months[tm->tm_mon - 1];
351 					replace_type = PGTYPES_TYPE_STRING_CONSTANT;
352 					break;
353 
354 					/*
355 					 * The	preferred  date  and  time	representation	for
356 					 * the current locale.
357 					 */
358 				case 'c':
359 					/* XXX */
360 					break;
361 					/* the century number with leading zeroes */
362 				case 'C':
363 					replace_val.uint_val = tm->tm_year / 100;
364 					replace_type = PGTYPES_TYPE_UINT_2_LZ;
365 					break;
366 					/* day with leading zeroes (01 - 31) */
367 				case 'd':
368 					replace_val.uint_val = tm->tm_mday;
369 					replace_type = PGTYPES_TYPE_UINT_2_LZ;
370 					break;
371 					/* the date in the format mm/dd/yy */
372 				case 'D':
373 
374 					/*
375 					 * ts, dDate, dow, tm is information about the timestamp
376 					 *
377 					 * q is the start of the current output buffer
378 					 *
379 					 * pstr_len is a pointer to the remaining size of output,
380 					 * i.e. the size of q
381 					 */
382 					i = dttofmtasc_replace(ts, dDate, dow, tm,
383 										   q, pstr_len,
384 										   "%m/%d/%y");
385 					if (i)
386 						return i;
387 					break;
388 					/* day with leading spaces (01 - 31) */
389 				case 'e':
390 					replace_val.uint_val = tm->tm_mday;
391 					replace_type = PGTYPES_TYPE_UINT_2_LS;
392 					break;
393 
394 					/*
395 					 * alternative format modifier
396 					 */
397 				case 'E':
398 					{
399 						char		tmp[4] = "%Ex";
400 
401 						p++;
402 						if (*p == '\0')
403 							return -1;
404 						tmp[2] = *p;
405 
406 						/*
407 						 * strftime's month is 0 based, ours is 1 based
408 						 */
409 						tm->tm_mon -= 1;
410 						i = strftime(q, *pstr_len, tmp, tm);
411 						if (i == 0)
412 							return -1;
413 						while (*q)
414 						{
415 							q++;
416 							(*pstr_len)--;
417 						}
418 						tm->tm_mon += 1;
419 						replace_type = PGTYPES_TYPE_NOTHING;
420 						break;
421 					}
422 
423 					/*
424 					 * The ISO 8601 year with century as a decimal number. The
425 					 * 4-digit year corresponding to the ISO week number.
426 					 */
427 				case 'G':
428 					{
429 						/* Keep compiler quiet - Don't use a literal format */
430 						const char *fmt = "%G";
431 
432 						tm->tm_mon -= 1;
433 						i = strftime(q, *pstr_len, fmt, tm);
434 						if (i == 0)
435 							return -1;
436 						while (*q)
437 						{
438 							q++;
439 							(*pstr_len)--;
440 						}
441 						tm->tm_mon += 1;
442 						replace_type = PGTYPES_TYPE_NOTHING;
443 					}
444 					break;
445 
446 					/*
447 					 * Like %G, but without century, i.e., with a 2-digit year
448 					 * (00-99).
449 					 */
450 				case 'g':
451 					{
452 						const char *fmt = "%g"; /* Keep compiler quiet about
453 												 * 2-digit year */
454 
455 						tm->tm_mon -= 1;
456 						i = strftime(q, *pstr_len, fmt, tm);
457 						if (i == 0)
458 							return -1;
459 						while (*q)
460 						{
461 							q++;
462 							(*pstr_len)--;
463 						}
464 						tm->tm_mon += 1;
465 						replace_type = PGTYPES_TYPE_NOTHING;
466 					}
467 					break;
468 					/* hour (24 hour clock) with leading zeroes */
469 				case 'H':
470 					replace_val.uint_val = tm->tm_hour;
471 					replace_type = PGTYPES_TYPE_UINT_2_LZ;
472 					break;
473 					/* hour (12 hour clock) with leading zeroes */
474 				case 'I':
475 					replace_val.uint_val = tm->tm_hour % 12;
476 					replace_type = PGTYPES_TYPE_UINT_2_LZ;
477 					break;
478 
479 					/*
480 					 * The day of the year as a decimal number with leading
481 					 * zeroes. It ranges from 001 to 366.
482 					 */
483 				case 'j':
484 					replace_val.uint_val = tm->tm_yday;
485 					replace_type = PGTYPES_TYPE_UINT_3_LZ;
486 					break;
487 
488 					/*
489 					 * The hour (24 hour clock). Leading zeroes will be turned
490 					 * into spaces.
491 					 */
492 				case 'k':
493 					replace_val.uint_val = tm->tm_hour;
494 					replace_type = PGTYPES_TYPE_UINT_2_LS;
495 					break;
496 
497 					/*
498 					 * The hour (12 hour clock). Leading zeroes will be turned
499 					 * into spaces.
500 					 */
501 				case 'l':
502 					replace_val.uint_val = tm->tm_hour % 12;
503 					replace_type = PGTYPES_TYPE_UINT_2_LS;
504 					break;
505 					/* The month as a decimal number with a leading zero */
506 				case 'm':
507 					replace_val.uint_val = tm->tm_mon;
508 					replace_type = PGTYPES_TYPE_UINT_2_LZ;
509 					break;
510 					/* The minute as a decimal number with a leading zero */
511 				case 'M':
512 					replace_val.uint_val = tm->tm_min;
513 					replace_type = PGTYPES_TYPE_UINT_2_LZ;
514 					break;
515 					/* A newline character */
516 				case 'n':
517 					replace_val.char_val = '\n';
518 					replace_type = PGTYPES_TYPE_CHAR;
519 					break;
520 					/* the AM/PM specifier (uppercase) */
521 					/* XXX should be locale aware */
522 				case 'p':
523 					if (tm->tm_hour < 12)
524 						replace_val.str_val = "AM";
525 					else
526 						replace_val.str_val = "PM";
527 					replace_type = PGTYPES_TYPE_STRING_CONSTANT;
528 					break;
529 					/* the AM/PM specifier (lowercase) */
530 					/* XXX should be locale aware */
531 				case 'P':
532 					if (tm->tm_hour < 12)
533 						replace_val.str_val = "am";
534 					else
535 						replace_val.str_val = "pm";
536 					replace_type = PGTYPES_TYPE_STRING_CONSTANT;
537 					break;
538 					/* the time in the format %I:%M:%S %p */
539 					/* XXX should be locale aware */
540 				case 'r':
541 					i = dttofmtasc_replace(ts, dDate, dow, tm,
542 										   q, pstr_len,
543 										   "%I:%M:%S %p");
544 					if (i)
545 						return i;
546 					break;
547 					/* The time in 24 hour notation (%H:%M) */
548 				case 'R':
549 					i = dttofmtasc_replace(ts, dDate, dow, tm,
550 										   q, pstr_len,
551 										   "%H:%M");
552 					if (i)
553 						return i;
554 					break;
555 					/* The number of seconds since the Epoch (1970-01-01) */
556 				case 's':
557 					replace_val.int64_val = (*ts - SetEpochTimestamp()) / 1000000.0;
558 					replace_type = PGTYPES_TYPE_INT64;
559 					break;
560 					/* seconds as a decimal number with leading zeroes */
561 				case 'S':
562 					replace_val.uint_val = tm->tm_sec;
563 					replace_type = PGTYPES_TYPE_UINT_2_LZ;
564 					break;
565 					/* A tabulator */
566 				case 't':
567 					replace_val.char_val = '\t';
568 					replace_type = PGTYPES_TYPE_CHAR;
569 					break;
570 					/* The time in 24 hour notation (%H:%M:%S) */
571 				case 'T':
572 					i = dttofmtasc_replace(ts, dDate, dow, tm,
573 										   q, pstr_len,
574 										   "%H:%M:%S");
575 					if (i)
576 						return i;
577 					break;
578 
579 					/*
580 					 * The day of the week as a decimal, Monday = 1, Sunday =
581 					 * 7
582 					 */
583 				case 'u':
584 					replace_val.uint_val = dow;
585 					if (replace_val.uint_val == 0)
586 						replace_val.uint_val = 7;
587 					replace_type = PGTYPES_TYPE_UINT;
588 					break;
589 					/* The week number of the year as a decimal number */
590 				case 'U':
591 					tm->tm_mon -= 1;
592 					i = strftime(q, *pstr_len, "%U", tm);
593 					if (i == 0)
594 						return -1;
595 					while (*q)
596 					{
597 						q++;
598 						(*pstr_len)--;
599 					}
600 					tm->tm_mon += 1;
601 					replace_type = PGTYPES_TYPE_NOTHING;
602 					break;
603 
604 					/*
605 					 * The ISO 8601:1988 week number of the current year as a
606 					 * decimal number.
607 					 */
608 				case 'V':
609 					{
610 						/* Keep compiler quiet - Don't use a literal format */
611 						const char *fmt = "%V";
612 
613 						i = strftime(q, *pstr_len, fmt, tm);
614 						if (i == 0)
615 							return -1;
616 						while (*q)
617 						{
618 							q++;
619 							(*pstr_len)--;
620 						}
621 						replace_type = PGTYPES_TYPE_NOTHING;
622 					}
623 					break;
624 
625 					/*
626 					 * The day of the week as a decimal, Sunday being 0 and
627 					 * Monday 1.
628 					 */
629 				case 'w':
630 					replace_val.uint_val = dow;
631 					replace_type = PGTYPES_TYPE_UINT;
632 					break;
633 					/* The week number of the year (another definition) */
634 				case 'W':
635 					tm->tm_mon -= 1;
636 					i = strftime(q, *pstr_len, "%U", tm);
637 					if (i == 0)
638 						return -1;
639 					while (*q)
640 					{
641 						q++;
642 						(*pstr_len)--;
643 					}
644 					tm->tm_mon += 1;
645 					replace_type = PGTYPES_TYPE_NOTHING;
646 					break;
647 
648 					/*
649 					 * The preferred date representation for the current
650 					 * locale without the time.
651 					 */
652 				case 'x':
653 					{
654 						const char *fmt = "%x"; /* Keep compiler quiet about
655 												 * 2-digit year */
656 
657 						tm->tm_mon -= 1;
658 						i = strftime(q, *pstr_len, fmt, tm);
659 						if (i == 0)
660 							return -1;
661 						while (*q)
662 						{
663 							q++;
664 							(*pstr_len)--;
665 						}
666 						tm->tm_mon += 1;
667 						replace_type = PGTYPES_TYPE_NOTHING;
668 					}
669 					break;
670 
671 					/*
672 					 * The preferred time representation for the current
673 					 * locale without the date.
674 					 */
675 				case 'X':
676 					tm->tm_mon -= 1;
677 					i = strftime(q, *pstr_len, "%X", tm);
678 					if (i == 0)
679 						return -1;
680 					while (*q)
681 					{
682 						q++;
683 						(*pstr_len)--;
684 					}
685 					tm->tm_mon += 1;
686 					replace_type = PGTYPES_TYPE_NOTHING;
687 					break;
688 					/* The year without the century (2 digits, leading zeroes) */
689 				case 'y':
690 					replace_val.uint_val = tm->tm_year % 100;
691 					replace_type = PGTYPES_TYPE_UINT_2_LZ;
692 					break;
693 					/* The year with the century (4 digits) */
694 				case 'Y':
695 					replace_val.uint_val = tm->tm_year;
696 					replace_type = PGTYPES_TYPE_UINT;
697 					break;
698 					/* The time zone offset from GMT */
699 				case 'z':
700 					tm->tm_mon -= 1;
701 					i = strftime(q, *pstr_len, "%z", tm);
702 					if (i == 0)
703 						return -1;
704 					while (*q)
705 					{
706 						q++;
707 						(*pstr_len)--;
708 					}
709 					tm->tm_mon += 1;
710 					replace_type = PGTYPES_TYPE_NOTHING;
711 					break;
712 					/* The name or abbreviation of the time zone */
713 				case 'Z':
714 					tm->tm_mon -= 1;
715 					i = strftime(q, *pstr_len, "%Z", tm);
716 					if (i == 0)
717 						return -1;
718 					while (*q)
719 					{
720 						q++;
721 						(*pstr_len)--;
722 					}
723 					tm->tm_mon += 1;
724 					replace_type = PGTYPES_TYPE_NOTHING;
725 					break;
726 					/* A % sign */
727 				case '%':
728 					replace_val.char_val = '%';
729 					replace_type = PGTYPES_TYPE_CHAR;
730 					break;
731 				case '\0':
732 					/* fmtstr: foo%' - The string ends with a % sign */
733 
734 					/*
735 					 * this is not compliant to the specification
736 					 */
737 					return -1;
738 				default:
739 
740 					/*
741 					 * if we don't know the pattern, we just copy it
742 					 */
743 					if (*pstr_len > 1)
744 					{
745 						*q = '%';
746 						q++;
747 						(*pstr_len)--;
748 						if (*pstr_len > 1)
749 						{
750 							*q = *p;
751 							q++;
752 							(*pstr_len)--;
753 						}
754 						else
755 						{
756 							*q = '\0';
757 							return -1;
758 						}
759 						*q = '\0';
760 					}
761 					else
762 						return -1;
763 					break;
764 			}
765 			i = pgtypes_fmt_replace(replace_val, replace_type, &q, pstr_len);
766 			if (i)
767 				return i;
768 		}
769 		else
770 		{
771 			if (*pstr_len > 1)
772 			{
773 				*q = *p;
774 				(*pstr_len)--;
775 				q++;
776 				*q = '\0';
777 			}
778 			else
779 				return -1;
780 		}
781 		p++;
782 	}
783 	return 0;
784 }
785 
786 
787 int
PGTYPEStimestamp_fmt_asc(timestamp * ts,char * output,int str_len,const char * fmtstr)788 PGTYPEStimestamp_fmt_asc(timestamp * ts, char *output, int str_len, const char *fmtstr)
789 {
790 	struct tm	tm;
791 	fsec_t		fsec;
792 	date		dDate;
793 	int			dow;
794 
795 	dDate = PGTYPESdate_from_timestamp(*ts);
796 	dow = PGTYPESdate_dayofweek(dDate);
797 	timestamp2tm(*ts, NULL, &tm, &fsec, NULL);
798 
799 	return dttofmtasc_replace(ts, dDate, dow, &tm, output, &str_len, fmtstr);
800 }
801 
802 int
PGTYPEStimestamp_sub(timestamp * ts1,timestamp * ts2,interval * iv)803 PGTYPEStimestamp_sub(timestamp * ts1, timestamp * ts2, interval * iv)
804 {
805 	if (TIMESTAMP_NOT_FINITE(*ts1) || TIMESTAMP_NOT_FINITE(*ts2))
806 		return PGTYPES_TS_ERR_EINFTIME;
807 	else
808 		iv->time = (*ts1 - *ts2);
809 
810 	iv->month = 0;
811 
812 	return 0;
813 }
814 
815 int
PGTYPEStimestamp_defmt_asc(const char * str,const char * fmt,timestamp * d)816 PGTYPEStimestamp_defmt_asc(const char *str, const char *fmt, timestamp * d)
817 {
818 	int			year,
819 				month,
820 				day;
821 	int			hour,
822 				minute,
823 				second;
824 	int			tz;
825 
826 	int			i;
827 	char	   *mstr;
828 	char	   *mfmt;
829 
830 	if (!fmt)
831 		fmt = "%Y-%m-%d %H:%M:%S";
832 	if (!fmt[0])
833 		return 1;
834 
835 	mstr = pgtypes_strdup(str);
836 	mfmt = pgtypes_strdup(fmt);
837 
838 	/*
839 	 * initialize with impossible values so that we can see if the fields
840 	 * where specified at all
841 	 */
842 	/* XXX ambiguity with 1 BC for year? */
843 	year = -1;
844 	month = -1;
845 	day = -1;
846 	hour = 0;
847 	minute = -1;
848 	second = -1;
849 	tz = 0;
850 
851 	i = PGTYPEStimestamp_defmt_scan(&mstr, mfmt, d, &year, &month, &day, &hour, &minute, &second, &tz);
852 	free(mstr);
853 	free(mfmt);
854 	return i;
855 }
856 
857 /*
858 * add an interval to a time stamp
859 *
860 *	*tout = tin + span
861 *
862 *	 returns 0 if successful
863 *	 returns -1 if it fails
864 *
865 */
866 
867 int
PGTYPEStimestamp_add_interval(timestamp * tin,interval * span,timestamp * tout)868 PGTYPEStimestamp_add_interval(timestamp * tin, interval * span, timestamp * tout)
869 {
870 	if (TIMESTAMP_NOT_FINITE(*tin))
871 		*tout = *tin;
872 
873 
874 	else
875 	{
876 		if (span->month != 0)
877 		{
878 			struct tm	tt,
879 					   *tm = &tt;
880 			fsec_t		fsec;
881 
882 
883 			if (timestamp2tm(*tin, NULL, tm, &fsec, NULL) != 0)
884 				return -1;
885 			tm->tm_mon += span->month;
886 			if (tm->tm_mon > MONTHS_PER_YEAR)
887 			{
888 				tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
889 				tm->tm_mon = (tm->tm_mon - 1) % MONTHS_PER_YEAR + 1;
890 			}
891 			else if (tm->tm_mon < 1)
892 			{
893 				tm->tm_year += tm->tm_mon / MONTHS_PER_YEAR - 1;
894 				tm->tm_mon = tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR;
895 			}
896 
897 
898 			/* adjust for end of month boundary problems... */
899 			if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
900 				tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]);
901 
902 
903 			if (tm2timestamp(tm, fsec, NULL, tin) != 0)
904 				return -1;
905 		}
906 
907 
908 		*tin += span->time;
909 		*tout = *tin;
910 	}
911 	return 0;
912 
913 }
914 
915 
916 /*
917 * subtract an interval from a time stamp
918 *
919 *	*tout = tin - span
920 *
921 *	 returns 0 if successful
922 *	 returns -1 if it fails
923 *
924 */
925 
926 int
PGTYPEStimestamp_sub_interval(timestamp * tin,interval * span,timestamp * tout)927 PGTYPEStimestamp_sub_interval(timestamp * tin, interval * span, timestamp * tout)
928 {
929 	interval	tspan;
930 
931 	tspan.month = -span->month;
932 	tspan.time = -span->time;
933 
934 
935 	return PGTYPEStimestamp_add_interval(tin, &tspan, tout);
936 }
937