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