4 #include "postgres_fe.h"
12 #error -ffast-math is known to break this code
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
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
;
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
;
39 dt2local(timestamp dt
, int tz
)
41 #ifdef HAVE_INT64_TIMESTAMP
42 dt
-= (tz
* USECS_PER_SEC
);
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).
57 tm2timestamp(struct tm
* tm
, fsec_t fsec
, int *tzp
, timestamp
* result
)
59 #ifdef HAVE_INT64_TIMESTAMP
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
))
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
)
78 /* check for just-barely overflow (okay except time-of-day wraps) */
79 if ((*result
< 0 && dDate
>= 0) ||
80 (*result
>= 0 && dDate
< 0))
83 *result
= dDate
* SECS_PER_DAY
+ time
;
86 *result
= dt2local(*result
, -(*tzp
));
89 } /* tm2timestamp() */
92 SetEpochTimestamp(void)
99 tm2timestamp(tm
, 0, NULL
, &dt
);
101 } /* SetEpochTimestamp() */
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.
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
115 timestamp2tm(timestamp dt
, int *tzp
, struct tm
* tm
, fsec_t
*fsec
, char **tzn
)
117 #ifdef HAVE_INT64_TIMESTAMP
128 #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
132 date0
= date2j(2000, 1, 1);
134 #ifdef HAVE_INT64_TIMESTAMP
136 TMODULO(time
, dDate
, USECS_PER_DAY
);
138 if (time
< INT64CONST(0))
140 time
+= USECS_PER_DAY
;
144 /* add offset to go from J2000 back to standard Julian date */
147 /* Julian day routine does not work for negative Julian days */
148 if (dDate
< 0 || dDate
> (timestamp
) INT_MAX
)
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
);
155 TMODULO(time
, dDate
, (double) SECS_PER_DAY
);
159 time
+= SECS_PER_DAY
;
163 /* add offset to go from J2000 back to standard Julian date */
167 /* Julian day routine does not work for negative Julian days */
168 if (dDate
< 0 || dDate
> (timestamp
) INT_MAX
)
171 j2date((int) dDate
, &tm
->tm_year
, &tm
->tm_mon
, &tm
->tm_mday
);
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 */
180 if (time
>= (double) SECS_PER_DAY
)
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));
202 utime
= dt
+ (date0
- date2j(1970, 1, 1)) * SECS_PER_DAY
;
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 */
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
;
224 *tzn
= TZNAME_GLOBAL
[(tm
->tm_isdst
> 0)];
226 #else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
228 /* Mark this as *no* time zone available */
234 dt
= dt2local(dt
, *tzp
);
239 /* Mark this as *no* time zone available */
253 } /* timestamp2tm() */
255 /* EncodeSpecialTimestamp()
256 * * Convert reserved timestamp data type to string.
259 EncodeSpecialTimestamp(timestamp dt
, char *str
)
261 if (TIMESTAMP_IS_NOBEGIN(dt
))
263 else if (TIMESTAMP_IS_NOEND(dt
))
269 } /* EncodeSpecialTimestamp() */
272 PGTYPEStimestamp_from_asc(char *str
, char **endptr
)
276 #ifdef HAVE_INT64_TIMESTAMP
279 double noresult
= 0.0;
286 char *field
[MAXDATEFIELDS
];
287 int ftype
[MAXDATEFIELDS
];
288 char lowstr
[MAXDATELEN
+ MAXDATEFIELDS
];
290 char **ptr
= (endptr
!= NULL
) ? endptr
: &realptr
;
292 if (strlen(str
) >= sizeof(lowstr
))
294 errno
= PGTYPES_TS_BAD_TIMESTAMP
;
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
;
308 if (tm2timestamp(tm
, fsec
, NULL
, &result
) != 0)
310 errno
= PGTYPES_TS_BAD_TIMESTAMP
;
316 result
= SetEpochTimestamp();
320 TIMESTAMP_NOEND(result
);
324 TIMESTAMP_NOBEGIN(result
);
328 errno
= PGTYPES_TS_BAD_TIMESTAMP
;
332 errno
= PGTYPES_TS_BAD_TIMESTAMP
;
336 /* AdjustTimestampForTypmod(&result, typmod); */
339 * Since it's difficult to test for noresult, make sure errno is 0 if no
347 PGTYPEStimestamp_to_asc(timestamp tstamp
)
351 char buf
[MAXDATELEN
+ 1];
354 int DateStyle
= 1; /* this defaults to ISO_DATES, shall we make
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);
363 errno
= PGTYPES_TS_BAD_TIMESTAMP
;
366 return pgtypes_strdup(buf
);
370 PGTYPEStimestamp_current(timestamp
* ts
)
374 GetCurrentDateTime(&tm
);
375 tm2timestamp(&tm
, 0, NULL
, ts
);
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
;
394 /* fix compiler warning */
395 replace_type
= PGTYPES_TYPE_NOTHING
;
398 /* the abbreviated name of the day in the week */
399 /* XXX should be locale aware */
401 replace_val
.str_val
= pgtypes_date_weekdays_short
[dow
];
402 replace_type
= PGTYPES_TYPE_STRING_CONSTANT
;
404 /* the full name of the day in the week */
405 /* XXX should be locale aware */
407 replace_val
.str_val
= days
[dow
];
408 replace_type
= PGTYPES_TYPE_STRING_CONSTANT
;
410 /* the abbreviated name of the month */
411 /* XXX should be locale aware */
414 replace_val
.str_val
= months
[tm
->tm_mon
];
415 replace_type
= PGTYPES_TYPE_STRING_CONSTANT
;
417 /* the full name name of the month */
418 /* XXX should be locale aware */
420 replace_val
.str_val
= pgtypes_date_months
[tm
->tm_mon
];
421 replace_type
= PGTYPES_TYPE_STRING_CONSTANT
;
425 * The preferred date and time representation for
426 * the current locale.
431 /* the century number with leading zeroes */
433 replace_val
.uint_val
= tm
->tm_year
/ 100;
434 replace_type
= PGTYPES_TYPE_UINT_2_LZ
;
436 /* day with leading zeroes (01 - 31) */
438 replace_val
.uint_val
= tm
->tm_mday
;
439 replace_type
= PGTYPES_TYPE_UINT_2_LZ
;
441 /* the date in the format mm/dd/yy */
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,
452 i
= dttofmtasc_replace(ts
, dDate
, dow
, tm
,
458 /* day with leading spaces (01 - 31) */
460 replace_val
.uint_val
= tm
->tm_mday
;
461 replace_type
= PGTYPES_TYPE_UINT_2_LS
;
465 * alternative format modifier
477 * strftime's month is 0 based, ours is 1 based
480 i
= strftime(q
, *pstr_len
, tmp
, tm
);
489 replace_type
= PGTYPES_TYPE_NOTHING
;
494 * The ISO 8601 year with century as a decimal number. The
495 * 4-digit year corresponding to the ISO week number.
499 i
= strftime(q
, *pstr_len
, "%G", tm
);
508 replace_type
= PGTYPES_TYPE_NOTHING
;
512 * Like %G, but without century, i.e., with a 2-digit year
517 const char *fmt
= "%g"; /* Keep compiler quiet about
521 i
= strftime(q
, *pstr_len
, fmt
, tm
);
530 replace_type
= PGTYPES_TYPE_NOTHING
;
533 /* hour (24 hour clock) with leading zeroes */
535 replace_val
.uint_val
= tm
->tm_hour
;
536 replace_type
= PGTYPES_TYPE_UINT_2_LZ
;
538 /* hour (12 hour clock) with leading zeroes */
540 replace_val
.uint_val
= tm
->tm_hour
% 12;
541 replace_type
= PGTYPES_TYPE_UINT_2_LZ
;
545 * The day of the year as a decimal number with leading
546 * zeroes. It ranges from 001 to 366.
549 replace_val
.uint_val
= tm
->tm_yday
;
550 replace_type
= PGTYPES_TYPE_UINT_3_LZ
;
554 * The hour (24 hour clock). Leading zeroes will be turned
558 replace_val
.uint_val
= tm
->tm_hour
;
559 replace_type
= PGTYPES_TYPE_UINT_2_LS
;
563 * The hour (12 hour clock). Leading zeroes will be turned
567 replace_val
.uint_val
= tm
->tm_hour
% 12;
568 replace_type
= PGTYPES_TYPE_UINT_2_LS
;
570 /* The month as a decimal number with a leading zero */
572 replace_val
.uint_val
= tm
->tm_mon
;
573 replace_type
= PGTYPES_TYPE_UINT_2_LZ
;
575 /* The minute as a decimal number with a leading zero */
577 replace_val
.uint_val
= tm
->tm_min
;
578 replace_type
= PGTYPES_TYPE_UINT_2_LZ
;
580 /* A newline character */
582 replace_val
.char_val
= '\n';
583 replace_type
= PGTYPES_TYPE_CHAR
;
585 /* the AM/PM specifier (uppercase) */
586 /* XXX should be locale aware */
588 if (tm
->tm_hour
< 12)
589 replace_val
.str_val
= "AM";
591 replace_val
.str_val
= "PM";
592 replace_type
= PGTYPES_TYPE_STRING_CONSTANT
;
594 /* the AM/PM specifier (lowercase) */
595 /* XXX should be locale aware */
597 if (tm
->tm_hour
< 12)
598 replace_val
.str_val
= "am";
600 replace_val
.str_val
= "pm";
601 replace_type
= PGTYPES_TYPE_STRING_CONSTANT
;
603 /* the time in the format %I:%M:%S %p */
604 /* XXX should be locale aware */
606 i
= dttofmtasc_replace(ts
, dDate
, dow
, tm
,
612 /* The time in 24 hour notation (%H:%M) */
614 i
= dttofmtasc_replace(ts
, dDate
, dow
, tm
,
620 /* The number of seconds since the Epoch (1970-01-01) */
622 #ifdef HAVE_INT64_TIMESTAMP
623 replace_val
.int64_val
= (*ts
- SetEpochTimestamp()) / 1000000.0;
624 replace_type
= PGTYPES_TYPE_INT64
;
626 replace_val
.double_val
= *ts
- SetEpochTimestamp();
627 replace_type
= PGTYPES_TYPE_DOUBLE_NF
;
630 /* seconds as a decimal number with leading zeroes */
632 replace_val
.uint_val
= tm
->tm_sec
;
633 replace_type
= PGTYPES_TYPE_UINT_2_LZ
;
637 replace_val
.char_val
= '\t';
638 replace_type
= PGTYPES_TYPE_CHAR
;
640 /* The time in 24 hour notation (%H:%M:%S) */
642 i
= dttofmtasc_replace(ts
, dDate
, dow
, tm
,
650 * The day of the week as a decimal, Monday = 1, Sunday =
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
;
659 /* The week number of the year as a decimal number */
662 i
= strftime(q
, *pstr_len
, "%U", tm
);
671 replace_type
= PGTYPES_TYPE_NOTHING
;
675 * The ISO 8601:1988 week number of the current year as a
679 i
= strftime(q
, *pstr_len
, "%V", tm
);
687 replace_type
= PGTYPES_TYPE_NOTHING
;
691 * The day of the week as a decimal, Sunday being 0 and
695 replace_val
.uint_val
= dow
;
696 replace_type
= PGTYPES_TYPE_UINT
;
698 /* The week number of the year (another definition) */
701 i
= strftime(q
, *pstr_len
, "%U", tm
);
710 replace_type
= PGTYPES_TYPE_NOTHING
;
714 * The preferred date representation for the current
715 * locale without the time.
719 const char *fmt
= "%x"; /* Keep compiler quiet about
723 i
= strftime(q
, *pstr_len
, fmt
, tm
);
732 replace_type
= PGTYPES_TYPE_NOTHING
;
737 * The preferred time representation for the current
738 * locale without the date.
742 i
= strftime(q
, *pstr_len
, "%X", tm
);
751 replace_type
= PGTYPES_TYPE_NOTHING
;
753 /* The year without the century (2 digits, leading zeroes) */
755 replace_val
.uint_val
= tm
->tm_year
% 100;
756 replace_type
= PGTYPES_TYPE_UINT_2_LZ
;
758 /* The year with the century (4 digits) */
760 replace_val
.uint_val
= tm
->tm_year
;
761 replace_type
= PGTYPES_TYPE_UINT
;
763 /* The time zone offset from GMT */
766 i
= strftime(q
, *pstr_len
, "%z", tm
);
775 replace_type
= PGTYPES_TYPE_NOTHING
;
777 /* The name or abbreviation of the time zone */
780 i
= strftime(q
, *pstr_len
, "%Z", tm
);
789 replace_type
= PGTYPES_TYPE_NOTHING
;
793 replace_val
.char_val
= '%';
794 replace_type
= PGTYPES_TYPE_CHAR
;
797 /* fmtstr: foo%' - The string ends with a % sign */
800 * this is not compliant to the specification
806 * if we don't know the pattern, we just copy it
830 i
= pgtypes_fmt_replace(replace_val
, replace_type
, &q
, pstr_len
);
853 PGTYPEStimestamp_fmt_asc(timestamp
* ts
, char *output
, int str_len
, char *fmtstr
)
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
;
873 iv
->time
= (*ts1
- *ts2
);
881 PGTYPEStimestamp_defmt_asc(char *str
, char *fmt
, timestamp
* d
)
896 fmt
= "%Y-%m-%d %H:%M:%S";
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? */
916 i
= PGTYPEStimestamp_defmt_scan(&mstr
, mfmt
, d
, &year
, &month
, &day
, &hour
, &minute
, &second
, &tz
);
923 * add an interval to a time stamp
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
))
943 if (span
->month
!= 0)
950 if (timestamp2tm(*tin
, NULL
, tm
, &fsec
, NULL
) != 0)
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)
984 * subtract an interval from a time stamp
988 * returns 0 if successful
989 * returns -1 if it fails
994 PGTYPEStimestamp_sub_interval(timestamp
* tin
, interval
* span
, timestamp
* tout
)
998 tspan
.month
= -span
->month
;
999 tspan
.time
= -span
->time
;
1002 return PGTYPEStimestamp_add_interval(tin
, &tspan
, tout
);