postgresql/src/interfaces/ecpg/pgtypeslib/timestamp.c

932 lines
20 KiB
C
Raw Normal View History

/*
2010-09-20 22:08:53 +02:00
* src/interfaces/ecpg/pgtypeslib/timestamp.c
*/
#include "postgres_fe.h"
2003-03-20 16:56:50 +01:00
#include <time.h>
#include <limits.h>
#include <math.h>
2003-03-20 16:56:50 +01:00
#ifdef __FAST_MATH__
#error -ffast-math is known to break this code
#endif
#include "pgtypeslib_extern.h"
2003-06-25 19:55:50 +02:00
#include "dt.h"
2003-03-20 16:56:50 +01:00
#include "pgtypes_timestamp.h"
#include "pgtypes_date.h"
2003-03-20 16:56:50 +01:00
2003-03-20 16:56:50 +01:00
static int64
time2t(const int hour, const int min, const int sec, const fsec_t fsec)
{
return (((((hour * MINS_PER_HOUR) + min) * SECS_PER_MINUTE) + sec) * USECS_PER_SEC) + fsec;
2017-06-21 20:39:04 +02:00
} /* time2t() */
2003-03-20 16:56:50 +01:00
static timestamp
dt2local(timestamp dt, int tz)
2003-03-20 16:56:50 +01:00
{
2005-05-23 23:54:02 +02:00
dt -= (tz * USECS_PER_SEC);
return dt;
2017-06-21 20:39:04 +02:00
} /* dt2local() */
2003-03-20 16:56:50 +01:00
/* tm2timestamp()
* Convert a tm structure to a timestamp data type.
* Note that year is _not_ 1900-based, but is an explicit full value.
* Also, month is one-based, _not_ zero-based.
*
* Returns -1 on failure (overflow).
2003-03-20 16:56:50 +01:00
*/
int
2017-06-21 20:39:04 +02:00
tm2timestamp(struct tm *tm, fsec_t fsec, int *tzp, timestamp * result)
2003-03-20 16:56:50 +01:00
{
2004-08-29 07:07:03 +02:00
int dDate;
2003-03-20 16:56:50 +01:00
int64 time;
/* Prevent overflow in Julian-day routines */
2003-03-20 16:56:50 +01:00
if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
return -1;
dDate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1);
2003-03-20 16:56:50 +01:00
time = time2t(tm->tm_hour, tm->tm_min, tm->tm_sec, fsec);
*result = (dDate * USECS_PER_DAY) + time;
/* check for major overflow */
if ((*result - time) / USECS_PER_DAY != dDate)
return -1;
/* check for just-barely overflow (okay except time-of-day wraps) */
/* caution: we want to allow 1999-12-31 24:00:00 */
if ((*result < 0 && dDate > 0) ||
(*result > 0 && dDate < -1))
return -1;
2003-03-20 16:56:50 +01:00
if (tzp != NULL)
*result = dt2local(*result, -(*tzp));
/* final range check catches just-out-of-range timestamps */
if (!IS_VALID_TIMESTAMP(*result))
return -1;
2003-03-20 16:56:50 +01:00
return 0;
2017-06-21 20:39:04 +02:00
} /* tm2timestamp() */
2003-03-20 16:56:50 +01:00
static timestamp
2003-03-20 16:56:50 +01:00
SetEpochTimestamp(void)
{
int64 noresult = 0;
timestamp dt;
2003-08-04 02:43:34 +02:00
struct tm tt,
*tm = &tt;
2003-03-20 16:56:50 +01:00
if (GetEpochTime(tm) < 0)
return noresult;
2003-03-20 16:56:50 +01:00
tm2timestamp(tm, 0, NULL, &dt);
return dt;
2017-06-21 20:39:04 +02:00
} /* SetEpochTimestamp() */
2003-03-20 16:56:50 +01:00
/* timestamp2tm()
* Convert timestamp data type to POSIX time structure.
* Note that year is _not_ 1900-based, but is an explicit full value.
* Also, month is one-based, _not_ zero-based.
* Returns:
* 0 on success
* -1 on out of range
*
* For dates within the system-supported time_t range, convert to the
* local time zone. If out of this range, leave as GMT. - tgl 97/05/27
*/
static int
2017-06-21 20:39:04 +02:00
timestamp2tm(timestamp dt, int *tzp, struct tm *tm, fsec_t *fsec, const char **tzn)
2003-03-20 16:56:50 +01:00
{
int64 dDate,
2004-08-29 07:07:03 +02:00
date0;
2003-03-20 16:56:50 +01:00
int64 time;
#if defined(HAVE_STRUCT_TM_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
time_t utime;
2003-03-20 16:56:50 +01:00
struct tm *tx;
#endif
date0 = date2j(2000, 1, 1);
time = dt;
TMODULO(time, dDate, USECS_PER_DAY);
2003-03-20 16:56:50 +01:00
if (time < INT64CONST(0))
{
time += USECS_PER_DAY;
dDate -= 1;
2003-03-20 16:56:50 +01:00
}
/* add offset to go from J2000 back to standard Julian date */
dDate += date0;
/* Julian day routine does not work for negative Julian days */
if (dDate < 0 || dDate > (timestamp) INT_MAX)
return -1;
j2date((int) dDate, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
2003-03-20 16:56:50 +01:00
if (tzp != NULL)
{
/*
* Does this fall within the capabilities of the localtime()
* interface? Then use this to rotate to the local time zone.
*/
if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
{
#if defined(HAVE_STRUCT_TM_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
utime = dt / USECS_PER_SEC +
((date0 - date2j(1970, 1, 1)) * INT64CONST(86400));
2003-03-20 16:56:50 +01:00
tx = localtime(&utime);
tm->tm_year = tx->tm_year + 1900;
tm->tm_mon = tx->tm_mon + 1;
tm->tm_mday = tx->tm_mday;
tm->tm_hour = tx->tm_hour;
tm->tm_min = tx->tm_min;
tm->tm_isdst = tx->tm_isdst;
#if defined(HAVE_STRUCT_TM_TM_ZONE)
2003-03-20 16:56:50 +01:00
tm->tm_gmtoff = tx->tm_gmtoff;
tm->tm_zone = tx->tm_zone;
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
*tzp = -tm->tm_gmtoff; /* tm_gmtoff is Sun/DEC-ism */
2003-03-20 16:56:50 +01:00
if (tzn != NULL)
*tzn = tm->tm_zone;
2003-03-20 16:56:50 +01:00
#elif defined(HAVE_INT_TIMEZONE)
*tzp = (tm->tm_isdst > 0) ? TIMEZONE_GLOBAL - SECS_PER_HOUR : TIMEZONE_GLOBAL;
2003-03-20 16:56:50 +01:00
if (tzn != NULL)
*tzn = TZNAME_GLOBAL[(tm->tm_isdst > 0)];
2003-03-20 16:56:50 +01:00
#endif
#else /* not (HAVE_STRUCT_TM_TM_ZONE || HAVE_INT_TIMEZONE) */
2003-03-20 16:56:50 +01:00
*tzp = 0;
/* Mark this as *no* time zone available */
tm->tm_isdst = -1;
if (tzn != NULL)
*tzn = NULL;
#endif
}
else
{
*tzp = 0;
/* Mark this as *no* time zone available */
tm->tm_isdst = -1;
if (tzn != NULL)
*tzn = NULL;
}
}
else
{
tm->tm_isdst = -1;
if (tzn != NULL)
*tzn = NULL;
}
tm->tm_yday = dDate - date2j(tm->tm_year, 1, 1) + 1;
2003-03-20 16:56:50 +01:00
return 0;
2017-06-21 20:39:04 +02:00
} /* timestamp2tm() */
2003-03-20 16:56:50 +01:00
/* EncodeSpecialTimestamp()
2003-08-04 02:43:34 +02:00
* * Convert reserved timestamp data type to string.
* */
static void
EncodeSpecialTimestamp(timestamp dt, char *str)
2003-03-20 16:56:50 +01:00
{
if (TIMESTAMP_IS_NOBEGIN(dt))
strcpy(str, EARLY);
else if (TIMESTAMP_IS_NOEND(dt))
strcpy(str, LATE);
else
abort(); /* shouldn't happen */
}
2003-03-20 16:56:50 +01:00
timestamp
PGTYPEStimestamp_from_asc(char *str, char **endptr)
2003-03-20 16:56:50 +01:00
{
timestamp result;
2003-03-20 16:56:50 +01:00
int64 noresult = 0;
fsec_t fsec;
2003-08-04 02:43:34 +02:00
struct tm tt,
*tm = &tt;
int dtype;
int nf;
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
2003-03-20 16:56:50 +01:00
char lowstr[MAXDATELEN + MAXDATEFIELDS];
2003-08-04 02:43:34 +02:00
char *realptr;
char **ptr = (endptr != NULL) ? endptr : &realptr;
2003-03-20 16:56:50 +01:00
if (strlen(str) > MAXDATELEN)
2003-03-20 16:56:50 +01:00
{
errno = PGTYPES_TS_BAD_TIMESTAMP;
return noresult;
2003-03-20 16:56:50 +01:00
}
if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, 0) != 0)
2003-03-20 16:56:50 +01:00
{
2003-08-04 02:43:34 +02:00
errno = PGTYPES_TS_BAD_TIMESTAMP;
return noresult;
2003-08-04 02:43:34 +02:00
}
2003-03-20 16:56:50 +01:00
switch (dtype)
{
case DTK_DATE:
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
{
errno = PGTYPES_TS_BAD_TIMESTAMP;
return noresult;
2003-03-20 16:56:50 +01:00
}
break;
case DTK_EPOCH:
result = SetEpochTimestamp();
break;
case DTK_LATE:
TIMESTAMP_NOEND(result);
break;
case DTK_EARLY:
TIMESTAMP_NOBEGIN(result);
break;
default:
errno = PGTYPES_TS_BAD_TIMESTAMP;
return noresult;
2003-03-20 16:56:50 +01:00
}
/* AdjustTimestampForTypmod(&result, typmod); */
2004-08-29 07:07:03 +02:00
/*
2005-10-15 04:49:52 +02:00
* Since it's difficult to test for noresult, make sure errno is 0 if no
* error occurred.
2004-08-29 07:07:03 +02:00
*/
errno = 0;
2003-03-20 16:56:50 +01:00
return result;
}
char *
PGTYPEStimestamp_to_asc(timestamp tstamp)
2003-03-20 16:56:50 +01:00
{
2003-08-04 02:43:34 +02:00
struct tm tt,
*tm = &tt;
char buf[MAXDATELEN + 1];
fsec_t fsec;
int DateStyle = 1; /* this defaults to USE_ISO_DATES, shall we
* make it an option? */
2003-03-20 16:56:50 +01:00
if (TIMESTAMP_NOT_FINITE(tstamp))
EncodeSpecialTimestamp(tstamp, buf);
else if (timestamp2tm(tstamp, NULL, tm, &fsec, NULL) == 0)
EncodeDateTime(tm, fsec, false, 0, NULL, DateStyle, buf, 0);
else
2003-03-20 16:56:50 +01:00
{
errno = PGTYPES_TS_BAD_TIMESTAMP;
2003-03-20 16:56:50 +01:00
return NULL;
}
return pgtypes_strdup(buf);
}
void
2005-10-15 04:49:52 +02:00
PGTYPEStimestamp_current(timestamp * ts)
{
struct tm tm;
2003-08-04 02:43:34 +02:00
GetCurrentDateTime(&tm);
if (errno == 0)
tm2timestamp(&tm, 0, NULL, ts);
return;
}
static int
2017-06-21 20:39:04 +02:00
dttofmtasc_replace(timestamp * ts, date dDate, int dow, struct tm *tm,
char *output, int *pstr_len, const char *fmtstr)
{
union un_fmt_comb replace_val;
2003-08-04 02:43:34 +02:00
int replace_type;
int i;
const char *p = fmtstr;
2003-08-04 02:43:34 +02:00
char *q = output;
2003-08-04 02:43:34 +02:00
while (*p)
{
if (*p == '%')
{
p++;
/* fix compiler warning */
replace_type = PGTYPES_TYPE_NOTHING;
2003-08-04 02:43:34 +02:00
switch (*p)
{
2006-10-04 02:30:14 +02:00
/* the abbreviated name of the day in the week */
/* XXX should be locale aware */
case 'a':
replace_val.str_val = pgtypes_date_weekdays_short[dow];
replace_type = PGTYPES_TYPE_STRING_CONSTANT;
break;
2006-10-04 02:30:14 +02:00
/* the full name of the day in the week */
/* XXX should be locale aware */
case 'A':
replace_val.str_val = days[dow];
replace_type = PGTYPES_TYPE_STRING_CONSTANT;
break;
2006-10-04 02:30:14 +02:00
/* the abbreviated name of the month */
/* XXX should be locale aware */
case 'b':
case 'h':
replace_val.str_val = months[tm->tm_mon];
replace_type = PGTYPES_TYPE_STRING_CONSTANT;
break;
2014-10-20 16:33:16 +02:00
/* the full name of the month */
2006-10-04 02:30:14 +02:00
/* XXX should be locale aware */
case 'B':
replace_val.str_val = pgtypes_date_months[tm->tm_mon];
replace_type = PGTYPES_TYPE_STRING_CONSTANT;
break;
2006-10-04 02:30:14 +02:00
/*
* The preferred date and time representation for
* the current locale.
*/
case 'c':
/* XXX */
break;
2006-10-04 02:30:14 +02:00
/* the century number with leading zeroes */
case 'C':
2003-09-01 14:37:40 +02:00
replace_val.uint_val = tm->tm_year / 100;
replace_type = PGTYPES_TYPE_UINT_2_LZ;
break;
2006-10-04 02:30:14 +02:00
/* day with leading zeroes (01 - 31) */
case 'd':
replace_val.uint_val = tm->tm_mday;
replace_type = PGTYPES_TYPE_UINT_2_LZ;
break;
2006-10-04 02:30:14 +02:00
/* the date in the format mm/dd/yy */
case 'D':
2003-08-04 02:43:34 +02:00
/*
2005-10-15 04:49:52 +02:00
* ts, dDate, dow, tm is information about the timestamp
*
2003-08-04 02:43:34 +02:00
* q is the start of the current output buffer
*
2003-08-04 02:43:34 +02:00
* pstr_len is a pointer to the remaining size of output,
* i.e. the size of q
*/
i = dttofmtasc_replace(ts, dDate, dow, tm,
2003-08-04 02:43:34 +02:00
q, pstr_len,
"%m/%d/%y");
if (i)
return i;
break;
2006-10-04 02:30:14 +02:00
/* day with leading spaces (01 - 31) */
case 'e':
replace_val.uint_val = tm->tm_mday;
replace_type = PGTYPES_TYPE_UINT_2_LS;
break;
2006-10-04 02:30:14 +02:00
/*
* alternative format modifier
*/
case 'E':
{
2003-08-04 02:43:34 +02:00
char tmp[4] = "%Ex";
p++;
2003-08-04 02:43:34 +02:00
if (*p == '\0')
return -1;
tmp[2] = *p;
2006-10-04 02:30:14 +02:00
2003-08-04 02:43:34 +02:00
/*
* strftime's month is 0 based, ours is 1 based
*/
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, tmp, tm);
2003-08-04 02:43:34 +02:00
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
break;
}
2006-10-04 02:30:14 +02:00
/*
* The ISO 8601 year with century as a decimal number. The
* 4-digit year corresponding to the ISO week number.
*/
case 'G':
2003-08-04 02:43:34 +02:00
{
/* Keep compiler quiet - Don't use a literal format */
2011-06-09 20:32:50 +02:00
const char *fmt = "%G";
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, fmt, tm);
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
}
break;
2006-10-04 02:30:14 +02:00
/*
* Like %G, but without century, i.e., with a 2-digit year
* (00-99).
*/
case 'g':
{
const char *fmt = "%g"; /* Keep compiler quiet about
2005-10-15 04:49:52 +02:00
* 2-digit year */
2003-08-04 02:43:34 +02:00
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, fmt, tm);
2003-08-04 02:43:34 +02:00
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
}
break;
2006-10-04 02:30:14 +02:00
/* hour (24 hour clock) with leading zeroes */
case 'H':
replace_val.uint_val = tm->tm_hour;
replace_type = PGTYPES_TYPE_UINT_2_LZ;
break;
2006-10-04 02:30:14 +02:00
/* hour (12 hour clock) with leading zeroes */
case 'I':
replace_val.uint_val = tm->tm_hour % 12;
replace_type = PGTYPES_TYPE_UINT_2_LZ;
break;
2006-10-04 02:30:14 +02:00
/*
* The day of the year as a decimal number with leading
* zeroes. It ranges from 001 to 366.
*/
case 'j':
replace_val.uint_val = tm->tm_yday;
replace_type = PGTYPES_TYPE_UINT_3_LZ;
break;
2006-10-04 02:30:14 +02:00
/*
* The hour (24 hour clock). Leading zeroes will be turned
* into spaces.
*/
case 'k':
replace_val.uint_val = tm->tm_hour;
replace_type = PGTYPES_TYPE_UINT_2_LS;
break;
2006-10-04 02:30:14 +02:00
/*
* The hour (12 hour clock). Leading zeroes will be turned
* into spaces.
*/
case 'l':
replace_val.uint_val = tm->tm_hour % 12;
replace_type = PGTYPES_TYPE_UINT_2_LS;
break;
2006-10-04 02:30:14 +02:00
/* The month as a decimal number with a leading zero */
case 'm':
replace_val.uint_val = tm->tm_mon;
replace_type = PGTYPES_TYPE_UINT_2_LZ;
break;
2006-10-04 02:30:14 +02:00
/* The minute as a decimal number with a leading zero */
case 'M':
replace_val.uint_val = tm->tm_min;
replace_type = PGTYPES_TYPE_UINT_2_LZ;
break;
2006-10-04 02:30:14 +02:00
/* A newline character */
case 'n':
replace_val.char_val = '\n';
replace_type = PGTYPES_TYPE_CHAR;
break;
2006-10-04 02:30:14 +02:00
/* the AM/PM specifier (uppercase) */
/* XXX should be locale aware */
case 'p':
2003-08-04 02:43:34 +02:00
if (tm->tm_hour < 12)
replace_val.str_val = "AM";
2003-08-04 02:43:34 +02:00
else
replace_val.str_val = "PM";
replace_type = PGTYPES_TYPE_STRING_CONSTANT;
break;
2006-10-04 02:30:14 +02:00
/* the AM/PM specifier (lowercase) */
/* XXX should be locale aware */
case 'P':
2003-08-04 02:43:34 +02:00
if (tm->tm_hour < 12)
replace_val.str_val = "am";
2003-08-04 02:43:34 +02:00
else
replace_val.str_val = "pm";
replace_type = PGTYPES_TYPE_STRING_CONSTANT;
break;
2006-10-04 02:30:14 +02:00
/* the time in the format %I:%M:%S %p */
/* XXX should be locale aware */
case 'r':
i = dttofmtasc_replace(ts, dDate, dow, tm,
2003-08-04 02:43:34 +02:00
q, pstr_len,
"%I:%M:%S %p");
if (i)
return i;
break;
2006-10-04 02:30:14 +02:00
/* The time in 24 hour notation (%H:%M) */
case 'R':
i = dttofmtasc_replace(ts, dDate, dow, tm,
2003-08-04 02:43:34 +02:00
q, pstr_len,
"%H:%M");
if (i)
return i;
break;
2006-10-04 02:30:14 +02:00
/* The number of seconds since the Epoch (1970-01-01) */
case 's':
replace_val.int64_val = (*ts - SetEpochTimestamp()) / 1000000.0;
replace_type = PGTYPES_TYPE_INT64;
break;
2006-10-04 02:30:14 +02:00
/* seconds as a decimal number with leading zeroes */
case 'S':
replace_val.uint_val = tm->tm_sec;
replace_type = PGTYPES_TYPE_UINT_2_LZ;
break;
2006-10-04 02:30:14 +02:00
/* A tabulator */
case 't':
replace_val.char_val = '\t';
replace_type = PGTYPES_TYPE_CHAR;
break;
2006-10-04 02:30:14 +02:00
/* The time in 24 hour notation (%H:%M:%S) */
case 'T':
i = dttofmtasc_replace(ts, dDate, dow, tm,
2003-08-04 02:43:34 +02:00
q, pstr_len,
"%H:%M:%S");
if (i)
return i;
break;
2006-10-04 02:30:14 +02:00
/*
* The day of the week as a decimal, Monday = 1, Sunday =
* 7
*/
case 'u':
replace_val.uint_val = dow;
if (replace_val.uint_val == 0)
replace_val.uint_val = 7;
replace_type = PGTYPES_TYPE_UINT;
break;
2006-10-04 02:30:14 +02:00
/* The week number of the year as a decimal number */
case 'U':
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, "%U", tm);
2003-08-04 02:43:34 +02:00
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
break;
2006-10-04 02:30:14 +02:00
/*
* The ISO 8601:1988 week number of the current year as a
* decimal number.
*/
case 'V':
2003-08-04 02:43:34 +02:00
{
/* Keep compiler quiet - Don't use a literal format */
2011-06-09 20:32:50 +02:00
const char *fmt = "%V";
i = strftime(q, *pstr_len, fmt, tm);
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
replace_type = PGTYPES_TYPE_NOTHING;
}
break;
2006-10-04 02:30:14 +02:00
/*
* The day of the week as a decimal, Sunday being 0 and
* Monday 1.
*/
case 'w':
replace_val.uint_val = dow;
replace_type = PGTYPES_TYPE_UINT;
break;
2006-10-04 02:30:14 +02:00
/* The week number of the year (another definition) */
case 'W':
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, "%U", tm);
2003-08-04 02:43:34 +02:00
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
break;
2006-10-04 02:30:14 +02:00
/*
* The preferred date representation for the current
* locale without the time.
*/
case 'x':
{
const char *fmt = "%x"; /* Keep compiler quiet about
2005-10-15 04:49:52 +02:00
* 2-digit year */
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, fmt, tm);
2003-08-04 02:43:34 +02:00
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
}
break;
2006-10-04 02:30:14 +02:00
/*
* The preferred time representation for the current
* locale without the date.
*/
case 'X':
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, "%X", tm);
2003-08-04 02:43:34 +02:00
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
break;
2006-10-04 02:30:14 +02:00
/* The year without the century (2 digits, leading zeroes) */
case 'y':
replace_val.uint_val = tm->tm_year % 100;
replace_type = PGTYPES_TYPE_UINT_2_LZ;
break;
2006-10-04 02:30:14 +02:00
/* The year with the century (4 digits) */
case 'Y':
2003-09-01 14:37:40 +02:00
replace_val.uint_val = tm->tm_year;
replace_type = PGTYPES_TYPE_UINT;
break;
2006-10-04 02:30:14 +02:00
/* The time zone offset from GMT */
case 'z':
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, "%z", tm);
2003-08-04 02:43:34 +02:00
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
break;
2006-10-04 02:30:14 +02:00
/* The name or abbreviation of the time zone */
case 'Z':
tm->tm_mon -= 1;
i = strftime(q, *pstr_len, "%Z", tm);
2003-08-04 02:43:34 +02:00
if (i == 0)
return -1;
while (*q)
{
q++;
(*pstr_len)--;
}
tm->tm_mon += 1;
replace_type = PGTYPES_TYPE_NOTHING;
break;
2006-10-04 02:30:14 +02:00
/* A % sign */
case '%':
replace_val.char_val = '%';
replace_type = PGTYPES_TYPE_CHAR;
break;
case '\0':
/* fmtstr: foo%' - The string ends with a % sign */
2006-10-04 02:30:14 +02:00
2003-08-04 02:43:34 +02:00
/*
* this is not compliant to the specification
*/
return -1;
default:
2006-10-04 02:30:14 +02:00
2003-08-04 02:43:34 +02:00
/*
* if we don't know the pattern, we just copy it
*/
if (*pstr_len > 1)
{
*q = '%';
2003-08-04 02:43:34 +02:00
q++;
(*pstr_len)--;
if (*pstr_len > 1)
{
*q = *p;
2003-08-04 02:43:34 +02:00
q++;
(*pstr_len)--;
}
else
{
*q = '\0';
return -1;
}
*q = '\0';
}
2003-08-04 02:43:34 +02:00
else
return -1;
break;
}
i = pgtypes_fmt_replace(replace_val, replace_type, &q, pstr_len);
2003-08-04 02:43:34 +02:00
if (i)
return i;
2003-08-04 02:43:34 +02:00
}
else
{
if (*pstr_len > 1)
{
*q = *p;
(*pstr_len)--;
q++;
*q = '\0';
}
2003-08-04 02:43:34 +02:00
else
return -1;
}
p++;
}
return 0;
}
int
PGTYPEStimestamp_fmt_asc(timestamp * ts, char *output, int str_len, const char *fmtstr)
{
2003-08-04 02:43:34 +02:00
struct tm tm;
fsec_t fsec;
date dDate;
2003-08-04 02:43:34 +02:00
int dow;
dDate = PGTYPESdate_from_timestamp(*ts);
dow = PGTYPESdate_dayofweek(dDate);
timestamp2tm(*ts, NULL, &tm, &fsec, NULL);
return dttofmtasc_replace(ts, dDate, dow, &tm, output, &str_len, fmtstr);
}
int
2005-10-15 04:49:52 +02:00
PGTYPEStimestamp_sub(timestamp * ts1, timestamp * ts2, interval * iv)
{
if (TIMESTAMP_NOT_FINITE(*ts1) || TIMESTAMP_NOT_FINITE(*ts2))
return PGTYPES_TS_ERR_EINFTIME;
else
iv->time = (*ts1 - *ts2);
iv->month = 0;
return 0;
2003-03-20 16:56:50 +01:00
}
2003-08-04 02:43:34 +02:00
int
PGTYPEStimestamp_defmt_asc(const char *str, const char *fmt, timestamp * d)
2003-08-04 02:43:34 +02:00
{
int year,
month,
day;
int hour,
minute,
second;
int tz;
int i;
char *mstr;
char *mfmt;
if (!fmt)
fmt = "%Y-%m-%d %H:%M:%S";
2003-08-04 02:43:34 +02:00
if (!fmt[0])
return 1;
mstr = pgtypes_strdup(str);
mfmt = pgtypes_strdup(fmt);
2003-08-04 02:43:34 +02:00
/*
* initialize with impossible values so that we can see if the fields
* where specified at all
*/
/* XXX ambiguity with 1 BC for year? */
2003-08-04 02:43:34 +02:00
year = -1;
month = -1;
day = -1;
hour = 0;
minute = -1;
second = -1;
tz = 0;
i = PGTYPEStimestamp_defmt_scan(&mstr, mfmt, d, &year, &month, &day, &hour, &minute, &second, &tz);
free(mstr);
free(mfmt);
return i;
}
/*
* add an interval to a time stamp
*
2005-10-15 04:49:52 +02:00
* *tout = tin + span
*
2005-10-15 04:49:52 +02:00
* returns 0 if successful
* returns -1 if it fails
*
*/
2005-10-15 04:49:52 +02:00
int
2005-10-15 04:49:52 +02:00
PGTYPEStimestamp_add_interval(timestamp * tin, interval * span, timestamp * tout)
{
2005-10-15 04:49:52 +02:00
if (TIMESTAMP_NOT_FINITE(*tin))
*tout = *tin;
else
{
if (span->month != 0)
{
struct tm tt,
*tm = &tt;
fsec_t fsec;
if (timestamp2tm(*tin, NULL, tm, &fsec, NULL) != 0)
return -1;
tm->tm_mon += span->month;
if (tm->tm_mon > MONTHS_PER_YEAR)
{
tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
tm->tm_mon = (tm->tm_mon - 1) % MONTHS_PER_YEAR + 1;
}
else if (tm->tm_mon < 1)
{
tm->tm_year += tm->tm_mon / MONTHS_PER_YEAR - 1;
tm->tm_mon = tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR;
}
/* adjust for end of month boundary problems... */
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]);
if (tm2timestamp(tm, fsec, NULL, tin) != 0)
return -1;
}
*tin += span->time;
*tout = *tin;
}
return 0;
}
2005-10-15 04:49:52 +02:00
/*
* subtract an interval from a time stamp
*
2005-10-15 04:49:52 +02:00
* *tout = tin - span
*
2005-10-15 04:49:52 +02:00
* returns 0 if successful
* returns -1 if it fails
*
*/
2005-10-15 04:49:52 +02:00
int
2005-10-15 04:49:52 +02:00
PGTYPEStimestamp_sub_interval(timestamp * tin, interval * span, timestamp * tout)
{
2005-10-15 04:49:52 +02:00
interval tspan;
tspan.month = -span->month;
tspan.time = -span->time;
2005-10-15 04:49:52 +02:00
return PGTYPEStimestamp_add_interval(tin, &tspan, tout);
}