#include "postgres_fe.h"#include <time.h>#include <ctype.h>#include <math.h>#include "extern.h"#include "dt.h"#include "pgtypes_timestamp.h"
Go to the source code of this file.
Defines | |
| #define | ABS_SIGNBIT ((char) 0200) |
| #define | POS(n) (n) |
| #define | NEG(n) ((n)|ABS_SIGNBIT) |
| #define | FROMVAL(tp) (-SIGNEDCHAR((tp)->value) * 15) |
| #define | VALMASK ((char) 0177) |
| #define | SIGNEDCHAR(c) ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c)) |
Typedefs | |
| typedef long | AbsoluteTime |
Functions | |
| static datetkn * | datebsearch (char *key, datetkn *base, unsigned int nel) |
| int | DecodeUnits (int field, char *lowtoken, int *val) |
| int | date2j (int y, int m, int d) |
| void | j2date (int jd, int *year, int *month, int *day) |
| static int | DecodeSpecial (int field, char *lowtoken, int *val) |
| int | EncodeDateOnly (struct tm *tm, int style, char *str, bool EuroDates) |
| void | TrimTrailingZeros (char *str) |
| int | EncodeDateTime (struct tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str, bool EuroDates) |
| int | GetEpochTime (struct tm *tm) |
| static void | abstime2tm (AbsoluteTime _time, int *tzp, struct tm *tm, char **tzn) |
| void | GetCurrentDateTime (struct tm *tm) |
| void | dt2time (double jd, int *hour, int *min, int *sec, fsec_t *fsec) |
| static int | DecodeNumberField (int len, char *str, int fmask, int *tmask, struct tm *tm, fsec_t *fsec, int *is2digits) |
| static int | DecodeNumber (int flen, char *str, int fmask, int *tmask, struct tm *tm, fsec_t *fsec, int *is2digits, bool EuroDates) |
| static int | DecodeDate (char *str, int fmask, int *tmask, struct tm *tm, bool EuroDates) |
| int | DecodeTime (char *str, int *tmask, struct tm *tm, fsec_t *fsec) |
| static int | DecodeTimezone (char *str, int *tzp) |
| static int | DecodePosixTimezone (char *str, int *tzp) |
| int | ParseDateTime (char *timestr, char *lowstr, char **field, int *ftype, int *numfields, char **endstr) |
| int | DecodeDateTime (char **field, int *ftype, int nf, int *dtype, struct tm *tm, fsec_t *fsec, bool EuroDates) |
| static char * | find_end_token (char *str, char *fmt) |
| static int | pgtypes_defmt_scan (union un_fmt_comb *scan_val, int scan_type, char **pstr, char *pfmt) |
| int | PGTYPEStimestamp_defmt_scan (char **, char *, timestamp *, int *, int *, int *, int *, int *, int *, int *) |
Variables | |
| int | day_tab [2][13] |
| static datetkn | datetktbl [] |
| static datetkn | deltatktbl [] |
| static const unsigned int | szdatetktbl = lengthof(datetktbl) |
| static const unsigned int | szdeltatktbl = lengthof(deltatktbl) |
| static datetkn * | datecache [MAXDATEFIELDS] = {NULL} |
| static datetkn * | deltacache [MAXDATEFIELDS] = {NULL} |
| char * | months [] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL} |
| char * | days [] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", NULL} |
| char * | pgtypes_date_weekdays_short [] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL} |
| char * | pgtypes_date_months [] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", NULL} |
| #define ABS_SIGNBIT ((char) 0200) |
Definition at line 19 of file dt_common.c.
| #define FROMVAL | ( | tp | ) | (-SIGNEDCHAR((tp)->value) * 15) |
Definition at line 22 of file dt_common.c.
Referenced by DecodeSpecial(), and DecodeUnits().
| #define NEG | ( | n | ) | ((n)|ABS_SIGNBIT) |
Definition at line 21 of file dt_common.c.
| #define POS | ( | n | ) | (n) |
Definition at line 20 of file dt_common.c.
Definition at line 24 of file dt_common.c.
| #define VALMASK ((char) 0177) |
Definition at line 23 of file dt_common.c.
| typedef long AbsoluteTime |
Definition at line 17 of file dt_common.c.
| static void abstime2tm | ( | AbsoluteTime | _time, | |
| int * | tzp, | |||
| struct tm * | tm, | |||
| char ** | tzn | |||
| ) | [static] |
Definition at line 1035 of file dt_common.c.
References MAXTZLEN, NULL, SECS_PER_HOUR, StrNCpy, TIMEZONE_GLOBAL, and TZNAME_GLOBAL.
{
time_t time = (time_t) _time;
struct tm *tx;
errno = 0;
if (tzp != NULL)
tx = localtime((time_t *) &time);
else
tx = gmtime((time_t *) &time);
if (!tx)
{
errno = PGTYPES_TS_BAD_TIMESTAMP;
return;
}
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_sec = tx->tm_sec;
tm->tm_isdst = tx->tm_isdst;
#if defined(HAVE_TM_ZONE)
tm->tm_gmtoff = tx->tm_gmtoff;
tm->tm_zone = tx->tm_zone;
if (tzp != NULL)
{
/*
* We have a brute force time zone per SQL99? Then use it without
* change since we have already rotated to the time zone.
*/
*tzp = -tm->tm_gmtoff; /* tm_gmtoff is Sun/DEC-ism */
/*
* FreeBSD man pages indicate that this should work - tgl 97/04/23
*/
if (tzn != NULL)
{
/*
* Copy no more than MAXTZLEN bytes of timezone to tzn, in case it
* contains an error message, which doesn't fit in the buffer
*/
StrNCpy(*tzn, tm->tm_zone, MAXTZLEN + 1);
if (strlen(tm->tm_zone) > MAXTZLEN)
tm->tm_isdst = -1;
}
}
else
tm->tm_isdst = -1;
#elif defined(HAVE_INT_TIMEZONE)
if (tzp != NULL)
{
*tzp = (tm->tm_isdst > 0) ? TIMEZONE_GLOBAL - SECS_PER_HOUR : TIMEZONE_GLOBAL;
if (tzn != NULL)
{
/*
* Copy no more than MAXTZLEN bytes of timezone to tzn, in case it
* contains an error message, which doesn't fit in the buffer
*/
StrNCpy(*tzn, TZNAME_GLOBAL[tm->tm_isdst], MAXTZLEN + 1);
if (strlen(TZNAME_GLOBAL[tm->tm_isdst]) > MAXTZLEN)
tm->tm_isdst = -1;
}
}
else
tm->tm_isdst = -1;
#else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
if (tzp != NULL)
{
/* default to UTC */
*tzp = 0;
if (tzn != NULL)
*tzn = NULL;
}
else
tm->tm_isdst = -1;
#endif
}
| int date2j | ( | int | y, | |
| int | m, | |||
| int | d | |||
| ) |
Definition at line 592 of file dt_common.c.
{
int julian;
int century;
if (m > 2)
{
m += 1;
y += 4800;
}
else
{
m += 13;
y += 4799;
}
century = y / 100;
julian = y * 365 - 32167;
julian += y / 4 - century + century / 4;
julian += 7834 * m / 256 + d;
return julian;
} /* date2j() */
Definition at line 513 of file dt_common.c.
References TOKMAXLEN.
Referenced by DecodeSpecial(), and DecodeUnits().
{
if (nel > 0)
{
datetkn *last = base + nel - 1,
*position;
int result;
while (last >= base)
{
position = base + ((last - base) >> 1);
result = key[0] - position->token[0];
if (result == 0)
{
result = strncmp(key, position->token, TOKMAXLEN);
if (result == 0)
return position;
}
if (result < 0)
last = position - 1;
else
base = position + 1;
}
}
return NULL;
}
| static int DecodeDate | ( | char * | str, | |
| int | fmask, | |||
| int * | tmask, | |||
| struct tm * | tm, | |||
| bool | EuroDates | |||
| ) | [static] |
Definition at line 1378 of file dt_common.c.
References ADBC, DecodeNumber(), DecodeSpecial(), DOY, DTK_M, i, IGNORE_DTF, MAXDATEFIELDS, MONTH, NULL, TZ, and val.
Referenced by DecodeDateTime().
{
fsec_t fsec;
int nf = 0;
int i,
len;
int bc = FALSE;
int is2digits = FALSE;
int type,
val,
dmask = 0;
char *field[MAXDATEFIELDS];
/* parse this string... */
while (*str != '\0' && nf < MAXDATEFIELDS)
{
/* skip field separators */
while (!isalnum((unsigned char) *str))
str++;
field[nf] = str;
if (isdigit((unsigned char) *str))
{
while (isdigit((unsigned char) *str))
str++;
}
else if (isalpha((unsigned char) *str))
{
while (isalpha((unsigned char) *str))
str++;
}
/* Just get rid of any non-digit, non-alpha characters... */
if (*str != '\0')
*str++ = '\0';
nf++;
}
#if 0
/* don't allow too many fields */
if (nf > 3)
return -1;
#endif
*tmask = 0;
/* look first for text fields, since that will be unambiguous month */
for (i = 0; i < nf; i++)
{
if (isalpha((unsigned char) *field[i]))
{
type = DecodeSpecial(i, field[i], &val);
if (type == IGNORE_DTF)
continue;
dmask = DTK_M(type);
switch (type)
{
case MONTH:
tm->tm_mon = val;
break;
case ADBC:
bc = (val == BC);
break;
default:
return -1;
}
if (fmask & dmask)
return -1;
fmask |= dmask;
*tmask |= dmask;
/* mark this field as being completed */
field[i] = NULL;
}
}
/* now pick up remaining numeric fields */
for (i = 0; i < nf; i++)
{
if (field[i] == NULL)
continue;
if ((len = strlen(field[i])) <= 0)
return -1;
if (DecodeNumber(len, field[i], fmask, &dmask, tm, &fsec, &is2digits, EuroDates) != 0)
return -1;
if (fmask & dmask)
return -1;
fmask |= dmask;
*tmask |= dmask;
}
if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
return -1;
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
if (bc)
{
if (tm->tm_year > 0)
tm->tm_year = -(tm->tm_year - 1);
else
return -1;
}
else if (is2digits)
{
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
}
return 0;
} /* DecodeDate() */
| int DecodeDateTime | ( | char ** | field, | |
| int * | ftype, | |||
| int | nf, | |||
| int * | dtype, | |||
| struct tm * | tm, | |||
| fsec_t * | fsec, | |||
| bool | EuroDates | |||
| ) |
Definition at line 1852 of file dt_common.c.
References ADBC, AM, AMPM, date2j(), DAY, day_tab, DecodeDate(), DecodeNumber(), DecodeNumberField(), DecodePosixTimezone(), DecodeSpecial(), DecodeTime(), DecodeTimezone(), DOW, dt2time(), DTK_DATE, DTK_DATE_M, DTK_DAY, DTK_HOUR, DTK_JULIAN, DTK_M, DTK_MINUTE, DTK_MONTH, DTK_NOW, DTK_NUMBER, DTK_SECOND, DTK_SPECIAL, DTK_STRING, DTK_TIME, DTK_TIME_M, DTK_TODAY, DTK_TOMORROW, DTK_TZ, DTK_YEAR, DTK_YESTERDAY, DTK_ZULU, DTZ, DTZMOD, GetCurrentDateTime(), HOUR, HR24, i, IGNORE_DTF, isleap, ISOTIME, j2date(), MINUTE, MONTH, NULL, PM, RESERV, SECOND, SECS_PER_DAY, TZ, UNITS, USECS_PER_DAY, val, and YEAR.
{
int fmask = 0,
tmask,
type;
int ptype = 0; /* "prefix type" for ISO y2001m02d04 format */
int i;
int val;
int mer = HR24;
int haveTextMonth = FALSE;
int is2digits = FALSE;
int bc = FALSE;
int t = 0;
int *tzp = &t;
/***
* We'll insist on at least all of the date fields, but initialize the
* remaining fields in case they are not set later...
***/
*dtype = DTK_DATE;
tm->tm_hour = 0;
tm->tm_min = 0;
tm->tm_sec = 0;
*fsec = 0;
/* don't know daylight savings time status apriori */
tm->tm_isdst = -1;
if (tzp != NULL)
*tzp = 0;
for (i = 0; i < nf; i++)
{
switch (ftype[i])
{
case DTK_DATE:
/***
* Integral julian day with attached time zone?
* All other forms with JD will be separated into
* distinct fields, so we handle just this case here.
***/
if (ptype == DTK_JULIAN)
{
char *cp;
int val;
if (tzp == NULL)
return -1;
val = strtol(field[i], &cp, 10);
if (*cp != '-')
return -1;
j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
/* Get the time zone from the end of the string */
if (DecodeTimezone(cp, tzp) != 0)
return -1;
tmask = DTK_DATE_M | DTK_TIME_M | DTK_M(TZ);
ptype = 0;
break;
}
/***
* Already have a date? Then this might be a POSIX time
* zone with an embedded dash (e.g. "PST-3" == "EST") or
* a run-together time with trailing time zone (e.g. hhmmss-zz).
* - thomas 2001-12-25
***/
else if (((fmask & DTK_DATE_M) == DTK_DATE_M)
|| (ptype != 0))
{
/* No time zone accepted? Then quit... */
if (tzp == NULL)
return -1;
if (isdigit((unsigned char) *field[i]) || ptype != 0)
{
char *cp;
if (ptype != 0)
{
/* Sanity check; should not fail this test */
if (ptype != DTK_TIME)
return -1;
ptype = 0;
}
/*
* Starts with a digit but we already have a time
* field? Then we are in trouble with a date and time
* already...
*/
if ((fmask & DTK_TIME_M) == DTK_TIME_M)
return -1;
if ((cp = strchr(field[i], '-')) == NULL)
return -1;
/* Get the time zone from the end of the string */
if (DecodeTimezone(cp, tzp) != 0)
return -1;
*cp = '\0';
/*
* Then read the rest of the field as a concatenated
* time
*/
if ((ftype[i] = DecodeNumberField(strlen(field[i]), field[i], fmask,
&tmask, tm, fsec, &is2digits)) < 0)
return -1;
/*
* modify tmask after returning from
* DecodeNumberField()
*/
tmask |= DTK_M(TZ);
}
else
{
if (DecodePosixTimezone(field[i], tzp) != 0)
return -1;
ftype[i] = DTK_TZ;
tmask = DTK_M(TZ);
}
}
else if (DecodeDate(field[i], fmask, &tmask, tm, EuroDates) != 0)
return -1;
break;
case DTK_TIME:
if (DecodeTime(field[i], &tmask, tm, fsec) != 0)
return -1;
/*
* Check upper limit on hours; other limits checked in
* DecodeTime()
*/
/* test for > 24:00:00 */
if (tm->tm_hour > 24 ||
(tm->tm_hour == 24 && (tm->tm_min > 0 || tm->tm_sec > 0)))
return -1;
break;
case DTK_TZ:
{
int tz;
if (tzp == NULL)
return -1;
if (DecodeTimezone(field[i], &tz) != 0)
return -1;
/*
* Already have a time zone? Then maybe this is the second
* field of a POSIX time: EST+3 (equivalent to PST)
*/
if (i > 0 && (fmask & DTK_M(TZ)) != 0 &&
ftype[i - 1] == DTK_TZ &&
isalpha((unsigned char) *field[i - 1]))
{
*tzp -= tz;
tmask = 0;
}
else
{
*tzp = tz;
tmask = DTK_M(TZ);
}
}
break;
case DTK_NUMBER:
/*
* Was this an "ISO date" with embedded field labels? An
* example is "y2001m02d04" - thomas 2001-02-04
*/
if (ptype != 0)
{
char *cp;
int val;
val = strtol(field[i], &cp, 10);
/*
* only a few kinds are allowed to have an embedded
* decimal
*/
if (*cp == '.')
switch (ptype)
{
case DTK_JULIAN:
case DTK_TIME:
case DTK_SECOND:
break;
default:
return 1;
break;
}
else if (*cp != '\0')
return -1;
switch (ptype)
{
case DTK_YEAR:
tm->tm_year = val;
tmask = DTK_M(YEAR);
break;
case DTK_MONTH:
/*
* already have a month and hour? then assume
* minutes
*/
if ((fmask & DTK_M(MONTH)) != 0 &&
(fmask & DTK_M(HOUR)) != 0)
{
tm->tm_min = val;
tmask = DTK_M(MINUTE);
}
else
{
tm->tm_mon = val;
tmask = DTK_M(MONTH);
}
break;
case DTK_DAY:
tm->tm_mday = val;
tmask = DTK_M(DAY);
break;
case DTK_HOUR:
tm->tm_hour = val;
tmask = DTK_M(HOUR);
break;
case DTK_MINUTE:
tm->tm_min = val;
tmask = DTK_M(MINUTE);
break;
case DTK_SECOND:
tm->tm_sec = val;
tmask = DTK_M(SECOND);
if (*cp == '.')
{
double frac;
frac = strtod(cp, &cp);
if (*cp != '\0')
return -1;
#ifdef HAVE_INT64_TIMESTAMP
*fsec = frac * 1000000;
#else
*fsec = frac;
#endif
}
break;
case DTK_TZ:
tmask = DTK_M(TZ);
if (DecodeTimezone(field[i], tzp) != 0)
return -1;
break;
case DTK_JULIAN:
/***
* previous field was a label for "julian date"?
***/
tmask = DTK_DATE_M;
j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
/* fractional Julian Day? */
if (*cp == '.')
{
double time;
time = strtod(cp, &cp);
if (*cp != '\0')
return -1;
tmask |= DTK_TIME_M;
#ifdef HAVE_INT64_TIMESTAMP
dt2time((time * USECS_PER_DAY), &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
#else
dt2time((time * SECS_PER_DAY), &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
#endif
}
break;
case DTK_TIME:
/* previous field was "t" for ISO time */
if ((ftype[i] = DecodeNumberField(strlen(field[i]), field[i], (fmask | DTK_DATE_M),
&tmask, tm, fsec, &is2digits)) < 0)
return -1;
if (tmask != DTK_TIME_M)
return -1;
break;
default:
return -1;
break;
}
ptype = 0;
*dtype = DTK_DATE;
}
else
{
char *cp;
int flen;
flen = strlen(field[i]);
cp = strchr(field[i], '.');
/* Embedded decimal and no date yet? */
if (cp != NULL && !(fmask & DTK_DATE_M))
{
if (DecodeDate(field[i], fmask, &tmask, tm, EuroDates) != 0)
return -1;
}
/* embedded decimal and several digits before? */
else if (cp != NULL && flen - strlen(cp) > 2)
{
/*
* Interpret as a concatenated date or time Set the
* type field to allow decoding other fields later.
* Example: 20011223 or 040506
*/
if ((ftype[i] = DecodeNumberField(flen, field[i], fmask,
&tmask, tm, fsec, &is2digits)) < 0)
return -1;
}
else if (flen > 4)
{
if ((ftype[i] = DecodeNumberField(flen, field[i], fmask,
&tmask, tm, fsec, &is2digits)) < 0)
return -1;
}
/* otherwise it is a single date/time field... */
else if (DecodeNumber(flen, field[i], fmask,
&tmask, tm, fsec, &is2digits, EuroDates) != 0)
return -1;
}
break;
case DTK_STRING:
case DTK_SPECIAL:
type = DecodeSpecial(i, field[i], &val);
if (type == IGNORE_DTF)
continue;
tmask = DTK_M(type);
switch (type)
{
case RESERV:
switch (val)
{
case DTK_NOW:
tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ));
*dtype = DTK_DATE;
GetCurrentDateTime(tm);
break;
case DTK_YESTERDAY:
tmask = DTK_DATE_M;
*dtype = DTK_DATE;
GetCurrentDateTime(tm);
j2date(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - 1,
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
tm->tm_hour = 0;
tm->tm_min = 0;
tm->tm_sec = 0;
break;
case DTK_TODAY:
tmask = DTK_DATE_M;
*dtype = DTK_DATE;
GetCurrentDateTime(tm);
tm->tm_hour = 0;
tm->tm_min = 0;
tm->tm_sec = 0;
break;
case DTK_TOMORROW:
tmask = DTK_DATE_M;
*dtype = DTK_DATE;
GetCurrentDateTime(tm);
j2date(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + 1,
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
tm->tm_hour = 0;
tm->tm_min = 0;
tm->tm_sec = 0;
break;
case DTK_ZULU:
tmask = (DTK_TIME_M | DTK_M(TZ));
*dtype = DTK_DATE;
tm->tm_hour = 0;
tm->tm_min = 0;
tm->tm_sec = 0;
if (tzp != NULL)
*tzp = 0;
break;
default:
*dtype = val;
}
break;
case MONTH:
/*
* already have a (numeric) month? then see if we can
* substitute...
*/
if ((fmask & DTK_M(MONTH)) && !haveTextMonth &&
!(fmask & DTK_M(DAY)) && tm->tm_mon >= 1 && tm->tm_mon <= 31)
{
tm->tm_mday = tm->tm_mon;
tmask = DTK_M(DAY);
}
haveTextMonth = TRUE;
tm->tm_mon = val;
break;
case DTZMOD:
/*
* daylight savings time modifier (solves "MET DST"
* syntax)
*/
tmask |= DTK_M(DTZ);
tm->tm_isdst = 1;
if (tzp == NULL)
return -1;
*tzp += val * MINS_PER_HOUR;
break;
case DTZ:
/*
* set mask for TZ here _or_ check for DTZ later when
* getting default timezone
*/
tmask |= DTK_M(TZ);
tm->tm_isdst = 1;
if (tzp == NULL)
return -1;
*tzp = val * MINS_PER_HOUR;
ftype[i] = DTK_TZ;
break;
case TZ:
tm->tm_isdst = 0;
if (tzp == NULL)
return -1;
*tzp = val * MINS_PER_HOUR;
ftype[i] = DTK_TZ;
break;
case IGNORE_DTF:
break;
case AMPM:
mer = val;
break;
case ADBC:
bc = (val == BC);
break;
case DOW:
tm->tm_wday = val;
break;
case UNITS:
tmask = 0;
ptype = val;
break;
case ISOTIME:
/*
* This is a filler field "t" indicating that the next
* field is time. Try to verify that this is sensible.
*/
tmask = 0;
/* No preceding date? Then quit... */
if ((fmask & DTK_DATE_M) != DTK_DATE_M)
return -1;
/***
* We will need one of the following fields:
* DTK_NUMBER should be hhmmss.fff
* DTK_TIME should be hh:mm:ss.fff
* DTK_DATE should be hhmmss-zz
***/
if (i >= nf - 1 ||
(ftype[i + 1] != DTK_NUMBER &&
ftype[i + 1] != DTK_TIME &&
ftype[i + 1] != DTK_DATE))
return -1;
ptype = val;
break;
default:
return -1;
}
break;
default:
return -1;
}
if (tmask & fmask)
return -1;
fmask |= tmask;
}
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
if (bc)
{
if (tm->tm_year > 0)
tm->tm_year = -(tm->tm_year - 1);
else
return -1;
}
else if (is2digits)
{
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
}
if (mer != HR24 && tm->tm_hour > 12)
return -1;
if (mer == AM && tm->tm_hour == 12)
tm->tm_hour = 0;
else if (mer == PM && tm->tm_hour != 12)
tm->tm_hour += 12;
/* do additional checking for full date specs... */
if (*dtype == DTK_DATE)
{
if ((fmask & DTK_DATE_M) != DTK_DATE_M)
return ((fmask & DTK_TIME_M) == DTK_TIME_M) ? 1 : -1;
/*
* check for valid day of month, now that we know for sure the month
* and year...
*/
if (tm->tm_mday < 1 || tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
return -1;
/*
* backend tried to find local timezone here but we don't use the
* result afterwards anyway so we only check for this error: daylight
* savings time modifier but no standard timezone?
*/
if ((fmask & DTK_DATE_M) == DTK_DATE_M && tzp != NULL && !(fmask & DTK_M(TZ)) && (fmask & DTK_M(DTZMOD)))
return -1;
}
return 0;
} /* DecodeDateTime() */
| static int DecodeNumber | ( | int | flen, | |
| char * | str, | |||
| int | fmask, | |||
| int * | tmask, | |||
| struct tm * | tm, | |||
| fsec_t * | fsec, | |||
| int * | is2digits, | |||
| bool | EuroDates | |||
| ) | [static] |
Definition at line 1269 of file dt_common.c.
References date2j(), DAY, DecodeNumberField(), DOY, DTK_DATE_M, DTK_M, j2date(), MONTH, MONTHS_PER_YEAR, val, and YEAR.
Referenced by DecodeDate(), and DecodeDateTime().
{
int val;
char *cp;
*tmask = 0;
val = strtol(str, &cp, 10);
if (cp == str)
return -1;
if (*cp == '.')
{
/*
* More than two digits? Then could be a date or a run-together time:
* 2001.360 20011225 040506.789
*/
if (cp - str > 2)
return DecodeNumberField(flen, str, (fmask | DTK_DATE_M),
tmask, tm, fsec, is2digits);
*fsec = strtod(cp, &cp);
if (*cp != '\0')
return -1;
}
else if (*cp != '\0')
return -1;
/* Special case day of year? */
if (flen == 3 && (fmask & DTK_M(YEAR)) && val >= 1 && val <= 366)
{
*tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
tm->tm_yday = val;
j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1,
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
}
/***
* Enough digits to be unequivocal year? Used to test for 4 digits or
* more, but we now test first for a three-digit doy so anything
* bigger than two digits had better be an explicit year.
* - thomas 1999-01-09
* Back to requiring a 4 digit year. We accept a two digit
* year farther down. - thomas 2000-03-28
***/
else if (flen >= 4)
{
*tmask = DTK_M(YEAR);
/* already have a year? then see if we can substitute... */
if ((fmask & DTK_M(YEAR)) && !(fmask & DTK_M(DAY)) &&
tm->tm_year >= 1 && tm->tm_year <= 31)
{
tm->tm_mday = tm->tm_year;
*tmask = DTK_M(DAY);
}
tm->tm_year = val;
}
/* already have year? then could be month */
else if ((fmask & DTK_M(YEAR)) && !(fmask & DTK_M(MONTH)) && val >= 1 && val <= MONTHS_PER_YEAR)
{
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
}
/* no year and EuroDates enabled? then could be day */
else if ((EuroDates || (fmask & DTK_M(MONTH))) &&
!(fmask & DTK_M(YEAR)) && !(fmask & DTK_M(DAY)) &&
val >= 1 && val <= 31)
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
else if (!(fmask & DTK_M(MONTH)) && val >= 1 && val <= MONTHS_PER_YEAR)
{
*tmask = DTK_M(MONTH);
tm->tm_mon = val;
}
else if (!(fmask & DTK_M(DAY)) && val >= 1 && val <= 31)
{
*tmask = DTK_M(DAY);
tm->tm_mday = val;
}
/*
* Check for 2 or 4 or more digits, but currently we reach here only if
* two digits. - thomas 2000-03-28
*/
else if (!(fmask & DTK_M(YEAR)) && (flen >= 4 || flen == 2))
{
*tmask = DTK_M(YEAR);
tm->tm_year = val;
/* adjust ONLY if exactly two digits... */
*is2digits = (flen == 2);
}
else
return -1;
return 0;
} /* DecodeNumber() */
| static int DecodeNumberField | ( | int | len, | |
| char * | str, | |||
| int | fmask, | |||
| int * | tmask, | |||
| struct tm * | tm, | |||
| fsec_t * | fsec, | |||
| int * | is2digits | |||
| ) | [static] |
Definition at line 1162 of file dt_common.c.
References DTK_DATE_M, DTK_TIME_M, MAXDATELEN, and NULL.
Referenced by DecodeDateTime(), and DecodeNumber().
{
char *cp;
/*
* Have a decimal point? Then this is a date or something with a seconds
* field...
*/
if ((cp = strchr(str, '.')) != NULL)
{
#ifdef HAVE_INT64_TIMESTAMP
char fstr[MAXDATELEN + 1];
/*
* OK, we have at most six digits to care about. Let's construct a
* string and then do the conversion to an integer.
*/
strcpy(fstr, (cp + 1));
strcpy(fstr + strlen(fstr), "000000");
*(fstr + 6) = '\0';
*fsec = strtol(fstr, NULL, 10);
#else
*fsec = strtod(cp, NULL);
#endif
*cp = '\0';
len = strlen(str);
}
/* No decimal point and no complete date yet? */
else if ((fmask & DTK_DATE_M) != DTK_DATE_M)
{
/* yyyymmdd? */
if (len == 8)
{
*tmask = DTK_DATE_M;
tm->tm_mday = atoi(str + 6);
*(str + 6) = '\0';
tm->tm_mon = atoi(str + 4);
*(str + 4) = '\0';
tm->tm_year = atoi(str + 0);
return DTK_DATE;
}
/* yymmdd? */
else if (len == 6)
{
*tmask = DTK_DATE_M;
tm->tm_mday = atoi(str + 4);
*(str + 4) = '\0';
tm->tm_mon = atoi(str + 2);
*(str + 2) = '\0';
tm->tm_year = atoi(str + 0);
*is2digits = TRUE;
return DTK_DATE;
}
/* yyddd? */
else if (len == 5)
{
*tmask = DTK_DATE_M;
tm->tm_mday = atoi(str + 2);
*(str + 2) = '\0';
tm->tm_mon = 1;
tm->tm_year = atoi(str + 0);
*is2digits = TRUE;
return DTK_DATE;
}
}
/* not all time fields are specified? */
if ((fmask & DTK_TIME_M) != DTK_TIME_M)
{
/* hhmmss */
if (len == 6)
{
*tmask = DTK_TIME_M;
tm->tm_sec = atoi(str + 4);
*(str + 4) = '\0';
tm->tm_min = atoi(str + 2);
*(str + 2) = '\0';
tm->tm_hour = atoi(str + 0);
return DTK_TIME;
}
/* hhmm? */
else if (len == 4)
{
*tmask = DTK_TIME_M;
tm->tm_sec = 0;
tm->tm_min = atoi(str + 2);
*(str + 2) = '\0';
tm->tm_hour = atoi(str + 0);
return DTK_TIME;
}
}
return -1;
} /* DecodeNumberField() */
| static int DecodePosixTimezone | ( | char * | str, | |
| int * | tzp | |||
| ) | [static] |
Definition at line 1621 of file dt_common.c.
References DecodeSpecial(), DecodeTimezone(), DTZ, MAXDATEFIELDS, TZ, and val.
Referenced by DecodeDateTime().
{
int val,
tz;
int type;
char *cp;
char delim;
cp = str;
while (*cp != '\0' && isalpha((unsigned char) *cp))
cp++;
if (DecodeTimezone(cp, &tz) != 0)
return -1;
delim = *cp;
*cp = '\0';
type = DecodeSpecial(MAXDATEFIELDS - 1, str, &val);
*cp = delim;
switch (type)
{
case DTZ:
case TZ:
*tzp = (val * MINS_PER_HOUR) - tz;
break;
default:
return -1;
}
return 0;
} /* DecodePosixTimezone() */
| static int DecodeSpecial | ( | int | field, | |
| char * | lowtoken, | |||
| int * | val | |||
| ) | [static] |
Definition at line 648 of file dt_common.c.
References datebsearch(), DTZ, DTZMOD, FROMVAL, NULL, szdatetktbl, TOKMAXLEN, datetkn::type, TZ, and datetkn::value.
{
int type;
datetkn *tp;
if (datecache[field] != NULL &&
strncmp(lowtoken, datecache[field]->token, TOKMAXLEN) == 0)
tp = datecache[field];
else
{
tp = NULL;
if (!tp)
tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
}
datecache[field] = tp;
if (tp == NULL)
{
type = UNKNOWN_FIELD;
*val = 0;
}
else
{
type = tp->type;
switch (type)
{
case TZ:
case DTZ:
case DTZMOD:
*val = FROMVAL(tp);
break;
default:
*val = tp->value;
break;
}
}
return type;
} /* DecodeSpecial() */
Definition at line 1507 of file dt_common.c.
References MAXDATELEN, and USECS_PER_SEC.
{
char *cp;
*tmask = DTK_TIME_M;
tm->tm_hour = strtol(str, &cp, 10);
if (*cp != ':')
return -1;
str = cp + 1;
tm->tm_min = strtol(str, &cp, 10);
if (*cp == '\0')
{
tm->tm_sec = 0;
*fsec = 0;
}
else if (*cp != ':')
return -1;
else
{
str = cp + 1;
tm->tm_sec = strtol(str, &cp, 10);
if (*cp == '\0')
*fsec = 0;
else if (*cp == '.')
{
#ifdef HAVE_INT64_TIMESTAMP
char fstr[MAXDATELEN + 1];
/*
* OK, we have at most six digits to work with. Let's construct a
* string and then do the conversion to an integer.
*/
strncpy(fstr, (cp + 1), 7);
strcpy(fstr + strlen(fstr), "000000");
*(fstr + 6) = '\0';
*fsec = strtol(fstr, &cp, 10);
#else
str = cp;
*fsec = strtod(str, &cp);
#endif
if (*cp != '\0')
return -1;
}
else
return -1;
}
/* do a sanity check */
#ifdef HAVE_INT64_TIMESTAMP
if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 ||
tm->tm_sec < 0 || tm->tm_sec > 59 || *fsec >= USECS_PER_SEC)
return -1;
#else
if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 ||
tm->tm_sec < 0 || tm->tm_sec > 59 || *fsec >= 1)
return -1;
#endif
return 0;
} /* DecodeTime() */
| static int DecodeTimezone | ( | char * | str, | |
| int * | tzp | |||
| ) | [static] |
Definition at line 1576 of file dt_common.c.
References MINS_PER_HOUR, and SECS_PER_MINUTE.
Referenced by DecodeDateTime(), DecodePosixTimezone(), and PGTYPEStimestamp_defmt_scan().
{
int tz;
int hr,
min;
char *cp;
int len;
/* assume leading character is "+" or "-" */
hr = strtol(str + 1, &cp, 10);
/* explicit delimiter? */
if (*cp == ':')
min = strtol(cp + 1, &cp, 10);
/* otherwise, might have run things together... */
else if (*cp == '\0' && (len = strlen(str)) > 3)
{
min = strtol(str + len - 2, &cp, 10);
if (min < 0 || min >= 60)
return -1;
*(str + len - 2) = '\0';
hr = strtol(str + 1, &cp, 10);
if (hr < 0 || hr > 13)
return -1;
}
else
min = 0;
tz = (hr * MINS_PER_HOUR + min) * SECS_PER_MINUTE;
if (*str == '-')
tz = -tz;
*tzp = -tz;
return *cp != '\0';
} /* DecodeTimezone() */
| int DecodeUnits | ( | int | field, | |
| char * | lowtoken, | |||
| int * | val | |||
| ) |
Definition at line 545 of file dt_common.c.
References datebsearch(), DTZ, FROMVAL, NULL, szdeltatktbl, datetkn::token, TOKMAXLEN, datetkn::type, TZ, and datetkn::value.
{
int type;
datetkn *tp;
if (deltacache[field] != NULL &&
strncmp(lowtoken, deltacache[field]->token, TOKMAXLEN) == 0)
tp = deltacache[field];
else
tp = datebsearch(lowtoken, deltatktbl, szdeltatktbl);
deltacache[field] = tp;
if (tp == NULL)
{
type = UNKNOWN_FIELD;
*val = 0;
}
else
{
type = tp->type;
if (type == TZ || type == DTZ)
*val = FROMVAL(tp);
else
*val = tp->value;
}
return type;
} /* DecodeUnits() */
| void dt2time | ( | double | jd, | |
| int * | hour, | |||
| int * | min, | |||
| int * | sec, | |||
| fsec_t * | fsec | |||
| ) |
Definition at line 1128 of file dt_common.c.
{
#ifdef HAVE_INT64_TIMESTAMP
int64 time;
#else
double time;
#endif
time = jd;
#ifdef HAVE_INT64_TIMESTAMP
*hour = time / USECS_PER_HOUR;
time -= (*hour) * USECS_PER_HOUR;
*min = time / USECS_PER_MINUTE;
time -= (*min) * USECS_PER_MINUTE;
*sec = time / USECS_PER_SEC;
*fsec = time - (*sec * USECS_PER_SEC);
#else
*hour = time / SECS_PER_HOUR;
time -= (*hour) * SECS_PER_HOUR;
*min = time / SECS_PER_MINUTE;
time -= (*min) * SECS_PER_MINUTE;
*sec = time;
*fsec = time - *sec;
#endif
} /* dt2time() */
Definition at line 692 of file dt_common.c.
References MONTHS_PER_YEAR, USE_GERMAN_DATES, USE_ISO_DATES, USE_POSTGRES_DATES, and USE_SQL_DATES.
{
if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
return -1;
switch (style)
{
case USE_ISO_DATES:
/* compatible with ISO date formats */
if (tm->tm_year > 0)
sprintf(str, "%04d-%02d-%02d",
tm->tm_year, tm->tm_mon, tm->tm_mday);
else
sprintf(str, "%04d-%02d-%02d %s",
-(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
break;
case USE_SQL_DATES:
/* compatible with Oracle/Ingres date formats */
if (EuroDates)
sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
else
sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
if (tm->tm_year > 0)
sprintf(str + 5, "/%04d", tm->tm_year);
else
sprintf(str + 5, "/%04d %s", -(tm->tm_year - 1), "BC");
break;
case USE_GERMAN_DATES:
/* German-style date format */
sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
if (tm->tm_year > 0)
sprintf(str + 5, ".%04d", tm->tm_year);
else
sprintf(str + 5, ".%04d %s", -(tm->tm_year - 1), "BC");
break;
case USE_POSTGRES_DATES:
default:
/* traditional date-only style for Postgres */
if (EuroDates)
sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon);
else
sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday);
if (tm->tm_year > 0)
sprintf(str + 5, "-%04d", tm->tm_year);
else
sprintf(str + 5, "-%04d %s", -(tm->tm_year - 1), "BC");
break;
}
return TRUE;
} /* EncodeDateOnly() */
| int EncodeDateTime | ( | struct tm * | tm, | |
| fsec_t | fsec, | |||
| bool | print_tz, | |||
| int | tz, | |||
| const char * | tzn, | |||
| int | style, | |||
| char * | str, | |||
| bool | EuroDates | |||
| ) |
Definition at line 779 of file dt_common.c.
References date2j(), days, MAXTZLEN, MINS_PER_HOUR, months, TrimTrailingZeros(), USE_GERMAN_DATES, USE_ISO_DATES, USE_POSTGRES_DATES, and USE_SQL_DATES.
{
int day,
hour,
min;
/*
* Negative tm_isdst means we have no valid time zone translation.
*/
if (tm->tm_isdst < 0)
print_tz = false;
switch (style)
{
case USE_ISO_DATES:
/* Compatible with ISO-8601 date formats */
sprintf(str, "%04d-%02d-%02d %02d:%02d",
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
/*
* Print fractional seconds if any. The field widths here should
* be at least equal to MAX_TIMESTAMP_PRECISION.
*
* In float mode, don't print fractional seconds before 1 AD,
* since it's unlikely there's any precision left ...
*/
#ifdef HAVE_INT64_TIMESTAMP
if (fsec != 0)
{
sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
#else
if ((fsec != 0) && (tm->tm_year > 0))
{
sprintf(str + strlen(str), ":%09.6f", tm->tm_sec + fsec);
#endif
TrimTrailingZeros(str);
}
else
sprintf(str + strlen(str), ":%02d", tm->tm_sec);
if (tm->tm_year <= 0)
sprintf(str + strlen(str), " BC");
if (print_tz)
{
hour = -(tz / SECS_PER_HOUR);
min = (abs(tz) / MINS_PER_HOUR) % MINS_PER_HOUR;
if (min != 0)
sprintf(str + strlen(str), "%+03d:%02d", hour, min);
else
sprintf(str + strlen(str), "%+03d", hour);
}
break;
case USE_SQL_DATES:
/* Compatible with Oracle/Ingres date formats */
if (EuroDates)
sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
else
sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
sprintf(str + 5, "/%04d %02d:%02d",
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
tm->tm_hour, tm->tm_min);
/*
* Print fractional seconds if any. The field widths here should
* be at least equal to MAX_TIMESTAMP_PRECISION.
*
* In float mode, don't print fractional seconds before 1 AD,
* since it's unlikely there's any precision left ...
*/
#ifdef HAVE_INT64_TIMESTAMP
if (fsec != 0)
{
sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
#else
if (fsec != 0 && tm->tm_year > 0)
{
sprintf(str + strlen(str), ":%09.6f", tm->tm_sec + fsec);
#endif
TrimTrailingZeros(str);
}
else
sprintf(str + strlen(str), ":%02d", tm->tm_sec);
if (tm->tm_year <= 0)
sprintf(str + strlen(str), " BC");
/*
* Note: the uses of %.*s in this function would be risky if the
* timezone names ever contain non-ASCII characters. However, all
* TZ abbreviations in the Olson database are plain ASCII.
*/
if (print_tz)
{
if (tzn)
sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
else
{
hour = -(tz / SECS_PER_HOUR);
min = (abs(tz) / MINS_PER_HOUR) % MINS_PER_HOUR;
if (min != 0)
sprintf(str + strlen(str), "%+03d:%02d", hour, min);
else
sprintf(str + strlen(str), "%+03d", hour);
}
}
break;
case USE_GERMAN_DATES:
/* German variant on European style */
sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
sprintf(str + 5, ".%04d %02d:%02d",
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
tm->tm_hour, tm->tm_min);
/*
* Print fractional seconds if any. The field widths here should
* be at least equal to MAX_TIMESTAMP_PRECISION.
*
* In float mode, don't print fractional seconds before 1 AD,
* since it's unlikely there's any precision left ...
*/
#ifdef HAVE_INT64_TIMESTAMP
if (fsec != 0)
{
sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
#else
if (fsec != 0 && tm->tm_year > 0)
{
sprintf(str + strlen(str), ":%09.6f", tm->tm_sec + fsec);
#endif
TrimTrailingZeros(str);
}
else
sprintf(str + strlen(str), ":%02d", tm->tm_sec);
if (tm->tm_year <= 0)
sprintf(str + strlen(str), " BC");
if (print_tz)
{
if (tzn)
sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
else
{
hour = -(tz / SECS_PER_HOUR);
min = (abs(tz) / MINS_PER_HOUR) % MINS_PER_HOUR;
if (min != 0)
sprintf(str + strlen(str), "%+03d:%02d", hour, min);
else
sprintf(str + strlen(str), "%+03d", hour);
}
}
break;
case USE_POSTGRES_DATES:
default:
/* Backward-compatible with traditional Postgres abstime dates */
day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
tm->tm_wday = (int) ((day + date2j(2000, 1, 1) + 1) % 7);
strncpy(str, days[tm->tm_wday], 3);
strcpy(str + 3, " ");
if (EuroDates)
sprintf(str + 4, "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
else
sprintf(str + 4, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);
sprintf(str + 10, " %02d:%02d", tm->tm_hour, tm->tm_min);
/*
* Print fractional seconds if any. The field widths here should
* be at least equal to MAX_TIMESTAMP_PRECISION.
*
* In float mode, don't print fractional seconds before 1 AD,
* since it's unlikely there's any precision left ...
*/
#ifdef HAVE_INT64_TIMESTAMP
if (fsec != 0)
{
sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
#else
if (fsec != 0 && tm->tm_year > 0)
{
sprintf(str + strlen(str), ":%09.6f", tm->tm_sec + fsec);
#endif
TrimTrailingZeros(str);
}
else
sprintf(str + strlen(str), ":%02d", tm->tm_sec);
sprintf(str + strlen(str), " %04d",
(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1));
if (tm->tm_year <= 0)
sprintf(str + strlen(str), " BC");
if (print_tz)
{
if (tzn)
sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
else
{
/*
* We have a time zone, but no string version. Use the
* numeric form, but be sure to include a leading space to
* avoid formatting something which would be rejected by
* the date/time parser later. - thomas 2001-10-19
*/
hour = -(tz / SECS_PER_HOUR);
min = (abs(tz) / MINS_PER_HOUR) % MINS_PER_HOUR;
if (min != 0)
sprintf(str + strlen(str), " %+03d:%02d", hour, min);
else
sprintf(str + strlen(str), " %+03d", hour);
}
}
break;
}
return TRUE;
} /* EncodeDateTime() */
| static char* find_end_token | ( | char * | str, | |
| char * | fmt | |||
| ) | [static] |
Definition at line 2432 of file dt_common.c.
Referenced by pgtypes_defmt_scan().
{
/*
* str: here is28the day12the hour fmt: here is%dthe day%hthe hour
*
* we extract the 28, we read the percent sign and the type "d" then this
* functions gets called as find_end_token("28the day12the hour", "the
* day%hthehour")
*
* fmt points to "the day%hthehour", next_percent points to %hthehour and
* we have to find a match for everything between these positions ("the
* day"). We look for "the day" in str and know that the pattern we are
* about to scan ends where this string starts (right after the "28")
*
* At the end, *fmt is '\0' and *str isn't. end_position then is
* unchanged.
*/
char *end_position = NULL;
char *next_percent,
*subst_location = NULL;
int scan_offset = 0;
char last_char;
/* are we at the end? */
if (!*fmt)
{
end_position = fmt;
return end_position;
}
/* not at the end */
while (fmt[scan_offset] == '%' && fmt[scan_offset + 1])
{
/*
* there is no delimiter, skip to the next delimiter if we're reading
* a number and then something that is not a number "9:15pm", we might
* be able to recover with the strtol end pointer. Go for the next
* percent sign
*/
scan_offset += 2;
}
next_percent = strchr(fmt + scan_offset, '%');
if (next_percent)
{
/*
* we don't want to allocate extra memory, so we temporarily set the
* '%' sign to '\0' and call strstr However since we allow whitespace
* to float around everything, we have to shorten the pattern until we
* reach a non-whitespace character
*/
subst_location = next_percent;
while (*(subst_location - 1) == ' ' && subst_location - 1 > fmt + scan_offset)
subst_location--;
last_char = *subst_location;
*subst_location = '\0';
/*
* the haystack is the str and the needle is the original fmt but it
* ends at the position where the next percent sign would be
*/
/*
* There is one special case. Imagine: str = " 2", fmt = "%d %...",
* since we want to allow blanks as "dynamic" padding we have to
* accept this. Now, we are called with a fmt of " %..." and look for
* " " in str. We find it at the first position and never read the
* 2...
*/
while (*str == ' ')
str++;
end_position = strstr(str, fmt + scan_offset);
*subst_location = last_char;
}
else
{
/*
* there is no other percent sign. So everything up to the end has to
* match.
*/
end_position = str + strlen(str);
}
if (!end_position)
{
/*
* maybe we have the following case:
*
* str = "4:15am" fmt = "%M:%S %p"
*
* at this place we could have
*
* str = "15am" fmt = " %p"
*
* and have set fmt to " " because overwrote the % sign with a NULL
*
* In this case where we would have to match a space but can't find
* it, set end_position to the end of the string
*/
if ((fmt + scan_offset)[0] == ' ' && fmt + scan_offset + 1 == subst_location)
end_position = str + strlen(str);
}
return end_position;
}
| void GetCurrentDateTime | ( | struct tm * | tm | ) |
Definition at line 1120 of file dt_common.c.
References abstime2tm(), and NULL.
{
int tz;
abstime2tm(time(NULL), &tz, tm, NULL);
}
| int GetEpochTime | ( | struct tm * | tm | ) |
Definition at line 1012 of file dt_common.c.
References epoch.
| void j2date | ( | int | jd, | |
| int * | year, | |||
| int * | month, | |||
| int * | day | |||
| ) |
Definition at line 617 of file dt_common.c.
References MONTHS_PER_YEAR.
{
unsigned int julian;
unsigned int quad;
unsigned int extra;
int y;
julian = jd;
julian += 32044;
quad = julian / 146097;
extra = (julian - quad * 146097) * 4 + 3;
julian += 60 + quad * 3 + extra / 146097;
quad = julian / 1461;
julian -= quad * 1461;
y = julian * 4 / 1461;
julian = ((y != 0) ? (julian + 305) % 365 : (julian + 306) % 366) + 123;
y += quad * 4;
*year = y - 4800;
quad = julian * 2141 / 65536;
*day = julian - 7834 * quad / 256;
*month = (quad + 10) % 12 + 1;
return;
} /* j2date() */
| int ParseDateTime | ( | char * | timestr, | |
| char * | lowstr, | |||
| char ** | field, | |||
| int * | ftype, | |||
| int * | numfields, | |||
| char ** | endstr | |||
| ) |
Definition at line 1670 of file dt_common.c.
References DTK_DATE, DTK_NUMBER, MAXDATEFIELDS, and pg_tolower().
{
int nf = 0;
char *lp = lowstr;
*endstr = timestr;
/* outer loop through fields */
while (*(*endstr) != '\0')
{
field[nf] = lp;
/* leading digit? then date or time */
if (isdigit((unsigned char) *(*endstr)))
{
*lp++ = *(*endstr)++;
while (isdigit((unsigned char) *(*endstr)))
*lp++ = *(*endstr)++;
/* time field? */
if (*(*endstr) == ':')
{
ftype[nf] = DTK_TIME;
*lp++ = *(*endstr)++;
while (isdigit((unsigned char) *(*endstr)) ||
(*(*endstr) == ':') || (*(*endstr) == '.'))
*lp++ = *(*endstr)++;
}
/* date field? allow embedded text month */
else if (*(*endstr) == '-' || *(*endstr) == '/' || *(*endstr) == '.')
{
/* save delimiting character to use later */
char *dp = (*endstr);
*lp++ = *(*endstr)++;
/* second field is all digits? then no embedded text month */
if (isdigit((unsigned char) *(*endstr)))
{
ftype[nf] = (*dp == '.') ? DTK_NUMBER : DTK_DATE;
while (isdigit((unsigned char) *(*endstr)))
*lp++ = *(*endstr)++;
/*
* insist that the delimiters match to get a three-field
* date.
*/
if (*(*endstr) == *dp)
{
ftype[nf] = DTK_DATE;
*lp++ = *(*endstr)++;
while (isdigit((unsigned char) *(*endstr)) || (*(*endstr) == *dp))
*lp++ = *(*endstr)++;
}
}
else
{
ftype[nf] = DTK_DATE;
while (isalnum((unsigned char) *(*endstr)) || (*(*endstr) == *dp))
*lp++ = pg_tolower((unsigned char) *(*endstr)++);
}
}
/*
* otherwise, number only and will determine year, month, day, or
* concatenated fields later...
*/
else
ftype[nf] = DTK_NUMBER;
}
/* Leading decimal point? Then fractional seconds... */
else if (*(*endstr) == '.')
{
*lp++ = *(*endstr)++;
while (isdigit((unsigned char) *(*endstr)))
*lp++ = *(*endstr)++;
ftype[nf] = DTK_NUMBER;
}
/*
* text? then date string, month, day of week, special, or timezone
*/
else if (isalpha((unsigned char) *(*endstr)))
{
ftype[nf] = DTK_STRING;
*lp++ = pg_tolower((unsigned char) *(*endstr)++);
while (isalpha((unsigned char) *(*endstr)))
*lp++ = pg_tolower((unsigned char) *(*endstr)++);
/*
* Full date string with leading text month? Could also be a POSIX
* time zone...
*/
if (*(*endstr) == '-' || *(*endstr) == '/' || *(*endstr) == '.')
{
char *dp = (*endstr);
ftype[nf] = DTK_DATE;
*lp++ = *(*endstr)++;
while (isdigit((unsigned char) *(*endstr)) || *(*endstr) == *dp)
*lp++ = *(*endstr)++;
}
}
/* skip leading spaces */
else if (isspace((unsigned char) *(*endstr)))
{
(*endstr)++;
continue;
}
/* sign? then special or numeric timezone */
else if (*(*endstr) == '+' || *(*endstr) == '-')
{
*lp++ = *(*endstr)++;
/* soak up leading whitespace */
while (isspace((unsigned char) *(*endstr)))
(*endstr)++;
/* numeric timezone? */
if (isdigit((unsigned char) *(*endstr)))
{
ftype[nf] = DTK_TZ;
*lp++ = *(*endstr)++;
while (isdigit((unsigned char) *(*endstr)) ||
(*(*endstr) == ':') || (*(*endstr) == '.'))
*lp++ = *(*endstr)++;
}
/* special? */
else if (isalpha((unsigned char) *(*endstr)))
{
ftype[nf] = DTK_SPECIAL;
*lp++ = pg_tolower((unsigned char) *(*endstr)++);
while (isalpha((unsigned char) *(*endstr)))
*lp++ = pg_tolower((unsigned char) *(*endstr)++);
}
/* otherwise something wrong... */
else
return -1;
}
/* ignore punctuation but use as delimiter */
else if (ispunct((unsigned char) *(*endstr)))
{
(*endstr)++;
continue;
}
/* otherwise, something is not right... */
else
return -1;
/* force in a delimiter after each field */
*lp++ = '\0';
nf++;
if (nf > MAXDATEFIELDS)
return -1;
}
*numfields = nf;
return 0;
} /* ParseDateTime() */
| static int pgtypes_defmt_scan | ( | union un_fmt_comb * | scan_val, | |
| int | scan_type, | |||
| char ** | pstr, | |||
| char * | pfmt | |||
| ) | [static] |
Definition at line 2537 of file dt_common.c.
References find_end_token(), un_fmt_comb::luint_val, NULL, pgtypes_strdup(), PGTYPES_TYPE_STRING_MALLOCED, PGTYPES_TYPE_UINT, PGTYPES_TYPE_UINT_LONG, un_fmt_comb::str_val, and un_fmt_comb::uint_val.
Referenced by PGTYPEStimestamp_defmt_scan().
{
/*
* scan everything between pstr and pstr_end. This is not including the
* last character so we might set it to '\0' for the parsing
*/
char last_char;
int err = 0;
char *pstr_end;
char *strtol_end = NULL;
while (**pstr == ' ')
pstr++;
pstr_end = find_end_token(*pstr, pfmt);
if (!pstr_end)
{
/* there was an error, no match */
return 1;
}
last_char = *pstr_end;
*pstr_end = '\0';
switch (scan_type)
{
case PGTYPES_TYPE_UINT:
/*
* numbers may be blank-padded, this is the only deviation from
* the fmt-string we accept
*/
while (**pstr == ' ')
(*pstr)++;
errno = 0;
scan_val->uint_val = (unsigned int) strtol(*pstr, &strtol_end, 10);
if (errno)
err = 1;
break;
case PGTYPES_TYPE_UINT_LONG:
while (**pstr == ' ')
(*pstr)++;
errno = 0;
scan_val->luint_val = (unsigned long int) strtol(*pstr, &strtol_end, 10);
if (errno)
err = 1;
break;
case PGTYPES_TYPE_STRING_MALLOCED:
scan_val->str_val = pgtypes_strdup(*pstr);
if (scan_val->str_val == NULL)
err = 1;
break;
}
if (strtol_end && *strtol_end)
*pstr = strtol_end;
else
*pstr = pstr_end;
*pstr_end = last_char;
return err;
}
| int PGTYPEStimestamp_defmt_scan | ( | char ** | str, | |
| char * | fmt, | |||
| timestamp * | d, | |||
| int * | year, | |||
| int * | month, | |||
| int * | day, | |||
| int * | hour, | |||
| int * | minute, | |||
| int * | second, | |||
| int * | tz | |||
| ) |
Definition at line 2602 of file dt_common.c.
References day_tab, days, DecodeTimezone(), free, isleap, un_fmt_comb::luint_val, MINS_PER_HOUR, months, MONTHS_PER_YEAR, pg_strcasecmp(), pgtypes_alloc(), pgtypes_date_months, pgtypes_date_weekdays_short, pgtypes_defmt_scan(), un_fmt_comb::str_val, szdatetktbl, tm2timestamp(), un_fmt_comb::uint_val, and datetkn::value.
Referenced by PGTYPEStimestamp_defmt_asc().
{
union un_fmt_comb scan_val;
int scan_type;
char *pstr,
*pfmt,
*tmp;
int err = 1;
unsigned int j;
struct tm tm;
pfmt = fmt;
pstr = *str;
while (*pfmt)
{
err = 0;
while (*pfmt == ' ')
pfmt++;
while (*pstr == ' ')
pstr++;
if (*pfmt != '%')
{
if (*pfmt == *pstr)
{
pfmt++;
pstr++;
}
else
{
/* Error: no match */
err = 1;
return err;
}
continue;
}
/* here *pfmt equals '%' */
pfmt++;
switch (*pfmt)
{
case 'a':
pfmt++;
/*
* we parse the day and see if it is a week day but we do not
* check if the week day really matches the date
*/
err = 1;
j = 0;
while (pgtypes_date_weekdays_short[j])
{
if (strncmp(pgtypes_date_weekdays_short[j], pstr,
strlen(pgtypes_date_weekdays_short[j])) == 0)
{
/* found it */
err = 0;
pstr += strlen(pgtypes_date_weekdays_short[j]);
break;
}
j++;
}
break;
case 'A':
/* see note above */
pfmt++;
err = 1;
j = 0;
while (days[j])
{
if (strncmp(days[j], pstr, strlen(days[j])) == 0)
{
/* found it */
err = 0;
pstr += strlen(days[j]);
break;
}
j++;
}
break;
case 'b':
case 'h':
pfmt++;
err = 1;
j = 0;
while (months[j])
{
if (strncmp(months[j], pstr, strlen(months[j])) == 0)
{
/* found it */
err = 0;
pstr += strlen(months[j]);
*month = j + 1;
break;
}
j++;
}
break;
case 'B':
/* see note above */
pfmt++;
err = 1;
j = 0;
while (pgtypes_date_months[j])
{
if (strncmp(pgtypes_date_months[j], pstr, strlen(pgtypes_date_months[j])) == 0)
{
/* found it */
err = 0;
pstr += strlen(pgtypes_date_months[j]);
*month = j + 1;
break;
}
j++;
}
break;
case 'c':
/* XXX */
break;
case 'C':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
*year = scan_val.uint_val * 100;
break;
case 'd':
case 'e':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
*day = scan_val.uint_val;
break;
case 'D':
/*
* we have to concatenate the strings in order to be able to
* find the end of the substitution
*/
pfmt++;
tmp = pgtypes_alloc(strlen("%m/%d/%y") + strlen(pstr) + 1);
strcpy(tmp, "%m/%d/%y");
strcat(tmp, pfmt);
err = PGTYPEStimestamp_defmt_scan(&pstr, tmp, d, year, month, day, hour, minute, second, tz);
free(tmp);
return err;
case 'm':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
*month = scan_val.uint_val;
break;
case 'y':
case 'g': /* XXX difference to y (ISO) */
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
if (*year < 0)
{
/* not yet set */
*year = scan_val.uint_val;
}
else
*year += scan_val.uint_val;
if (*year < 100)
*year += 1900;
break;
case 'G':
/* XXX difference to %V (ISO) */
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
*year = scan_val.uint_val;
break;
case 'H':
case 'I':
case 'k':
case 'l':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
*hour += scan_val.uint_val;
break;
case 'j':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
/*
* XXX what should we do with that? We could say that it's
* sufficient if we have the year and the day within the year
* to get at least a specific day.
*/
break;
case 'M':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
*minute = scan_val.uint_val;
break;
case 'n':
pfmt++;
if (*pstr == '\n')
pstr++;
else
err = 1;
break;
case 'p':
err = 1;
pfmt++;
if (strncmp(pstr, "am", 2) == 0)
{
*hour += 0;
err = 0;
pstr += 2;
}
if (strncmp(pstr, "a.m.", 4) == 0)
{
*hour += 0;
err = 0;
pstr += 4;
}
if (strncmp(pstr, "pm", 2) == 0)
{
*hour += 12;
err = 0;
pstr += 2;
}
if (strncmp(pstr, "p.m.", 4) == 0)
{
*hour += 12;
err = 0;
pstr += 4;
}
break;
case 'P':
err = 1;
pfmt++;
if (strncmp(pstr, "AM", 2) == 0)
{
*hour += 0;
err = 0;
pstr += 2;
}
if (strncmp(pstr, "A.M.", 4) == 0)
{
*hour += 0;
err = 0;
pstr += 4;
}
if (strncmp(pstr, "PM", 2) == 0)
{
*hour += 12;
err = 0;
pstr += 2;
}
if (strncmp(pstr, "P.M.", 4) == 0)
{
*hour += 12;
err = 0;
pstr += 4;
}
break;
case 'r':
pfmt++;
tmp = pgtypes_alloc(strlen("%I:%M:%S %p") + strlen(pstr) + 1);
strcpy(tmp, "%I:%M:%S %p");
strcat(tmp, pfmt);
err = PGTYPEStimestamp_defmt_scan(&pstr, tmp, d, year, month, day, hour, minute, second, tz);
free(tmp);
return err;
case 'R':
pfmt++;
tmp = pgtypes_alloc(strlen("%H:%M") + strlen(pstr) + 1);
strcpy(tmp, "%H:%M");
strcat(tmp, pfmt);
err = PGTYPEStimestamp_defmt_scan(&pstr, tmp, d, year, month, day, hour, minute, second, tz);
free(tmp);
return err;
case 's':
pfmt++;
scan_type = PGTYPES_TYPE_UINT_LONG;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
/* number of seconds in scan_val.luint_val */
{
struct tm *tms;
time_t et = (time_t) scan_val.luint_val;
tms = gmtime(&et);
if (tms)
{
*year = tms->tm_year + 1900;
*month = tms->tm_mon + 1;
*day = tms->tm_mday;
*hour = tms->tm_hour;
*minute = tms->tm_min;
*second = tms->tm_sec;
}
else
err = 1;
}
break;
case 'S':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
*second = scan_val.uint_val;
break;
case 't':
pfmt++;
if (*pstr == '\t')
pstr++;
else
err = 1;
break;
case 'T':
pfmt++;
tmp = pgtypes_alloc(strlen("%H:%M:%S") + strlen(pstr) + 1);
strcpy(tmp, "%H:%M:%S");
strcat(tmp, pfmt);
err = PGTYPEStimestamp_defmt_scan(&pstr, tmp, d, year, month, day, hour, minute, second, tz);
free(tmp);
return err;
case 'u':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
if (scan_val.uint_val < 1 || scan_val.uint_val > 7)
err = 1;
break;
case 'U':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
if (scan_val.uint_val > 53)
err = 1;
break;
case 'V':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
if (scan_val.uint_val < 1 || scan_val.uint_val > 53)
err = 1;
break;
case 'w':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
if (scan_val.uint_val > 6)
err = 1;
break;
case 'W':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
if (scan_val.uint_val > 53)
err = 1;
break;
case 'x':
case 'X':
/* XXX */
break;
case 'Y':
pfmt++;
scan_type = PGTYPES_TYPE_UINT;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
*year = scan_val.uint_val;
break;
case 'z':
pfmt++;
scan_type = PGTYPES_TYPE_STRING_MALLOCED;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
if (!err)
{
err = DecodeTimezone(scan_val.str_val, tz);
free(scan_val.str_val);
}
break;
case 'Z':
pfmt++;
scan_type = PGTYPES_TYPE_STRING_MALLOCED;
err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
/*
* XXX use DecodeSpecial instead ? - it's declared static but
* the arrays as well. :-(
*/
for (j = 0; !err && j < szdatetktbl; j++)
{
if (pg_strcasecmp(datetktbl[j].token, scan_val.str_val) == 0)
{
/*
* tz calculates the offset for the seconds, the
* timezone value of the datetktbl table is in quarter
* hours
*/
*tz = -15 * MINS_PER_HOUR * datetktbl[j].value;
break;
}
}
free(scan_val.str_val);
break;
case '+':
/* XXX */
break;
case '%':
pfmt++;
if (*pstr == '%')
pstr++;
else
err = 1;
break;
default:
err = 1;
}
}
if (!err)
{
if (*second < 0)
*second = 0;
if (*minute < 0)
*minute = 0;
if (*hour < 0)
*hour = 0;
if (*day < 0)
{
err = 1;
*day = 1;
}
if (*month < 0)
{
err = 1;
*month = 1;
}
if (*year < 0)
{
err = 1;
*year = 1970;
}
if (*second > 59)
{
err = 1;
*second = 0;
}
if (*minute > 59)
{
err = 1;
*minute = 0;
}
if (*hour > 24 || /* test for > 24:00:00 */
(*hour == 24 && (*minute > 0 || *second > 0)))
{
err = 1;
*hour = 0;
}
if (*month > MONTHS_PER_YEAR)
{
err = 1;
*month = 1;
}
if (*day > day_tab[isleap(*year)][*month - 1])
{
*day = day_tab[isleap(*year)][*month - 1];
err = 1;
}
tm.tm_sec = *second;
tm.tm_min = *minute;
tm.tm_hour = *hour;
tm.tm_mday = *day;
tm.tm_mon = *month;
tm.tm_year = *year;
tm2timestamp(&tm, 0, tz, d);
}
return err;
}
| void TrimTrailingZeros | ( | char * | str | ) |
Definition at line 748 of file dt_common.c.
{
int len = strlen(str);
/* chop off trailing zeros... but leave at least 2 fractional digits */
while (*(str + len - 1) == '0' && *(str + len - 3) != '.')
{
len--;
*(str + len) = '\0';
}
}
Definition at line 500 of file dt_common.c.
Definition at line 26 of file dt_common.c.
| int day_tab[2][13] |
{
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}}
Definition at line 13 of file dt_common.c.
| char* days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", NULL} |
Definition at line 506 of file dt_common.c.
datetkn* deltacache[MAXDATEFIELDS] = {NULL} [static] |
Definition at line 502 of file dt_common.c.
datetkn deltatktbl[] [static] |
Definition at line 430 of file dt_common.c.
| char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL} |
Definition at line 504 of file dt_common.c.
| char* pgtypes_date_months[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", NULL} |
Definition at line 510 of file dt_common.c.
Referenced by dttofmtasc_replace(), PGTYPESdate_defmt_asc(), and PGTYPEStimestamp_defmt_scan().
| char* pgtypes_date_weekdays_short[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL} |
Definition at line 508 of file dt_common.c.
Referenced by dttofmtasc_replace(), PGTYPESdate_fmt_asc(), and PGTYPEStimestamp_defmt_scan().
const unsigned int szdatetktbl = lengthof(datetktbl) [static] |
Definition at line 497 of file dt_common.c.
Referenced by DecodeSpecial(), and PGTYPEStimestamp_defmt_scan().
const unsigned int szdeltatktbl = lengthof(deltatktbl) [static] |
Definition at line 498 of file dt_common.c.
Referenced by DecodeUnits().
1.7.1