Fix pg_dump bug in the database-level collation patch. "datcollate" and
[PostgreSQL.git] / src / interfaces / ecpg / pgtypeslib / timestamp.c
blobe9fa5d5a621210a42ee0ee3b57db90ae00d52b31
1 /*
2 * $PostgreSQL:$
3 */
4 #include "postgres_fe.h"
6 #include <time.h>
7 #include <float.h>
8 #include <limits.h>
9 #include <math.h>
11 #ifdef __FAST_MATH__
12 #error -ffast-math is known to break this code
13 #endif
15 #include "extern.h"
16 #include "dt.h"
17 #include "pgtypes_timestamp.h"
18 #include "pgtypes_date.h"
21 int PGTYPEStimestamp_defmt_scan(char **, char *, timestamp *, int *, int *, int *,
22 int *, int *, int *, int *);
24 #ifdef HAVE_INT64_TIMESTAMP
25 static int64
26 time2t(const int hour, const int min, const int sec, const fsec_t fsec)
28 return (((((hour * MINS_PER_HOUR) + min) * SECS_PER_MINUTE) + sec) * USECS_PER_SEC) + fsec;
29 } /* time2t() */
30 #else
31 static double
32 time2t(const int hour, const int min, const int sec, const fsec_t fsec)
34 return (((hour * MINS_PER_HOUR) + min) * SECS_PER_MINUTE) + sec + fsec;
35 } /* time2t() */
36 #endif
38 static timestamp
39 dt2local(timestamp dt, int tz)
41 #ifdef HAVE_INT64_TIMESTAMP
42 dt -= (tz * USECS_PER_SEC);
43 #else
44 dt -= tz;
45 #endif
46 return dt;
47 } /* dt2local() */
49 /* tm2timestamp()
50 * Convert a tm structure to a timestamp data type.
51 * Note that year is _not_ 1900-based, but is an explicit full value.
52 * Also, month is one-based, _not_ zero-based.
54 * Returns -1 on failure (overflow).
56 int
57 tm2timestamp(struct tm * tm, fsec_t fsec, int *tzp, timestamp * result)
59 #ifdef HAVE_INT64_TIMESTAMP
60 int dDate;
61 int64 time;
62 #else
63 double dDate,
64 time;
65 #endif
67 /* Julian day routines are not correct for negative Julian days */
68 if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
69 return -1;
71 dDate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1);
72 time = time2t(tm->tm_hour, tm->tm_min, tm->tm_sec, fsec);
73 #ifdef HAVE_INT64_TIMESTAMP
74 *result = (dDate * USECS_PER_DAY) + time;
75 /* check for major overflow */
76 if ((*result - time) / USECS_PER_DAY != dDate)
77 return -1;
78 /* check for just-barely overflow (okay except time-of-day wraps) */
79 if ((*result < 0 && dDate >= 0) ||
80 (*result >= 0 && dDate < 0))
81 return -1;
82 #else
83 *result = dDate * SECS_PER_DAY + time;
84 #endif
85 if (tzp != NULL)
86 *result = dt2local(*result, -(*tzp));
88 return 0;
89 } /* tm2timestamp() */
91 static timestamp
92 SetEpochTimestamp(void)
94 timestamp dt;
95 struct tm tt,
96 *tm = &tt;
98 GetEpochTime(tm);
99 tm2timestamp(tm, 0, NULL, &dt);
100 return dt;
101 } /* SetEpochTimestamp() */
103 /* timestamp2tm()
104 * Convert timestamp data type to POSIX time structure.
105 * Note that year is _not_ 1900-based, but is an explicit full value.
106 * Also, month is one-based, _not_ zero-based.
107 * Returns:
108 * 0 on success
109 * -1 on out of range
111 * For dates within the system-supported time_t range, convert to the
112 * local time zone. If out of this range, leave as GMT. - tgl 97/05/27
114 static int
115 timestamp2tm(timestamp dt, int *tzp, struct tm * tm, fsec_t *fsec, char **tzn)
117 #ifdef HAVE_INT64_TIMESTAMP
118 int64 dDate,
119 date0;
120 int64 time;
121 #else
122 double dDate,
123 date0;
124 double time;
125 #endif
126 time_t utime;
128 #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
129 struct tm *tx;
130 #endif
132 date0 = date2j(2000, 1, 1);
134 #ifdef HAVE_INT64_TIMESTAMP
135 time = dt;
136 TMODULO(time, dDate, USECS_PER_DAY);
138 if (time < INT64CONST(0))
140 time += USECS_PER_DAY;
141 dDate -= 1;
144 /* add offset to go from J2000 back to standard Julian date */
145 dDate += date0;
147 /* Julian day routine does not work for negative Julian days */
148 if (dDate < 0 || dDate > (timestamp) INT_MAX)
149 return -1;
151 j2date((int) dDate, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
152 dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
153 #else
154 time = dt;
155 TMODULO(time, dDate, (double) SECS_PER_DAY);
157 if (time < 0)
159 time += SECS_PER_DAY;
160 dDate -= 1;
163 /* add offset to go from J2000 back to standard Julian date */
164 dDate += date0;
166 recalc_d:
167 /* Julian day routine does not work for negative Julian days */
168 if (dDate < 0 || dDate > (timestamp) INT_MAX)
169 return -1;
171 j2date((int) dDate, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
172 recalc_t:
173 dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
175 *fsec = TSROUND(*fsec);
176 /* roundoff may need to propagate to higher-order fields */
177 if (*fsec >= 1.0)
179 time = ceil(time);
180 if (time >= (double) SECS_PER_DAY)
182 time = 0;
183 dDate += 1;
184 goto recalc_d;
186 goto recalc_t;
188 #endif
190 if (tzp != NULL)
193 * Does this fall within the capabilities of the localtime()
194 * interface? Then use this to rotate to the local time zone.
196 if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
198 #ifdef HAVE_INT64_TIMESTAMP
199 utime = dt / USECS_PER_SEC +
200 ((date0 - date2j(1970, 1, 1)) * INT64CONST(86400));
201 #else
202 utime = dt + (date0 - date2j(1970, 1, 1)) * SECS_PER_DAY;
203 #endif
205 #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
206 tx = localtime(&utime);
207 tm->tm_year = tx->tm_year + 1900;
208 tm->tm_mon = tx->tm_mon + 1;
209 tm->tm_mday = tx->tm_mday;
210 tm->tm_hour = tx->tm_hour;
211 tm->tm_min = tx->tm_min;
212 tm->tm_isdst = tx->tm_isdst;
214 #if defined(HAVE_TM_ZONE)
215 tm->tm_gmtoff = tx->tm_gmtoff;
216 tm->tm_zone = tx->tm_zone;
218 *tzp = -tm->tm_gmtoff; /* tm_gmtoff is Sun/DEC-ism */
219 if (tzn != NULL)
220 *tzn = (char *) tm->tm_zone;
221 #elif defined(HAVE_INT_TIMEZONE)
222 *tzp = (tm->tm_isdst > 0) ? TIMEZONE_GLOBAL - SECS_PER_HOUR : TIMEZONE_GLOBAL;
223 if (tzn != NULL)
224 *tzn = TZNAME_GLOBAL[(tm->tm_isdst > 0)];
225 #endif
226 #else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
227 *tzp = 0;
228 /* Mark this as *no* time zone available */
229 tm->tm_isdst = -1;
230 if (tzn != NULL)
231 *tzn = NULL;
232 #endif
234 dt = dt2local(dt, *tzp);
236 else
238 *tzp = 0;
239 /* Mark this as *no* time zone available */
240 tm->tm_isdst = -1;
241 if (tzn != NULL)
242 *tzn = NULL;
245 else
247 tm->tm_isdst = -1;
248 if (tzn != NULL)
249 *tzn = NULL;
252 return 0;
253 } /* timestamp2tm() */
255 /* EncodeSpecialTimestamp()
256 * * Convert reserved timestamp data type to string.
257 * */
258 static int
259 EncodeSpecialTimestamp(timestamp dt, char *str)
261 if (TIMESTAMP_IS_NOBEGIN(dt))
262 strcpy(str, EARLY);
263 else if (TIMESTAMP_IS_NOEND(dt))
264 strcpy(str, LATE);
265 else
266 return FALSE;
268 return TRUE;
269 } /* EncodeSpecialTimestamp() */
271 timestamp
272 PGTYPEStimestamp_from_asc(char *str, char **endptr)
274 timestamp result;
276 #ifdef HAVE_INT64_TIMESTAMP
277 int64 noresult = 0;
278 #else
279 double noresult = 0.0;
280 #endif
281 fsec_t fsec;
282 struct tm tt,
283 *tm = &tt;
284 int dtype;
285 int nf;
286 char *field[MAXDATEFIELDS];
287 int ftype[MAXDATEFIELDS];
288 char lowstr[MAXDATELEN + MAXDATEFIELDS];
289 char *realptr;
290 char **ptr = (endptr != NULL) ? endptr : &realptr;
292 if (strlen(str) >= sizeof(lowstr))
294 errno = PGTYPES_TS_BAD_TIMESTAMP;
295 return (noresult);
298 if (ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf, ptr) != 0 ||
299 DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, 0) != 0)
301 errno = PGTYPES_TS_BAD_TIMESTAMP;
302 return (noresult);
305 switch (dtype)
307 case DTK_DATE:
308 if (tm2timestamp(tm, fsec, NULL, &result) != 0)
310 errno = PGTYPES_TS_BAD_TIMESTAMP;
311 return (noresult);
313 break;
315 case DTK_EPOCH:
316 result = SetEpochTimestamp();
317 break;
319 case DTK_LATE:
320 TIMESTAMP_NOEND(result);
321 break;
323 case DTK_EARLY:
324 TIMESTAMP_NOBEGIN(result);
325 break;
327 case DTK_INVALID:
328 errno = PGTYPES_TS_BAD_TIMESTAMP;
329 return (noresult);
331 default:
332 errno = PGTYPES_TS_BAD_TIMESTAMP;
333 return (noresult);
336 /* AdjustTimestampForTypmod(&result, typmod); */
339 * Since it's difficult to test for noresult, make sure errno is 0 if no
340 * error occured.
342 errno = 0;
343 return result;
346 char *
347 PGTYPEStimestamp_to_asc(timestamp tstamp)
349 struct tm tt,
350 *tm = &tt;
351 char buf[MAXDATELEN + 1];
352 char *tzn = NULL;
353 fsec_t fsec;
354 int DateStyle = 1; /* this defaults to ISO_DATES, shall we make
355 * it an option? */
357 if (TIMESTAMP_NOT_FINITE(tstamp))
358 EncodeSpecialTimestamp(tstamp, buf);
359 else if (timestamp2tm(tstamp, NULL, tm, &fsec, NULL) == 0)
360 EncodeDateTime(tm, fsec, NULL, &tzn, DateStyle, buf, 0);
361 else
363 errno = PGTYPES_TS_BAD_TIMESTAMP;
364 return NULL;
366 return pgtypes_strdup(buf);
369 void
370 PGTYPEStimestamp_current(timestamp * ts)
372 struct tm tm;
374 GetCurrentDateTime(&tm);
375 tm2timestamp(&tm, 0, NULL, ts);
376 return;
379 static int
380 dttofmtasc_replace(timestamp * ts, date dDate, int dow, struct tm * tm,
381 char *output, int *pstr_len, char *fmtstr)
383 union un_fmt_comb replace_val;
384 int replace_type;
385 int i;
386 char *p = fmtstr;
387 char *q = output;
389 while (*p)
391 if (*p == '%')
393 p++;
394 /* fix compiler warning */
395 replace_type = PGTYPES_TYPE_NOTHING;
396 switch (*p)
398 /* the abbreviated name of the day in the week */
399 /* XXX should be locale aware */
400 case 'a':
401 replace_val.str_val = pgtypes_date_weekdays_short[dow];
402 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
403 break;
404 /* the full name of the day in the week */
405 /* XXX should be locale aware */
406 case 'A':
407 replace_val.str_val = days[dow];
408 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
409 break;
410 /* the abbreviated name of the month */
411 /* XXX should be locale aware */
412 case 'b':
413 case 'h':
414 replace_val.str_val = months[tm->tm_mon];
415 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
416 break;
417 /* the full name name of the month */
418 /* XXX should be locale aware */
419 case 'B':
420 replace_val.str_val = pgtypes_date_months[tm->tm_mon];
421 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
422 break;
425 * The preferred date and time representation for
426 * the current locale.
428 case 'c':
429 /* XXX */
430 break;
431 /* the century number with leading zeroes */
432 case 'C':
433 replace_val.uint_val = tm->tm_year / 100;
434 replace_type = PGTYPES_TYPE_UINT_2_LZ;
435 break;
436 /* day with leading zeroes (01 - 31) */
437 case 'd':
438 replace_val.uint_val = tm->tm_mday;
439 replace_type = PGTYPES_TYPE_UINT_2_LZ;
440 break;
441 /* the date in the format mm/dd/yy */
442 case 'D':
445 * ts, dDate, dow, tm is information about the timestamp
447 * q is the start of the current output buffer
449 * pstr_len is a pointer to the remaining size of output,
450 * i.e. the size of q
452 i = dttofmtasc_replace(ts, dDate, dow, tm,
453 q, pstr_len,
454 "%m/%d/%y");
455 if (i)
456 return i;
457 break;
458 /* day with leading spaces (01 - 31) */
459 case 'e':
460 replace_val.uint_val = tm->tm_mday;
461 replace_type = PGTYPES_TYPE_UINT_2_LS;
462 break;
465 * alternative format modifier
467 case 'E':
469 char tmp[4] = "%Ex";
471 p++;
472 if (*p == '\0')
473 return -1;
474 tmp[2] = *p;
477 * strftime's month is 0 based, ours is 1 based
479 tm->tm_mon -= 1;
480 i = strftime(q, *pstr_len, tmp, tm);
481 if (i == 0)
482 return -1;
483 while (*q)
485 q++;
486 (*pstr_len)--;
488 tm->tm_mon += 1;
489 replace_type = PGTYPES_TYPE_NOTHING;
490 break;
494 * The ISO 8601 year with century as a decimal number. The
495 * 4-digit year corresponding to the ISO week number.
497 case 'G':
498 tm->tm_mon -= 1;
499 i = strftime(q, *pstr_len, "%G", tm);
500 if (i == 0)
501 return -1;
502 while (*q)
504 q++;
505 (*pstr_len)--;
507 tm->tm_mon += 1;
508 replace_type = PGTYPES_TYPE_NOTHING;
509 break;
512 * Like %G, but without century, i.e., with a 2-digit year
513 * (00-99).
515 case 'g':
517 const char *fmt = "%g"; /* Keep compiler quiet about
518 * 2-digit year */
520 tm->tm_mon -= 1;
521 i = strftime(q, *pstr_len, fmt, tm);
522 if (i == 0)
523 return -1;
524 while (*q)
526 q++;
527 (*pstr_len)--;
529 tm->tm_mon += 1;
530 replace_type = PGTYPES_TYPE_NOTHING;
532 break;
533 /* hour (24 hour clock) with leading zeroes */
534 case 'H':
535 replace_val.uint_val = tm->tm_hour;
536 replace_type = PGTYPES_TYPE_UINT_2_LZ;
537 break;
538 /* hour (12 hour clock) with leading zeroes */
539 case 'I':
540 replace_val.uint_val = tm->tm_hour % 12;
541 replace_type = PGTYPES_TYPE_UINT_2_LZ;
542 break;
545 * The day of the year as a decimal number with leading
546 * zeroes. It ranges from 001 to 366.
548 case 'j':
549 replace_val.uint_val = tm->tm_yday;
550 replace_type = PGTYPES_TYPE_UINT_3_LZ;
551 break;
554 * The hour (24 hour clock). Leading zeroes will be turned
555 * into spaces.
557 case 'k':
558 replace_val.uint_val = tm->tm_hour;
559 replace_type = PGTYPES_TYPE_UINT_2_LS;
560 break;
563 * The hour (12 hour clock). Leading zeroes will be turned
564 * into spaces.
566 case 'l':
567 replace_val.uint_val = tm->tm_hour % 12;
568 replace_type = PGTYPES_TYPE_UINT_2_LS;
569 break;
570 /* The month as a decimal number with a leading zero */
571 case 'm':
572 replace_val.uint_val = tm->tm_mon;
573 replace_type = PGTYPES_TYPE_UINT_2_LZ;
574 break;
575 /* The minute as a decimal number with a leading zero */
576 case 'M':
577 replace_val.uint_val = tm->tm_min;
578 replace_type = PGTYPES_TYPE_UINT_2_LZ;
579 break;
580 /* A newline character */
581 case 'n':
582 replace_val.char_val = '\n';
583 replace_type = PGTYPES_TYPE_CHAR;
584 break;
585 /* the AM/PM specifier (uppercase) */
586 /* XXX should be locale aware */
587 case 'p':
588 if (tm->tm_hour < 12)
589 replace_val.str_val = "AM";
590 else
591 replace_val.str_val = "PM";
592 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
593 break;
594 /* the AM/PM specifier (lowercase) */
595 /* XXX should be locale aware */
596 case 'P':
597 if (tm->tm_hour < 12)
598 replace_val.str_val = "am";
599 else
600 replace_val.str_val = "pm";
601 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
602 break;
603 /* the time in the format %I:%M:%S %p */
604 /* XXX should be locale aware */
605 case 'r':
606 i = dttofmtasc_replace(ts, dDate, dow, tm,
607 q, pstr_len,
608 "%I:%M:%S %p");
609 if (i)
610 return i;
611 break;
612 /* The time in 24 hour notation (%H:%M) */
613 case 'R':
614 i = dttofmtasc_replace(ts, dDate, dow, tm,
615 q, pstr_len,
616 "%H:%M");
617 if (i)
618 return i;
619 break;
620 /* The number of seconds since the Epoch (1970-01-01) */
621 case 's':
622 #ifdef HAVE_INT64_TIMESTAMP
623 replace_val.int64_val = (*ts - SetEpochTimestamp()) / 1000000.0;
624 replace_type = PGTYPES_TYPE_INT64;
625 #else
626 replace_val.double_val = *ts - SetEpochTimestamp();
627 replace_type = PGTYPES_TYPE_DOUBLE_NF;
628 #endif
629 break;
630 /* seconds as a decimal number with leading zeroes */
631 case 'S':
632 replace_val.uint_val = tm->tm_sec;
633 replace_type = PGTYPES_TYPE_UINT_2_LZ;
634 break;
635 /* A tabulator */
636 case 't':
637 replace_val.char_val = '\t';
638 replace_type = PGTYPES_TYPE_CHAR;
639 break;
640 /* The time in 24 hour notation (%H:%M:%S) */
641 case 'T':
642 i = dttofmtasc_replace(ts, dDate, dow, tm,
643 q, pstr_len,
644 "%H:%M:%S");
645 if (i)
646 return i;
647 break;
650 * The day of the week as a decimal, Monday = 1, Sunday =
653 case 'u':
654 replace_val.uint_val = dow;
655 if (replace_val.uint_val == 0)
656 replace_val.uint_val = 7;
657 replace_type = PGTYPES_TYPE_UINT;
658 break;
659 /* The week number of the year as a decimal number */
660 case 'U':
661 tm->tm_mon -= 1;
662 i = strftime(q, *pstr_len, "%U", tm);
663 if (i == 0)
664 return -1;
665 while (*q)
667 q++;
668 (*pstr_len)--;
670 tm->tm_mon += 1;
671 replace_type = PGTYPES_TYPE_NOTHING;
672 break;
675 * The ISO 8601:1988 week number of the current year as a
676 * decimal number.
678 case 'V':
679 i = strftime(q, *pstr_len, "%V", tm);
680 if (i == 0)
681 return -1;
682 while (*q)
684 q++;
685 (*pstr_len)--;
687 replace_type = PGTYPES_TYPE_NOTHING;
688 break;
691 * The day of the week as a decimal, Sunday being 0 and
692 * Monday 1.
694 case 'w':
695 replace_val.uint_val = dow;
696 replace_type = PGTYPES_TYPE_UINT;
697 break;
698 /* The week number of the year (another definition) */
699 case 'W':
700 tm->tm_mon -= 1;
701 i = strftime(q, *pstr_len, "%U", tm);
702 if (i == 0)
703 return -1;
704 while (*q)
706 q++;
707 (*pstr_len)--;
709 tm->tm_mon += 1;
710 replace_type = PGTYPES_TYPE_NOTHING;
711 break;
714 * The preferred date representation for the current
715 * locale without the time.
717 case 'x':
719 const char *fmt = "%x"; /* Keep compiler quiet about
720 * 2-digit year */
722 tm->tm_mon -= 1;
723 i = strftime(q, *pstr_len, fmt, tm);
724 if (i == 0)
725 return -1;
726 while (*q)
728 q++;
729 (*pstr_len)--;
731 tm->tm_mon += 1;
732 replace_type = PGTYPES_TYPE_NOTHING;
734 break;
737 * The preferred time representation for the current
738 * locale without the date.
740 case 'X':
741 tm->tm_mon -= 1;
742 i = strftime(q, *pstr_len, "%X", tm);
743 if (i == 0)
744 return -1;
745 while (*q)
747 q++;
748 (*pstr_len)--;
750 tm->tm_mon += 1;
751 replace_type = PGTYPES_TYPE_NOTHING;
752 break;
753 /* The year without the century (2 digits, leading zeroes) */
754 case 'y':
755 replace_val.uint_val = tm->tm_year % 100;
756 replace_type = PGTYPES_TYPE_UINT_2_LZ;
757 break;
758 /* The year with the century (4 digits) */
759 case 'Y':
760 replace_val.uint_val = tm->tm_year;
761 replace_type = PGTYPES_TYPE_UINT;
762 break;
763 /* The time zone offset from GMT */
764 case 'z':
765 tm->tm_mon -= 1;
766 i = strftime(q, *pstr_len, "%z", tm);
767 if (i == 0)
768 return -1;
769 while (*q)
771 q++;
772 (*pstr_len)--;
774 tm->tm_mon += 1;
775 replace_type = PGTYPES_TYPE_NOTHING;
776 break;
777 /* The name or abbreviation of the time zone */
778 case 'Z':
779 tm->tm_mon -= 1;
780 i = strftime(q, *pstr_len, "%Z", tm);
781 if (i == 0)
782 return -1;
783 while (*q)
785 q++;
786 (*pstr_len)--;
788 tm->tm_mon += 1;
789 replace_type = PGTYPES_TYPE_NOTHING;
790 break;
791 /* A % sign */
792 case '%':
793 replace_val.char_val = '%';
794 replace_type = PGTYPES_TYPE_CHAR;
795 break;
796 case '\0':
797 /* fmtstr: foo%' - The string ends with a % sign */
800 * this is not compliant to the specification
802 return -1;
803 default:
806 * if we don't know the pattern, we just copy it
808 if (*pstr_len > 1)
810 *q = '%';
811 q++;
812 (*pstr_len)--;
813 if (*pstr_len > 1)
815 *q = *p;
816 q++;
817 (*pstr_len)--;
819 else
821 *q = '\0';
822 return -1;
824 *q = '\0';
826 else
827 return -1;
828 break;
830 i = pgtypes_fmt_replace(replace_val, replace_type, &q, pstr_len);
831 if (i)
832 return i;
834 else
836 if (*pstr_len > 1)
838 *q = *p;
839 (*pstr_len)--;
840 q++;
841 *q = '\0';
843 else
844 return -1;
846 p++;
848 return 0;
853 PGTYPEStimestamp_fmt_asc(timestamp * ts, char *output, int str_len, char *fmtstr)
855 struct tm tm;
856 fsec_t fsec;
857 date dDate;
858 int dow;
860 dDate = PGTYPESdate_from_timestamp(*ts);
861 dow = PGTYPESdate_dayofweek(dDate);
862 timestamp2tm(*ts, NULL, &tm, &fsec, NULL);
864 return dttofmtasc_replace(ts, dDate, dow, &tm, output, &str_len, fmtstr);
868 PGTYPEStimestamp_sub(timestamp * ts1, timestamp * ts2, interval * iv)
870 if (TIMESTAMP_NOT_FINITE(*ts1) || TIMESTAMP_NOT_FINITE(*ts2))
871 return PGTYPES_TS_ERR_EINFTIME;
872 else
873 iv->time = (*ts1 - *ts2);
875 iv->month = 0;
877 return 0;
881 PGTYPEStimestamp_defmt_asc(char *str, char *fmt, timestamp * d)
883 int year,
884 month,
885 day;
886 int hour,
887 minute,
888 second;
889 int tz;
891 int i;
892 char *mstr;
893 char *mfmt;
895 if (!fmt)
896 fmt = "%Y-%m-%d %H:%M:%S";
897 if (!fmt[0])
898 return 1;
900 mstr = pgtypes_strdup(str);
901 mfmt = pgtypes_strdup(fmt);
904 * initialize with impossible values so that we can see if the fields
905 * where specified at all
907 /* XXX ambiguity with 1 BC for year? */
908 year = -1;
909 month = -1;
910 day = -1;
911 hour = 0;
912 minute = -1;
913 second = -1;
914 tz = 0;
916 i = PGTYPEStimestamp_defmt_scan(&mstr, mfmt, d, &year, &month, &day, &hour, &minute, &second, &tz);
917 free(mstr);
918 free(mfmt);
919 return i;
923 * add an interval to a time stamp
925 * *tout = tin + span
927 * returns 0 if successful
928 * returns -1 if it fails
933 PGTYPEStimestamp_add_interval(timestamp * tin, interval * span, timestamp * tout)
937 if (TIMESTAMP_NOT_FINITE(*tin))
938 *tout = *tin;
941 else
943 if (span->month != 0)
945 struct tm tt,
946 *tm = &tt;
947 fsec_t fsec;
950 if (timestamp2tm(*tin, NULL, tm, &fsec, NULL) != 0)
951 return -1;
952 tm->tm_mon += span->month;
953 if (tm->tm_mon > MONTHS_PER_YEAR)
955 tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
956 tm->tm_mon = (tm->tm_mon - 1) % MONTHS_PER_YEAR + 1;
958 else if (tm->tm_mon < 1)
960 tm->tm_year += tm->tm_mon / MONTHS_PER_YEAR - 1;
961 tm->tm_mon = tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR;
965 /* adjust for end of month boundary problems... */
966 if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
967 tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]);
970 if (tm2timestamp(tm, fsec, NULL, tin) != 0)
971 return -1;
975 *tin += span->time;
976 *tout = *tin;
978 return 0;
984 * subtract an interval from a time stamp
986 * *tout = tin - span
988 * returns 0 if successful
989 * returns -1 if it fails
994 PGTYPEStimestamp_sub_interval(timestamp * tin, interval * span, timestamp * tout)
996 interval tspan;
998 tspan.month = -span->month;
999 tspan.time = -span->time;
1002 return PGTYPEStimestamp_add_interval(tin, &tspan, tout);