/* copy&pasted from .../src/backend/utils/adt/datetime.c * with 3 exceptions * * * changesd struct pg_tm to struct tm * * * ECPG code called this without a 'range' parameter * removed 'int range' from the argument list and * places where DecodeTime is called; and added * int range = INTERVAL_FULL_RANGE; * * * ECPG semes not to have a global IntervalStyle * so added * int IntervalStyle = INTSTYLE_POSTGRES; * * * Assert wasn't available so removed it. */ int DecodeInterval(char **field, int *ftype, int nf, /* int range, */ int *dtype, struct /* pg_ */ tm * tm, fsec_t *fsec) { int IntervalStyle = INTSTYLE_POSTGRES_VERBOSE; int range = INTERVAL_FULL_RANGE; bool is_before = FALSE; char *cp; int fmask = 0, tmask, type; int i; int dterr; int val; double fval; *dtype = DTK_DELTA; type = IGNORE_DTF; ClearPgTm(tm, fsec); /* read through list backwards to pick up units before values */ for (i = nf - 1; i >= 0; i--) { switch (ftype[i]) { case DTK_TIME: dterr = DecodeTime(field[i], /* range, */ &tmask, tm, fsec); if (dterr) return dterr; type = DTK_DAY; break; case DTK_TZ: /* * Timezone is a token with a leading sign character and at * least one digit; there could be ':', '.', '-' embedded in * it as well. */ /* Assert(*field[i] == '-' || *field[i] == '+'); */ /* * Try for hh:mm or hh:mm:ss. If not, fall through to * DTK_NUMBER case, which can handle signed float numbers and * signed year-month values. */ if (strchr(field[i] + 1, ':') != NULL && DecodeTime(field[i] + 1, /* INTERVAL_FULL_RANGE, */ &tmask, tm, fsec) == 0) { if (*field[i] == '-') { /* flip the sign on all fields */ tm->tm_hour = -tm->tm_hour; tm->tm_min = -tm->tm_min; tm->tm_sec = -tm->tm_sec; *fsec = -(*fsec); } /* * Set the next type to be a day, if units are not * specified. This handles the case of '1 +02:03' since we * are reading right to left. */ type = DTK_DAY; tmask = DTK_M(TZ); break; } /* FALL THROUGH */ case DTK_DATE: case DTK_NUMBER: if (type == IGNORE_DTF) { /* use typmod to decide what rightmost field is */ switch (range) { case INTERVAL_MASK(YEAR): type = DTK_YEAR; break; case INTERVAL_MASK(MONTH): case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH): type = DTK_MONTH; break; case INTERVAL_MASK(DAY): type = DTK_DAY; break; case INTERVAL_MASK(HOUR): case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR): case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): type = DTK_HOUR; break; case INTERVAL_MASK(MINUTE): case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): type = DTK_MINUTE; break; case INTERVAL_MASK(SECOND): case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): type = DTK_SECOND; break; default: type = DTK_SECOND; break; } } errno = 0; val = strtoint(field[i], &cp, 10); if (errno == ERANGE) return DTERR_FIELD_OVERFLOW; if (*cp == '-') { /* SQL "years-months" syntax */ int val2; val2 = strtoint(cp + 1, &cp, 10); if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR) return DTERR_FIELD_OVERFLOW; if (*cp != '\0') return DTERR_BAD_FORMAT; type = DTK_MONTH; if (*field[i] == '-') val2 = -val2; val = val * MONTHS_PER_YEAR + val2; fval = 0; } else if (*cp == '.') { errno = 0; fval = strtod(cp, &cp); if (*cp != '\0' || errno != 0) return DTERR_BAD_FORMAT; if (*field[i] == '-') fval = -fval; } else if (*cp == '\0') fval = 0; else return DTERR_BAD_FORMAT; tmask = 0; /* DTK_M(type); */ switch (type) { case DTK_MICROSEC: #ifdef HAVE_INT64_TIMESTAMP *fsec += rint(val + fval); #else *fsec += (val + fval) * 1e-6; #endif tmask = DTK_M(MICROSECOND); break; case DTK_MILLISEC: #ifdef HAVE_INT64_TIMESTAMP *fsec += rint((val + fval) * 1000); #else *fsec += (val + fval) * 1e-3; #endif tmask = DTK_M(MILLISECOND); break; case DTK_SECOND: tm->tm_sec += val; #ifdef HAVE_INT64_TIMESTAMP *fsec += rint(fval * 1000000); #else *fsec += fval; #endif /* * If any subseconds were specified, consider this * microsecond and millisecond input as well. */ if (fval == 0) tmask = DTK_M(SECOND); else tmask = DTK_ALL_SECS_M; break; case DTK_MINUTE: tm->tm_min += val; AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE); tmask = DTK_M(MINUTE); break; case DTK_HOUR: tm->tm_hour += val; AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR); tmask = DTK_M(HOUR); type = DTK_DAY; break; case DTK_DAY: tm->tm_mday += val; AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY); break; case DTK_WEEK: tm->tm_mday += val * 7; AdjustFractDays(fval, tm, fsec, 7); tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY); break; case DTK_MONTH: tm->tm_mon += val; AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH); tmask = DTK_M(MONTH); break; case DTK_YEAR: tm->tm_year += val; if (fval != 0) tm->tm_mon += fval * MONTHS_PER_YEAR; tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); break; case DTK_DECADE: tm->tm_year += val * 10; if (fval != 0) tm->tm_mon += fval * MONTHS_PER_YEAR * 10; tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); break; case DTK_CENTURY: tm->tm_year += val * 100; if (fval != 0) tm->tm_mon += fval * MONTHS_PER_YEAR * 100; tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); break; case DTK_MILLENNIUM: tm->tm_year += val * 1000; if (fval != 0) tm->tm_mon += fval * MONTHS_PER_YEAR * 1000; tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); break; default: return DTERR_BAD_FORMAT; } break; case DTK_STRING: case DTK_SPECIAL: type = DecodeUnits(i, field[i], &val); if (type == IGNORE_DTF) continue; tmask = 0; /* DTK_M(type); */ switch (type) { case UNITS: type = val; break; case AGO: is_before = TRUE; type = val; break; case RESERV: tmask = (DTK_DATE_M | DTK_TIME_M); *dtype = val; break; default: return DTERR_BAD_FORMAT; } break; default: return DTERR_BAD_FORMAT; } if (tmask & fmask) return DTERR_BAD_FORMAT; fmask |= tmask; } /* ensure that at least one time field has been found */ if (fmask == 0) return DTERR_BAD_FORMAT; /* ensure fractional seconds are fractional */ if (*fsec != 0) { int sec; #ifdef HAVE_INT64_TIMESTAMP sec = *fsec / USECS_PER_SEC; *fsec -= sec * USECS_PER_SEC; #else TMODULO(*fsec, sec, 1.0); #endif tm->tm_sec += sec; } /*---------- * The SQL standard defines the interval literal * '-1 1:00:00' * to mean "negative 1 days and negative 1 hours", while Postgres * traditionally treats this as meaning "negative 1 days and positive * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign * to all fields if there are no other explicit signs. * * We leave the signs alone if there are additional explicit signs. * This protects us against misinterpreting postgres-style dump output, * since the postgres-style output code has always put an explicit sign on * all fields following a negative field. But note that SQL-spec output * is ambiguous and can be misinterpreted on load! (So it's best practice * to dump in postgres style, not SQL style.) *---------- */ if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-') { /* Check for additional explicit signs */ bool more_signs = false; for (i = 1; i < nf; i++) { if (*field[i] == '-' || *field[i] == '+') { more_signs = true; break; } } if (!more_signs) { /* * Rather than re-determining which field was field[0], just force * 'em all negative. */ if (*fsec > 0) *fsec = -(*fsec); if (tm->tm_sec > 0) tm->tm_sec = -tm->tm_sec; if (tm->tm_min > 0) tm->tm_min = -tm->tm_min; if (tm->tm_hour > 0) tm->tm_hour = -tm->tm_hour; if (tm->tm_mday > 0) tm->tm_mday = -tm->tm_mday; if (tm->tm_mon > 0) tm->tm_mon = -tm->tm_mon; if (tm->tm_year > 0) tm->tm_year = -tm->tm_year; } } /* finally, AGO negates everything */ if (is_before) { *fsec = -(*fsec); tm->tm_sec = -tm->tm_sec; tm->tm_min = -tm->tm_min; tm->tm_hour = -tm->tm_hour; tm->tm_mday = -tm->tm_mday; tm->tm_mon = -tm->tm_mon; tm->tm_year = -tm->tm_year; } return 0; }
/* DecodeDateTime() * Interpret previously parsed fields for general date and time. * Return 0 if full date, 1 if only time, and negative DTERR code if problems. * (Currently, all callers treat 1 as an error return too.) * * External format(s): * "<weekday> <month>-<day>-<year> <hour>:<minute>:<second>" * "Fri Feb-7-1997 15:23:27" * "Feb-7-1997 15:23:27" * "2-7-1997 15:23:27" * "1997-2-7 15:23:27" * "1997.038 15:23:27" (day of year 1-366) * Also supports input in compact time: * "970207 152327" * "97038 152327" * "20011225T040506.789-07" * * Use the system-provided functions to get the current time zone * if not specified in the input string. * * If the date is outside the range of pg_time_t (in practice that could only * happen if pg_time_t is just 32 bits), then assume UTC time zone - thomas * 1997-05-27 */ int DecodeDateTime(char **field, int *ftype, int nf, int *dtype, struct tm *tm, fsec_t *fsec, int *tzp) { int fmask = 0, tmask, type; int ptype = 0; /* "prefix type" for ISO y2001m02d04 format */ int i; int val; int dterr; int mer = HR24; bool haveTextMonth = FALSE; bool isjulian = FALSE; bool is2digits = FALSE; bool bc = FALSE; pg_tz *namedTz = NULL; struct tm cur_tm; /* * 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 DTERR_BAD_FORMAT; errno = 0; val = strtoi(field[i], &cp, 10); if (errno == ERANGE || val < 0) return DTERR_FIELD_OVERFLOW; j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); isjulian = TRUE; /* Get the time zone from the end of the string */ dterr = DecodeTimezone(cp, tzp); if (dterr) return dterr; tmask = DTK_DATE_M | DTK_TIME_M | DTK_M(TZ); ptype = 0; break; } /*** * Already have a date? Then this might be a time zone name * with embedded punctuation (e.g. "America/New_York") or a * run-together time with trailing time zone (e.g. hhmmss-zz). * - thomas 2001-12-25 * * We consider it a time zone if we already have month & day. * This is to allow the form "mmm dd hhmmss tz year", which * we've historically accepted. ***/ else if (ptype != 0 || ((fmask & (DTK_M(MONTH) | DTK_M(DAY))) == (DTK_M(MONTH) | DTK_M(DAY)))) { /* No time zone accepted? Then quit... */ if (tzp == NULL) return DTERR_BAD_FORMAT; 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 DTERR_BAD_FORMAT; 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 DTERR_BAD_FORMAT; if ((cp = strchr(field[i], '-')) == NULL) return DTERR_BAD_FORMAT; /* Get the time zone from the end of the string */ dterr = DecodeTimezone(cp, tzp); if (dterr) return dterr; *cp = '\0'; /* * Then read the rest of the field as a concatenated * time */ dterr = DecodeNumberField(strlen(field[i]), field[i], fmask, &tmask, tm, fsec, &is2digits); if (dterr < 0) return dterr; /* * modify tmask after returning from * DecodeNumberField() */ tmask |= DTK_M(TZ); } else { namedTz = pg_tzset(field[i]); if (!namedTz) { /* * We should return an error code instead of * ereport'ing directly, but then there is no way * to report the bad time zone name. */ warnx("time zone \"%s\" not recognized", field[i]); } /* we'll apply the zone setting below */ tmask = DTK_M(TZ); } } else { dterr = DecodeDate(field[i], fmask, &tmask, &is2digits, tm); if (dterr) return dterr; } break; case DTK_TIME: /* * This might be an ISO time following a "t" field. */ if (ptype != 0) { /* Sanity check; should not fail this test */ if (ptype != DTK_TIME) return DTERR_BAD_FORMAT; ptype = 0; } dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE, &tmask, tm, fsec); if (dterr) return dterr; /* * Check upper limit on hours; other limits checked in * DecodeTime() */ /* test for > 24:00:00 */ if (tm->tm_hour > HOURS_PER_DAY || (tm->tm_hour == HOURS_PER_DAY && (tm->tm_min > 0 || tm->tm_sec > 0 || *fsec > 0))) return DTERR_FIELD_OVERFLOW; break; case DTK_TZ: { int tz; if (tzp == NULL) return DTERR_BAD_FORMAT; dterr = DecodeTimezone(field[i], &tz); if (dterr) return dterr; *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; errno = 0; val = strtoi(field[i], &cp, 10); if (errno == ERANGE) return DTERR_FIELD_OVERFLOW; /* * 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 DTERR_BAD_FORMAT; break; } else if (*cp != '\0') return DTERR_BAD_FORMAT; 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 == '.') { dterr = ParseFractionalSecond(cp, fsec); if (dterr) return dterr; tmask = DTK_ALL_SECS_M; } break; case DTK_TZ: tmask = DTK_M(TZ); dterr = DecodeTimezone(field[i], tzp); if (dterr) return dterr; break; case DTK_JULIAN: /* previous field was a label for "julian date" */ if (val < 0) return DTERR_FIELD_OVERFLOW; tmask = DTK_DATE_M; j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); isjulian = TRUE; /* fractional Julian Day? */ if (*cp == '.') { double time; errno = 0; time = strtod(cp, &cp); if (*cp != '\0' || errno != 0) return DTERR_BAD_FORMAT; #ifdef HAVE_INT64_TIMESTAMP time *= USECS_PER_DAY; #else time *= SECS_PER_DAY; #endif dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec); tmask |= DTK_TIME_M; } break; case DTK_TIME: /* previous field was "t" for ISO time */ dterr = DecodeNumberField(strlen(field[i]), field[i], (fmask | DTK_DATE_M), &tmask, tm, fsec, &is2digits); if (dterr < 0) return dterr; if (tmask != DTK_TIME_M) return DTERR_BAD_FORMAT; break; default: return DTERR_BAD_FORMAT; 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)) { dterr = DecodeDate(field[i], fmask, &tmask, &is2digits, tm); if (dterr) return dterr; } /* 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 */ dterr = DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec, &is2digits); if (dterr < 0) return dterr; } else if (flen > 4) { dterr = DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec, &is2digits); if (dterr < 0) return dterr; } /* otherwise it is a single date/time field... */ else { dterr = DecodeNumber(flen, field[i], haveTextMonth, fmask, &tmask, tm, fsec, &is2digits); if (dterr) return dterr; } } 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_CURRENT: warnx("date/time value \"current\" is no longer supported"); return DTERR_BAD_FORMAT; break; case DTK_NOW: tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ)); *dtype = DTK_DATE; GetCurrentTimeUsec(tm, fsec, tzp); break; case DTK_YESTERDAY: tmask = DTK_DATE_M; *dtype = DTK_DATE; GetCurrentDateTime(&cur_tm); j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) - 1, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); break; case DTK_TODAY: tmask = DTK_DATE_M; *dtype = DTK_DATE; GetCurrentDateTime(&cur_tm); tm->tm_year = cur_tm.tm_year; tm->tm_mon = cur_tm.tm_mon; tm->tm_mday = cur_tm.tm_mday; break; case DTK_TOMORROW: tmask = DTK_DATE_M; *dtype = DTK_DATE; GetCurrentDateTime(&cur_tm); j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) + 1, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); 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 DTERR_BAD_FORMAT; *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 DTERR_BAD_FORMAT; *tzp = val * MINS_PER_HOUR; break; case TZ: tm->tm_isdst = 0; if (tzp == NULL) return DTERR_BAD_FORMAT; *tzp = val * MINS_PER_HOUR; 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 DTERR_BAD_FORMAT; /*** * 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 DTERR_BAD_FORMAT; ptype = val; break; case UNKNOWN_FIELD: /* * Before giving up and declaring error, check to see * if it is an all-alpha timezone name. */ namedTz = pg_tzset(field[i]); if (!namedTz) return DTERR_BAD_FORMAT; /* we'll apply the zone setting below */ tmask = DTK_M(TZ); break; default: return DTERR_BAD_FORMAT; } break; default: return DTERR_BAD_FORMAT; } if (tmask & fmask) return DTERR_BAD_FORMAT; fmask |= tmask; } /* end loop over fields */ /* do final checking/adjustment of Y/M/D fields */ dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm); if (dterr) return dterr; /* handle AM/PM */ if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2) return DTERR_FIELD_OVERFLOW; if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2) tm->tm_hour = 0; else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2) tm->tm_hour += HOURS_PER_DAY / 2; /* do additional checking for full date specs... */ if (*dtype == DTK_DATE) { if ((fmask & DTK_DATE_M) != DTK_DATE_M) { if ((fmask & DTK_TIME_M) == DTK_TIME_M) return 1; return DTERR_BAD_FORMAT; } /* * If we had a full timezone spec, compute the offset (we could not do * it before, because we need the date to resolve DST status). */ if (namedTz != NULL) { /* daylight savings time modifier disallowed with full TZ */ if (fmask & DTK_M(DTZMOD)) return DTERR_BAD_FORMAT; *tzp = DetermineTimeZoneOffset(tm, namedTz); } /* timezone not specified? then find local timezone if possible */ if (tzp != NULL && !(fmask & DTK_M(TZ))) { /* * daylight savings time modifier but no standard timezone? then * error */ if (fmask & DTK_M(DTZMOD)) return DTERR_BAD_FORMAT; *tzp = DetermineTimeZoneOffset(tm, session_timezone); } } return 0; }
/* DecodeInterval() * Interpret previously parsed fields for general time interval. * Return 0 if decoded and -1 if problems. * * Allow "date" field DTK_DATE since this could be just * an unsigned floating point number. - thomas 1997-11-16 * * Allow ISO-style time span, with implicit units on number of days * preceding an hh:mm:ss field. - thomas 1998-04-30 */ int DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec) { int is_before = FALSE; char *cp; int fmask = 0, tmask, type; int i; int val; double fval; *dtype = DTK_DELTA; type = IGNORE_DTF; tm->tm_year = 0; tm->tm_mon = 0; tm->tm_mday = 0; tm->tm_hour = 0; tm->tm_min = 0; tm->tm_sec = 0; *fsec = 0; /* read through list backwards to pick up units before values */ for (i = nf - 1; i >= 0; i--) { switch (ftype[i]) { case DTK_TIME: if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0) return -1; type = DTK_DAY; break; case DTK_TZ: /* * Timezone is a token with a leading sign character and * otherwise the same as a non-signed time field */ /* * A single signed number ends up here, but will be rejected * by DecodeTime(). So, work this out to drop through to * DTK_NUMBER, which *can* tolerate this. */ cp = field[i] + 1; while (*cp != '\0' && *cp != ':' && *cp != '.') cp++; if (*cp == ':' && DecodeTime((field[i] + 1), fmask, &tmask, tm, fsec) == 0) { if (*field[i] == '-') { /* flip the sign on all fields */ tm->tm_hour = -tm->tm_hour; tm->tm_min = -tm->tm_min; tm->tm_sec = -tm->tm_sec; *fsec = -(*fsec); } /* * Set the next type to be a day, if units are not * specified. This handles the case of '1 +02:03' since we * are reading right to left. */ type = DTK_DAY; tmask = DTK_M(TZ); break; } else if (type == IGNORE_DTF) { if (*cp == '.') { /* * Got a decimal point? Then assume some sort of * seconds specification */ type = DTK_SECOND; } else if (*cp == '\0') { /* * Only a signed integer? Then must assume a * timezone-like usage */ type = DTK_HOUR; } } /* DROP THROUGH */ case DTK_DATE: case DTK_NUMBER: val = strtol(field[i], &cp, 10); if (type == IGNORE_DTF) type = DTK_SECOND; if (*cp == '.') { fval = strtod(cp, &cp); if (*cp != '\0') return -1; if (val < 0) fval = -fval; } else if (*cp == '\0') fval = 0; else return -1; tmask = 0; /* DTK_M(type); */ switch (type) { case DTK_MICROSEC: #ifdef HAVE_INT64_TIMESTAMP *fsec += val + fval; #else *fsec += (val + fval) * 1e-6; #endif break; case DTK_MILLISEC: #ifdef HAVE_INT64_TIMESTAMP *fsec += (val + fval) * 1000; #else *fsec += (val + fval) * 1e-3; #endif break; case DTK_SECOND: tm->tm_sec += val; #ifdef HAVE_INT64_TIMESTAMP *fsec += fval * 1000000; #else *fsec += fval; #endif tmask = DTK_M(SECOND); break; case DTK_MINUTE: tm->tm_min += val; if (fval != 0) { int sec; fval *= SECS_PER_MINUTE; sec = fval; tm->tm_sec += sec; #ifdef HAVE_INT64_TIMESTAMP *fsec += ((fval - sec) * 1000000); #else *fsec += fval - sec; #endif } tmask = DTK_M(MINUTE); break; case DTK_HOUR: tm->tm_hour += val; if (fval != 0) { int sec; fval *= SECS_PER_HOUR; sec = fval; tm->tm_sec += sec; #ifdef HAVE_INT64_TIMESTAMP *fsec += (fval - sec) * 1000000; #else *fsec += fval - sec; #endif } tmask = DTK_M(HOUR); break; case DTK_DAY: tm->tm_mday += val; if (fval != 0) { int sec; fval *= SECS_PER_DAY; sec = fval; tm->tm_sec += sec; #ifdef HAVE_INT64_TIMESTAMP *fsec += (fval - sec) * 1000000; #else *fsec += fval - sec; #endif } tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY); break; case DTK_WEEK: tm->tm_mday += val * 7; if (fval != 0) { int extra_days; fval *= 7; extra_days = (int32) fval; tm->tm_mday += extra_days; fval -= extra_days; if (fval != 0) { int sec; fval *= SECS_PER_DAY; sec = fval; tm->tm_sec += sec; #ifdef HAVE_INT64_TIMESTAMP *fsec += (fval - sec) * 1000000; #else *fsec += fval - sec; #endif } } tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY); break; case DTK_MONTH: tm->tm_mon += val; if (fval != 0) { int day; fval *= DAYS_PER_MONTH; day = fval; tm->tm_mday += day; fval -= day; if (fval != 0) { int sec; fval *= SECS_PER_DAY; sec = fval; tm->tm_sec += sec; #ifdef HAVE_INT64_TIMESTAMP *fsec += (fval - sec) * 1000000; #else *fsec += fval - sec; #endif } } tmask = DTK_M(MONTH); break; case DTK_YEAR: tm->tm_year += val; if (fval != 0) tm->tm_mon += fval * MONTHS_PER_YEAR; tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); break; case DTK_DECADE: tm->tm_year += val * 10; if (fval != 0) tm->tm_mon += fval * MONTHS_PER_YEAR * 10; tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); break; case DTK_CENTURY: tm->tm_year += val * 100; if (fval != 0) tm->tm_mon += fval * MONTHS_PER_YEAR * 100; tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); break; case DTK_MILLENNIUM: tm->tm_year += val * 1000; if (fval != 0) tm->tm_mon += fval * MONTHS_PER_YEAR * 1000; tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR); break; default: return -1; } break; case DTK_STRING: case DTK_SPECIAL: type = DecodeUnits(i, field[i], &val); if (type == IGNORE_DTF) continue; tmask = 0; /* DTK_M(type); */ switch (type) { case UNITS: type = val; break; case AGO: is_before = TRUE; type = val; break; case RESERV: tmask = (DTK_DATE_M || DTK_TIME_M); *dtype = val; break; default: return -1; } break; default: return -1; } if (tmask & fmask) return -1; fmask |= tmask; } if (*fsec != 0) { int sec; #ifdef HAVE_INT64_TIMESTAMP sec = *fsec / USECS_PER_SEC; *fsec -= sec * USECS_PER_SEC; #else TMODULO(*fsec, sec, 1.0); #endif tm->tm_sec += sec; } if (is_before) { *fsec = -(*fsec); tm->tm_sec = -(tm->tm_sec); tm->tm_min = -(tm->tm_min); tm->tm_hour = -(tm->tm_hour); tm->tm_mday = -(tm->tm_mday); tm->tm_mon = -(tm->tm_mon); tm->tm_year = -(tm->tm_year); } /* ensure that at least one time field has been found */ return (fmask != 0) ? 0 : -1; } /* DecodeInterval() */
/* DecodeDate() * Decode date string which includes delimiters. * Return 0 if okay, a DTERR code if not. * * str: field to be parsed * fmask: bitmask for field types already seen * *tmask: receives bitmask for fields found here * *is2digits: set to TRUE if we find 2-digit year * *tm: field values are stored into appropriate members of this struct */ int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits, struct tm *tm) { fsec_t fsec; int nf = 0; int i, len; int dterr; bool haveTextMonth = FALSE; int type, val, dmask = 0; char *field[MAXDATEFIELDS]; *tmask = 0; /* parse this string... */ while (*str != '\0' && nf < MAXDATEFIELDS) { /* skip field separators */ while (*str != '\0' && !isalnum((unsigned char) *str)) str++; if (*str == '\0') return DTERR_BAD_FORMAT; /* end of string after separator */ 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++; } /* 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; haveTextMonth = TRUE; break; default: return DTERR_BAD_FORMAT; } if (fmask & dmask) return DTERR_BAD_FORMAT; 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 DTERR_BAD_FORMAT; dterr = DecodeNumber(len, field[i], haveTextMonth, fmask, &dmask, tm, &fsec, is2digits); if (dterr) return dterr; if (fmask & dmask) return DTERR_BAD_FORMAT; fmask |= dmask; *tmask |= dmask; } if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M) return DTERR_BAD_FORMAT; /* validation of the field values must wait until ValidateDate() */ return 0; }