/* 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). */ int tm2timestamp(struct tm * tm, fsec_t fsec, int *tzp, timestamp * result) { #ifdef HAVE_INT64_TIMESTAMP int dDate; int64 time; #else double dDate, time; #endif /* Julian day routines are not correct for negative Julian days */ 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); time = time2t(tm->tm_hour, tm->tm_min, tm->tm_sec, fsec); #ifdef HAVE_INT64_TIMESTAMP *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) */ if ((*result < 0 && dDate >= 0) || (*result >= 0 && dDate < 0)) return -1; #else *result = dDate * SECS_PER_DAY + time; #endif if (tzp != NULL) *result = dt2local(*result, -(*tzp)); return 0; } /* tm2timestamp() */
void PGTYPESdate_mdyjul(int *mdy, date * jdate) { /* month is mdy[0] */ /* day is mdy[1] */ /* year is mdy[2] */ *jdate = (date) (date2j(mdy[2], mdy[0], mdy[1]) - date2j(2000, 1, 1)); }
void PGTYPESdate_today(date * d) { struct tm ts; GetCurrentDateTime(&ts); *d = date2j(ts.tm_year, ts.tm_mon, ts.tm_mday) - date2j(2000, 1, 1); return; }
date PGTYPESdate_from_asc(char *str, char **endptr) { date dDate; fsec_t fsec; struct tm tt, *tm = &tt; int dtype; int nf; char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; char lowstr[MAXDATELEN + 1]; char *realptr; char **ptr = (endptr != NULL) ? endptr : &realptr; bool EuroDates = FALSE; errno = 0; if (strlen(str) >= sizeof(lowstr)) { errno = PGTYPES_DATE_BAD_DATE; return INT_MIN; } if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 || DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, EuroDates) != 0) { errno = PGTYPES_DATE_BAD_DATE; return INT_MIN; } switch (dtype) { case DTK_DATE: break; case DTK_EPOCH: if (GetEpochTime(tm) < 0) { errno = PGTYPES_DATE_BAD_DATE; return INT_MIN; } break; default: errno = PGTYPES_DATE_BAD_DATE; return INT_MIN; } dDate = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1)); return dDate; }
Datum plvdate_days_inmonth(PG_FUNCTION_ARGS) { DateADT day = PG_GETARG_DATEADT(0); int result; int y, m, d; j2date(day + POSTGRES_EPOCH_JDATE, &y, &m, &d); result = date2j(y, m+1, 1) - date2j(y, m, 1); PG_RETURN_INT32(result); }
/* * returns true, when day d is any easter holiday. * */ static bool easter_holidays(DateADT day, int y, int m) { if (use_great_friday || use_easter) { if (m == 3 || m == 4) { int easter_sunday_day; int easter_sunday_month; int easter_sunday; calc_easter_sunday(y, &easter_sunday_day, &easter_sunday_month); easter_sunday = date2j(y, easter_sunday_month, easter_sunday_day) - POSTGRES_EPOCH_JDATE; if (use_easter && (day == easter_sunday || day == easter_sunday + 1)) return true; if (use_great_friday && day == easter_sunday - 2) { /* Great Friday is introduced in Czech Republic in 2016 */ if (country_id == 0) { if (y >= 2016) return true; } else return true; } } } return false; }
Datum add_months(PG_FUNCTION_ARGS) { DateADT day = PG_GETARG_DATEADT(0); int n = PG_GETARG_INT32(1); int y, m, d; int days; DateADT result; div_t v; bool last_day; j2date(day + POSTGRES_EPOCH_JDATE, &y, &m, &d); last_day = (d == days_of_month(y, m)); v = div(y * 12 + m - 1 + n, 12); y = v.quot; if (y < 0) y += 1; /* offset because of year 0 */ m = v.rem + 1; days = days_of_month(y, m); if (last_day || d > days) d = days; result = date2j(y, m, d) - POSTGRES_EPOCH_JDATE; PG_RETURN_DATEADT (result); }
int PGTYPESdate_dayofweek(date dDate) { /* * Sunday: 0 Monday: 1 Tuesday: 2 Wednesday: 3 Thursday: 4 * Friday: 5 Saturday: 6 */ return (int) (dDate + date2j(2000, 1, 1) + 1) % 7; }
Datum last_day(PG_FUNCTION_ARGS) { DateADT day = PG_GETARG_DATEADT(0); DateADT result; int y, m, d; j2date(day + POSTGRES_EPOCH_JDATE, &y, &m, &d); result = date2j(y, m+1, 1) - POSTGRES_EPOCH_JDATE; PG_RETURN_DATEADT(result - 1); }
void PGTYPESdate_julmdy(date jd, int *mdy) { int y, m, d; j2date((int) (jd + date2j(2000, 1, 1)), &y, &m, &d); mdy[0] = m; mdy[1] = d; mdy[2] = y; }
char * PGTYPESdate_to_asc(date dDate) { struct tm tt, *tm = &tt; char buf[MAXDATELEN + 1]; int DateStyle = 1; bool EuroDates = FALSE; j2date(dDate + date2j(2000, 1, 1), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); EncodeDateOnly(tm, DateStyle, buf, EuroDates); return pgtypes_strdup(buf); }
/* 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). */ int tm2timestamp(struct tm * tm, fsec_t fsec, int *tzp, timestamp * result) { #ifdef HAVE_INT64_TIMESTAMP int dDate; int64 time; #else double dDate, time; #endif /* Prevent overflow in Julian-day routines */ 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); time = time2t(tm->tm_hour, tm->tm_min, tm->tm_sec, fsec); #ifdef HAVE_INT64_TIMESTAMP *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; #else *result = dDate * SECS_PER_DAY + time; #endif if (tzp != NULL) *result = dt2local(*result, -(*tzp)); /* final range check catches just-out-of-range timestamps */ if (!IS_VALID_TIMESTAMP(*result)) return -1; return 0; } /* tm2timestamp() */
Datum get_semester(PG_FUNCTION_ARGS) { pg_tm current_date; GetCurrentDateTime(¤t_date); DateADT entering_date = DatumGetDateADT(PG_GETARG_DATUM(0)); int entering_year, entering_month, entering_day; j2date(entering_date + date2j(2000, 1, 1), &entering_year, &entering_month, &entering_day); int semester = (current_date.tm_year - entering_year) * 2; if (current_date.tm_mon > 6) semester++; PG_RETURN_INT32(semester); }
/* tm2abstime() * Convert a tm structure to abstime. * Note that tm has full year (not 1900-based) and 1-based month. */ static AbsoluteTime tm2abstime(struct pg_tm * tm, int tz) { int day; AbsoluteTime sec; /* validate, before going out of range on some members */ if (tm->tm_year < 1901 || tm->tm_year > 2038 || tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR || tm->tm_mday < 1 || tm->tm_mday > 31 || tm->tm_hour < 0 || tm->tm_hour > HOURS_PER_DAY || /* test for > 24:00:00 */ (tm->tm_hour == HOURS_PER_DAY && (tm->tm_min > 0 || tm->tm_sec > 0)) || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 || tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE) return INVALID_ABSTIME; day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE; /* check for time out of range */ if (day < MIN_DAYNUM || day > MAX_DAYNUM) return INVALID_ABSTIME; /* convert to seconds */ sec = tm->tm_sec + tz + (tm->tm_min + (day * HOURS_PER_DAY + tm->tm_hour) * MINS_PER_HOUR) * SECS_PER_MINUTE; /* * check for overflow. We need a little slop here because the H/M/S plus * TZ offset could add up to more than 1 day. */ if ((day >= MAX_DAYNUM - 10 && sec < 0) || (day <= MIN_DAYNUM + 10 && sec > 0)) return INVALID_ABSTIME; /* check for reserved values (e.g. "current" on edge of usual range */ if (!AbsoluteTimeIsReal(sec)) return INVALID_ABSTIME; return sec; }
/* * PGTYPESdate_defmt_asc * * function works as follows: * - first we analyze the paramters * - if this is a special case with no delimiters, add delimters * - find the tokens. First we look for numerical values. If we have found * less than 3 tokens, we check for the months' names and thereafter for * the abbreviations of the months' names. * - then we see which parameter should be the date, the month and the * year and from these values we calculate the date */ #define PGTYPES_DATE_MONTH_MAXLENGTH 20 /* probably even less :-) */ int PGTYPESdate_defmt_asc(date * d, char *fmt, char *str) { /* * token[2] = { 4,6 } means that token 2 starts at position 4 and ends at * (including) position 6 */ int token[3][2]; int token_values[3] = {-1, -1, -1}; char *fmt_token_order; char *fmt_ystart, *fmt_mstart, *fmt_dstart; int i; int reading_digit; int token_count; char *str_copy; struct tm tm; tm.tm_year = tm.tm_mon = tm.tm_mday = 0; /* keep compiler quiet */ if (!d || !str || !fmt) { errno = PGTYPES_DATE_ERR_EARGS; return -1; } /* analyze the fmt string */ fmt_ystart = strstr(fmt, "yy"); fmt_mstart = strstr(fmt, "mm"); fmt_dstart = strstr(fmt, "dd"); if (!fmt_ystart || !fmt_mstart || !fmt_dstart) { errno = PGTYPES_DATE_ERR_EARGS; return -1; } if (fmt_ystart < fmt_mstart) { /* y m */ if (fmt_dstart < fmt_ystart) { /* d y m */ fmt_token_order = "dym"; } else if (fmt_dstart > fmt_mstart) { /* y m d */ fmt_token_order = "ymd"; } else { /* y d m */ fmt_token_order = "ydm"; } } else { /* fmt_ystart > fmt_mstart */ /* m y */ if (fmt_dstart < fmt_mstart) { /* d m y */ fmt_token_order = "dmy"; } else if (fmt_dstart > fmt_ystart) { /* m y d */ fmt_token_order = "myd"; } else { /* m d y */ fmt_token_order = "mdy"; } } /* * handle the special cases where there is no delimiter between the * digits. If we see this: * * only digits, 6 or 8 bytes then it might be ddmmyy and ddmmyyyy (or * similar) * * we reduce it to a string with delimiters and continue processing */ /* check if we have only digits */ reading_digit = 1; for (i = 0; str[i]; i++) { if (!isdigit((unsigned char) str[i])) { reading_digit = 0; break; } } if (reading_digit) { int frag_length[3]; int target_pos; i = strlen(str); if (i != 8 && i != 6) { errno = PGTYPES_DATE_ERR_ENOSHORTDATE; return -1; } /* okay, this really is the special case */ /* * as long as the string, one additional byte for the terminator and 2 * for the delimiters between the 3 fiedls */ str_copy = pgtypes_alloc(strlen(str) + 1 + 2); if (!str_copy) return -1; /* determine length of the fragments */ if (i == 6) { frag_length[0] = 2; frag_length[1] = 2; frag_length[2] = 2; } else { if (fmt_token_order[0] == 'y') { frag_length[0] = 4; frag_length[1] = 2; frag_length[2] = 2; } else if (fmt_token_order[1] == 'y') { frag_length[0] = 2; frag_length[1] = 4; frag_length[2] = 2; } else { frag_length[0] = 2; frag_length[1] = 2; frag_length[2] = 4; } } target_pos = 0; /* * XXX: Here we could calculate the positions of the tokens and save * the for loop down there where we again check with isdigit() for * digits. */ for (i = 0; i < 3; i++) { int start_pos = 0; if (i >= 1) start_pos += frag_length[0]; if (i == 2) start_pos += frag_length[1]; strncpy(str_copy + target_pos, str + start_pos, frag_length[i]); target_pos += frag_length[i]; if (i != 2) { str_copy[target_pos] = ' '; target_pos++; } } str_copy[target_pos] = '\0'; } else { str_copy = pgtypes_strdup(str); if (!str_copy) return -1; /* convert the whole string to lower case */ for (i = 0; str_copy[i]; i++) str_copy[i] = (char) pg_tolower((unsigned char) str_copy[i]); } /* look for numerical tokens */ reading_digit = 0; token_count = 0; for (i = 0; i < strlen(str_copy); i++) { if (!isdigit((unsigned char) str_copy[i]) && reading_digit) { /* the token is finished */ token[token_count][1] = i - 1; reading_digit = 0; token_count++; } else if (isdigit((unsigned char) str_copy[i]) && !reading_digit) { /* we have found a token */ token[token_count][0] = i; reading_digit = 1; } } /* * we're at the end of the input string, but maybe we are still reading a * number... */ if (reading_digit) { token[token_count][1] = i - 1; token_count++; } if (token_count < 2) { /* * not all tokens found, no way to find 2 missing tokens with string * matches */ free(str_copy); errno = PGTYPES_DATE_ERR_ENOSHORTDATE; return -1; } if (token_count != 3) { /* * not all tokens found but we may find another one with string * matches by testing for the months names and months abbreviations */ char *month_lower_tmp = pgtypes_alloc(PGTYPES_DATE_MONTH_MAXLENGTH); char *start_pos; int j; int offset; int found = 0; char **list; if (!month_lower_tmp) { /* free variables we alloc'ed before */ free(str_copy); return -1; } list = pgtypes_date_months; for (i = 0; list[i]; i++) { for (j = 0; j < PGTYPES_DATE_MONTH_MAXLENGTH; j++) { month_lower_tmp[j] = (char) pg_tolower((unsigned char) list[i][j]); if (!month_lower_tmp[j]) { /* properly terminated */ break; } } if ((start_pos = strstr(str_copy, month_lower_tmp))) { offset = start_pos - str_copy; /* * sort the new token into the numeric tokens, shift them if * necessary */ if (offset < token[0][0]) { token[2][0] = token[1][0]; token[2][1] = token[1][1]; token[1][0] = token[0][0]; token[1][1] = token[0][1]; token_count = 0; } else if (offset < token[1][0]) { token[2][0] = token[1][0]; token[2][1] = token[1][1]; token_count = 1; } else token_count = 2; token[token_count][0] = offset; token[token_count][1] = offset + strlen(month_lower_tmp) - 1; /* * the value is the index of the month in the array of months * + 1 (January is month 0) */ token_values[token_count] = i + 1; found = 1; break; } /* * evil[tm] hack: if we read the pgtypes_date_months and haven't * found a match, reset list to point to pgtypes_date_months_short * and reset the counter variable i */ if (list == pgtypes_date_months) { if (list[i + 1] == NULL) { list = months; i = -1; } } } if (!found) { free(month_lower_tmp); free(str_copy); errno = PGTYPES_DATE_ERR_ENOTDMY; return -1; } /* * here we found a month. token[token_count] and * token_values[token_count] reflect the month's details. * * only the month can be specified with a literal. Here we can do a * quick check if the month is at the right position according to the * format string because we can check if the token that we expect to * be the month is at the position of the only token that already has * a value. If we wouldn't check here we could say "December 4 1990" * with a fmt string of "dd mm yy" for 12 April 1990. */ if (fmt_token_order[token_count] != 'm') { /* deal with the error later on */ token_values[token_count] = -1; } free(month_lower_tmp); } /* terminate the tokens with ASCII-0 and get their values */ for (i = 0; i < 3; i++) { *(str_copy + token[i][1] + 1) = '\0'; /* A month already has a value set, check for token_value == -1 */ if (token_values[i] == -1) { errno = 0; token_values[i] = strtol(str_copy + token[i][0], (char **) NULL, 10); /* strtol sets errno in case of an error */ if (errno) token_values[i] = -1; } if (fmt_token_order[i] == 'd') tm.tm_mday = token_values[i]; else if (fmt_token_order[i] == 'm') tm.tm_mon = token_values[i]; else if (fmt_token_order[i] == 'y') tm.tm_year = token_values[i]; } free(str_copy); if (tm.tm_mday < 1 || tm.tm_mday > 31) { errno = PGTYPES_DATE_BAD_DAY; return -1; } if (tm.tm_mon < 1 || tm.tm_mon > MONTHS_PER_YEAR) { errno = PGTYPES_DATE_BAD_MONTH; return -1; } if (tm.tm_mday == 31 && (tm.tm_mon == 4 || tm.tm_mon == 6 || tm.tm_mon == 9 || tm.tm_mon == 11)) { errno = PGTYPES_DATE_BAD_DAY; return -1; } if (tm.tm_mon == 2 && tm.tm_mday > 29) { errno = PGTYPES_DATE_BAD_DAY; return -1; } *d = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - date2j(2000, 1, 1); return 0; }
int PGTYPESdate_fmt_asc(date dDate, char *fmtstring, char *outbuf) { static struct { char *format; int component; } mapping[] = { /* * format items have to be sorted according to their length, since the * first pattern that matches gets replaced by its value */ { "ddd", PGTYPES_FMTDATE_DOW_LITERAL_SHORT }, { "dd", PGTYPES_FMTDATE_DAY_DIGITS_LZ }, { "mmm", PGTYPES_FMTDATE_MONTH_LITERAL_SHORT }, { "mm", PGTYPES_FMTDATE_MONTH_DIGITS_LZ }, { "yyyy", PGTYPES_FMTDATE_YEAR_DIGITS_LONG }, { "yy", PGTYPES_FMTDATE_YEAR_DIGITS_SHORT }, { NULL, 0 } }; union un_fmt_comb replace_val; int replace_type; int i; int dow; char *start_pattern; struct tm tm; /* copy the string over */ strcpy(outbuf, fmtstring); /* get the date */ j2date(dDate + date2j(2000, 1, 1), &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); dow = PGTYPESdate_dayofweek(dDate); for (i = 0; mapping[i].format != NULL; i++) { while ((start_pattern = strstr(outbuf, mapping[i].format)) != NULL) { switch (mapping[i].component) { case PGTYPES_FMTDATE_DOW_LITERAL_SHORT: replace_val.str_val = pgtypes_date_weekdays_short[dow]; replace_type = PGTYPES_TYPE_STRING_CONSTANT; break; case PGTYPES_FMTDATE_DAY_DIGITS_LZ: replace_val.uint_val = tm.tm_mday; replace_type = PGTYPES_TYPE_UINT_2_LZ; break; case PGTYPES_FMTDATE_MONTH_LITERAL_SHORT: replace_val.str_val = months[tm.tm_mon - 1]; replace_type = PGTYPES_TYPE_STRING_CONSTANT; break; case PGTYPES_FMTDATE_MONTH_DIGITS_LZ: replace_val.uint_val = tm.tm_mon; replace_type = PGTYPES_TYPE_UINT_2_LZ; break; case PGTYPES_FMTDATE_YEAR_DIGITS_LONG: replace_val.uint_val = tm.tm_year; replace_type = PGTYPES_TYPE_UINT_4_LZ; break; case PGTYPES_FMTDATE_YEAR_DIGITS_SHORT: replace_val.uint_val = tm.tm_year % 100; replace_type = PGTYPES_TYPE_UINT_2_LZ; break; default: /* * should not happen, set something anyway */ replace_val.str_val = " "; replace_type = PGTYPES_TYPE_STRING_CONSTANT; } switch (replace_type) { case PGTYPES_TYPE_STRING_MALLOCED: case PGTYPES_TYPE_STRING_CONSTANT: strncpy(start_pattern, replace_val.str_val, strlen(replace_val.str_val)); if (replace_type == PGTYPES_TYPE_STRING_MALLOCED) free(replace_val.str_val); break; case PGTYPES_TYPE_UINT: { char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS); if (!t) return -1; snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS, "%u", replace_val.uint_val); strncpy(start_pattern, t, strlen(t)); free(t); } break; case PGTYPES_TYPE_UINT_2_LZ: { char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS); if (!t) return -1; snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS, "%02u", replace_val.uint_val); strncpy(start_pattern, t, strlen(t)); free(t); } break; case PGTYPES_TYPE_UINT_4_LZ: { char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS); if (!t) return -1; snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS, "%04u", replace_val.uint_val); strncpy(start_pattern, t, strlen(t)); free(t); } break; default: /* * doesn't happen (we set replace_type to * PGTYPES_TYPE_STRING_CONSTANT in case of an error above) */ break; } } } return 0; }
/* timestamptz_pl_interval() * Add a interval to a timestamp with time zone data type. * Note that interval has provisions for qualitative year/month * units, so try to do the right thing with them. * To add a month, increment the month, and use the same day of month. * Then, if the next month has fewer days, set the day of month * to the last day of month. * Lastly, add in the "quantitative time". */ TimestampTz timestamptz_pl_interval(TimestampTz timestamp, Interval *span) { TimestampTz result; int tz; if (TIMESTAMP_NOT_FINITE(timestamp)) result = timestamp; else { if (span->month != 0) { struct tm tt, *tm = &tt; fsec_t fsec; if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) warnx("timestamp out of range"); 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]); tz = DetermineTimeZoneOffset(tm, session_timezone); if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) warnx("timestamp out of range"); } if (span->day != 0) { struct tm tt, *tm = &tt; fsec_t fsec; int julian; if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) warnx("timestamp out of range"); /* Add days by converting to and from julian */ julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day; j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); tz = DetermineTimeZoneOffset(tm, session_timezone); if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) warnx("timestamp out of range"); } timestamp += span->time; result = timestamp; } return result; }
/* 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; }
/* DetermineTimeZoneOffset() * * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and * tm_sec fields are set, attempt to determine the applicable time zone * (ie, regular or daylight-savings time) at that time. Set the struct pg_tm's * tm_isdst field accordingly, and return the actual timezone offset. * * Note: it might seem that we should use mktime() for this, but bitter * experience teaches otherwise. This code is much faster than most versions * of mktime(), anyway. */ int DetermineTimeZoneOffset(struct tm * tm, pg_tz *tzp) { int date, sec; pg_time_t day, mytime, prevtime, boundary, beforetime, aftertime; long int before_gmtoff, after_gmtoff; int before_isdst, after_isdst; int res; if (tzp == session_timezone && HasCTZSet) { tm->tm_isdst = 0; /* for lack of a better idea */ return CTimeZone; } /* * First, generate the pg_time_t value corresponding to the given * y/m/d/h/m/s taken as GMT time. If this overflows, punt and decide the * timezone is GMT. (We only need to worry about overflow on machines * where pg_time_t is 32 bits.) */ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) goto overflow; date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE; day = ((pg_time_t) date) * SECS_PER_DAY; if (day / SECS_PER_DAY != date) goto overflow; sec = tm->tm_sec + (tm->tm_min + tm->tm_hour * MINS_PER_HOUR) * SECS_PER_MINUTE; mytime = day + sec; /* since sec >= 0, overflow could only be from +day to -mytime */ if (mytime < 0 && day > 0) goto overflow; /* * Find the DST time boundary just before or following the target time. We * assume that all zones have GMT offsets less than 24 hours, and that DST * boundaries can't be closer together than 48 hours, so backing up 24 * hours and finding the "next" boundary will work. */ prevtime = mytime - SECS_PER_DAY; if (mytime < 0 && prevtime > 0) goto overflow; res = pg_next_dst_boundary(&prevtime, &before_gmtoff, &before_isdst, &boundary, &after_gmtoff, &after_isdst, tzp); if (res < 0) goto overflow; /* failure? */ if (res == 0) { /* Non-DST zone, life is simple */ tm->tm_isdst = before_isdst; return -(int) before_gmtoff; } /* * Form the candidate pg_time_t values with local-time adjustment */ beforetime = mytime - before_gmtoff; if ((before_gmtoff > 0 && mytime < 0 && beforetime > 0) || (before_gmtoff <= 0 && mytime > 0 && beforetime < 0)) goto overflow; aftertime = mytime - after_gmtoff; if ((after_gmtoff > 0 && mytime < 0 && aftertime > 0) || (after_gmtoff <= 0 && mytime > 0 && aftertime < 0)) goto overflow; /* * If both before or both after the boundary time, we know what to do */ if (beforetime <= boundary && aftertime < boundary) { tm->tm_isdst = before_isdst; return -(int) before_gmtoff; } if (beforetime > boundary && aftertime >= boundary) { tm->tm_isdst = after_isdst; return -(int) after_gmtoff; } /* * It's an invalid or ambiguous time due to timezone transition. Prefer * the standard-time interpretation. */ if (after_isdst == 0) { tm->tm_isdst = after_isdst; return -(int) after_gmtoff; } tm->tm_isdst = before_isdst; return -(int) before_gmtoff; overflow: /* Given date is out of range, so assume UTC */ tm->tm_isdst = 0; return 0; }
/* 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 timestamp2tm(timestamp dt, int *tzp, struct tm * tm, fsec_t *fsec, char **tzn) { #ifdef HAVE_INT64_TIMESTAMP int dDate, date0; int64 time; #else double dDate, date0; double time; #endif time_t utime; #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE) struct tm *tx; #endif date0 = date2j(2000, 1, 1); time = dt; #ifdef HAVE_INT64_TIMESTAMP TMODULO(time, dDate, INT64CONST(86400000000)); if (time < INT64CONST(0)) { time += INT64CONST(86400000000); dDate -= 1; } #else TMODULO(time, dDate, 86400e0); if (time < 0) { time += 86400; dDate -= 1; } #endif /* Julian day routine does not work for negative Julian days */ if (dDate < -date0) return -1; /* add offset to go from J2000 back to standard Julian date */ dDate += date0; j2date((int) dDate, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec); 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)) { #ifdef HAVE_INT64_TIMESTAMP utime = ((dt / INT64CONST(1000000)) + ((date0 - date2j(1970, 1, 1)) * INT64CONST(86400))); #else utime = (dt + ((date0 - date2j(1970, 1, 1)) * 86400)); #endif #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE) 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_TM_ZONE) tm->tm_gmtoff = tx->tm_gmtoff; tm->tm_zone = tx->tm_zone; *tzp = -(tm->tm_gmtoff); /* tm_gmtoff is Sun/DEC-ism */ if (tzn != NULL) *tzn = (char *) tm->tm_zone; #elif defined(HAVE_INT_TIMEZONE) *tzp = ((tm->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL); if (tzn != NULL) *tzn = tzname[(tm->tm_isdst > 0)]; #endif #else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */ *tzp = 0; /* Mark this as *no* time zone available */ tm->tm_isdst = -1; if (tzn != NULL) *tzn = NULL; #endif dt = dt2local(dt, *tzp); } 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; } return 0; } /* timestamp2tm() */
static DateADT _ora_date_round(DateADT day, int f) { int y, m, d, z; DateADT result; j2date(day + POSTGRES_EPOCH_JDATE, &y, &m, &d); switch (f) { CASE_fmt_CC if (y > 0) result = DATE2J((y/100)*100+(day < DATE2J((y/100)*100+50,1,1) ?1:101),1,1); else result = DATE2J((y/100)*100+(day < DATE2J((y/100)*100-50+1,1,1) ?-99:1),1,1); break; CASE_fmt_YYYY result = DATE2J(y+(day<DATE2J(y,7,1)?0:1),1,1); break; CASE_fmt_IYYY { if (day < DATE2J(y,7,1)) { result = iso_year(y, m, d); } else { DateADT iy1 = iso_year(y+1, 1, 8); result = iy1; if (((day - DATE2J(y,1,1)) / 7 + 1) >= 52) { bool overl = ((date2j(y+2,1,1)-date2j(y+1,1,1)) == 366); bool isSaturday = (J2DAY(day) == 6); DateADT iy2 = iso_year(y+2, 1, 8); DateADT day1 = DATE2J(y+1,1,1); /* exception saturdays */ if (iy1 >= (day1) && day >= day1 - 2 && isSaturday) { result = overl?iy2:iy1; } /* iso year stars in last year and day >= iso year */ else if (iy1 <= (day1) && day >= iy1 - 3) { DateADT cmp = iy1 - (iy1 < day1?0:1); int d = J2DAY(day1); /* some exceptions */ if ((day >= cmp - 2) && (!(d == 3 && overl))) { /* if year don't starts in thursday */ if ((d < 4 && J2DAY(day) != 5 && !isSaturday) ||(d == 2 && isSaturday && overl)) { result = iy2; } } } } } break; } CASE_fmt_MON result = DATE2J(y,m+(day<DATE2J(y,m,16)?0:1),1); break; CASE_fmt_WW z = (day - DATE2J(y,1,1)) % 7; result = day - z + (z < 4?0:7); break; CASE_fmt_IW { z = (day - iso_year(y,m,d)) % 7; result = day - z + (z < 4?0:7); if (((day - DATE2J(y,1,1)) / 7 + 1) >= 52) { /* only for last iso week */ DateADT isoyear = iso_year(y+1, 1, 8); if (isoyear > (DATE2J(y+1,1,1)-1)) if (day > isoyear - 7) { int d = J2DAY(day); result -= (d == 0 || d > 4?7:0); } } break; } CASE_fmt_W z = (day - DATE2J(y,m,1)) % 7; result = day - z + (z < 4?0:7); break; CASE_fmt_DAY z = J2DAY(day); if (y > 0) result = day - z + (z < 4?0:7); else result = day + (5 - (z>0?(z>1?z:z+7):7)); break; CASE_fmt_Q result = DATE2J(y,((m-1)/3)*3+(day<(DATE2J(y,((m-1)/3)*3+2,16))?1:4),1); break; default: result = day; } return result; }
/* 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 timestamp2tm(timestamp dt, int *tzp, struct tm * tm, fsec_t *fsec, char **tzn) { #ifdef HAVE_INT64_TIMESTAMP int64 dDate, date0; int64 time; #else double dDate, date0; double time; #endif time_t utime; #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE) struct tm *tx; #endif date0 = date2j(2000, 1, 1); #ifdef HAVE_INT64_TIMESTAMP time = dt; TMODULO(time, dDate, USECS_PER_DAY); if (time < INT64CONST(0)) { time += USECS_PER_DAY; dDate -= 1; } /* 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); #else time = dt; TMODULO(time, dDate, (double) SECS_PER_DAY); if (time < 0) { time += SECS_PER_DAY; dDate -= 1; } /* add offset to go from J2000 back to standard Julian date */ dDate += date0; recalc_d: /* 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); recalc_t: dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec); *fsec = TSROUND(*fsec); /* roundoff may need to propagate to higher-order fields */ if (*fsec >= 1.0) { time = ceil(time); if (time >= (double) SECS_PER_DAY) { time = 0; dDate += 1; goto recalc_d; } goto recalc_t; } #endif 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)) { #ifdef HAVE_INT64_TIMESTAMP utime = dt / USECS_PER_SEC + ((date0 - date2j(1970, 1, 1)) * INT64CONST(86400)); #else utime = dt + (date0 - date2j(1970, 1, 1)) * SECS_PER_DAY; #endif #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE) 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_TM_ZONE) tm->tm_gmtoff = tx->tm_gmtoff; tm->tm_zone = tx->tm_zone; *tzp = -tm->tm_gmtoff; /* tm_gmtoff is Sun/DEC-ism */ if (tzn != NULL) *tzn = (char *) tm->tm_zone; #elif defined(HAVE_INT_TIMEZONE) *tzp = (tm->tm_isdst > 0) ? TIMEZONE_GLOBAL - SECS_PER_HOUR : TIMEZONE_GLOBAL; if (tzn != NULL) *tzn = TZNAME_GLOBAL[(tm->tm_isdst > 0)]; #endif #else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */ *tzp = 0; /* Mark this as *no* time zone available */ tm->tm_isdst = -1; if (tzn != NULL) *tzn = NULL; #endif dt = dt2local(dt, *tzp); } 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; } return 0; } /* timestamp2tm() */