diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 9e291b5..fa50d79 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS) PG_RETURN_DATEADT(result); } + /* - * Internal routines for promoting date to timestamp and timestamp with - * time zone + * Promote date to timestamp. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set + * and zero is returned. */ - -static Timestamp -date2timestamp(DateADT dateVal) +Timestamp +date2timestamp_opt_error(DateADT dateVal, bool *have_error) { Timestamp result; @@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal) * boundary need be checked for overflow. */ if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range for timestamp"))); + { + if (have_error) + { + *have_error = true; + return (Timestamp) 0; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); + } + } /* date is days since 2000, timestamp is microseconds since same... */ result = dateVal * USECS_PER_DAY; @@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal) return result; } +/* + * Single-argument version of date2timestamp_opt_error(). + */ static TimestampTz -date2timestamptz(DateADT dateVal) +date2timestamp(DateADT dateVal) +{ + return date2timestamp_opt_error(dateVal, NULL); +} + +/* + * Promote date to timestamp with time zone. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set + * and zero is returned. + */ +TimestampTz +date2timestamptz_opt_error(DateADT dateVal, bool *have_error) { TimestampTz result; struct pg_tm tt, @@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal) * boundary need be checked for overflow. */ if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range for timestamp"))); + { + if (have_error) + { + *have_error = true; + return (TimestampTz) 0; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); + } + } j2date(dateVal + POSTGRES_EPOCH_JDATE, &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); @@ -621,15 +658,34 @@ date2timestamptz(DateADT dateVal) * of time zone, check for allowed timestamp range after adding tz. */ if (!IS_VALID_TIMESTAMP(result)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range for timestamp"))); + { + if (have_error) + { + *have_error = true; + return (TimestampTz) 0; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); + } + } } return result; } /* + * Single-argument version of date2timestamptz_opt_error(). + */ +static TimestampTz +date2timestamptz(DateADT dateVal) +{ + return date2timestamptz_opt_error(dateVal, NULL); +} + +/* * date2timestamp_no_overflow * * This is chartered to produce a double value that is numerically diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 9031432..a3b15b5 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -99,6 +99,43 @@ #include "utils/pg_locale.h" /* ---------- + * Convenience macros for error handling + * ---------- + * + * Two macros below help to handle errors in functions that take + * 'bool *have_error' argument. When this argument is not NULL, it's expected + * that function will suppress ereports when possible. Instead it should + * return some default value and set *have_error flag. + * + * RETURN_ERROR() macro intended to wrap ereport() calls. When have_error + * function argument is not NULL, then instead of ereport'ing we set + * *have_error flag and go to on_error label. It's supposed that jump + * resources will be freed and some 'default' value returned. + * + * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set. + * It's supposed to be used for immediate exit from the function on error + * after call of another function with 'bool *have_error' argument. + */ +#define RETURN_ERROR(throw_error) \ +do { \ + if (have_error) \ + { \ + *have_error = true; \ + goto on_error; \ + } \ + else \ + { \ + throw_error; \ + } \ +} while (0) + +#define CHECK_ERROR \ +do { \ + if (have_error && *have_error) \ + goto on_error; \ +} while (0) + +/* ---------- * Routines type * ---------- */ @@ -1008,7 +1045,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw, static void DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid collid); static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out, - bool strict); + bool strict, bool *have_error); #ifdef DEBUG_TO_FROM_CHAR static void dump_index(const KeyWord *k, const int *index); @@ -1019,14 +1056,21 @@ static const char *get_th(char *num, int type); static char *str_numth(char *dest, char *num, int type); static int adjust_partial_year_to_2020(int year); static int strspace_len(char *str); -static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode); -static void from_char_set_int(int *dest, const int value, const FormatNode *node); -static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node); -static int from_char_parse_int(int *dest, char **src, FormatNode *node); +static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, + bool *have_error); +static void from_char_set_int(int *dest, const int value, const FormatNode *node, + bool *have_error); +static int from_char_parse_int_len(int *dest, char **src, const int len, + FormatNode *node, bool *have_error); +static int from_char_parse_int(int *dest, char **src, FormatNode *node, + bool *have_error); static int seq_search(char *name, const char *const *array, int type, int max, int *len); -static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node); +static int from_char_seq_search(int *dest, char **src, + const char *const *array, int type, int max, + FormatNode *node, bool *have_error); static void do_to_timestamp(text *date_txt, text *fmt, bool strict, - struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags); + struct pg_tm *tm, fsec_t *fsec, int *fprec, + int *flags, bool *have_error); static char *fill_str(char *str, int c, int max); static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree); static char *int_to_roman(int number); @@ -2202,21 +2246,26 @@ strspace_len(char *str) * * Puke if the date mode has already been set, and the caller attempts to set * it to a conflicting mode. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set. */ static void -from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode) +from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error) { if (mode != FROM_CHAR_DATE_NONE) { if (tmfc->mode == FROM_CHAR_DATE_NONE) tmfc->mode = mode; else if (tmfc->mode != mode) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid combination of date conventions"), - errhint("Do not mix Gregorian and ISO week date " - "conventions in a formatting template."))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid combination of date conventions"), + errhint("Do not mix Gregorian and ISO week date " + "conventions in a formatting template.")))); } + +on_error: + return; } /* @@ -2224,18 +2273,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode) * * Puke if the destination integer has previously been set to some other * non-zero value. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set. */ static void -from_char_set_int(int *dest, const int value, const FormatNode *node) +from_char_set_int(int *dest, const int value, const FormatNode *node, + bool *have_error) { if (*dest != 0 && *dest != value) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("conflicting values for \"%s\" field in formatting string", - node->key->name), - errdetail("This value contradicts a previous setting for " - "the same field type."))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("conflicting values for \"%s\" field in " + "formatting string", + node->key->name), + errdetail("This value contradicts a previous setting " + "for the same field type.")))); *dest = value; + +on_error: + return; } /* @@ -2257,9 +2313,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node) * Note that from_char_parse_int() provides a more convenient wrapper where * the length of the field is the same as the length of the format keyword (as * with DD and MI). + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set + * and -1 is returned. */ static int -from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node) +from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node, + bool *have_error) { long result; char copy[DCH_MAX_ITEM_SIZ + 1]; @@ -2292,51 +2352,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node) char *last; if (used < len) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("source string too short for \"%s\" formatting field", - node->key->name), - errdetail("Field requires %d characters, but only %d " - "remain.", - len, used), - errhint("If your source string is not fixed-width, try " - "using the \"FM\" modifier."))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("source string too short for \"%s\" " + "formatting field", + node->key->name), + errdetail("Field requires %d characters, " + "but only %d remain.", + len, used), + errhint("If your source string is not fixed-width, " + "try using the \"FM\" modifier.")))); errno = 0; result = strtol(copy, &last, 10); used = last - copy; if (used > 0 && used < len) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid value \"%s\" for \"%s\"", - copy, node->key->name), - errdetail("Field requires %d characters, but only %d " - "could be parsed.", len, used), - errhint("If your source string is not fixed-width, try " - "using the \"FM\" modifier."))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("Field requires %d characters, " + "but only %d could be parsed.", + len, used), + errhint("If your source string is not fixed-width, " + "try using the \"FM\" modifier.")))); *src += used; } if (*src == init) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid value \"%s\" for \"%s\"", - copy, node->key->name), - errdetail("Value must be an integer."))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("Value must be an integer.")))); if (errno == ERANGE || result < INT_MIN || result > INT_MAX) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("value for \"%s\" in source string is out of range", - node->key->name), - errdetail("Value must be in the range %d to %d.", - INT_MIN, INT_MAX))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("value for \"%s\" in source string is out of range", + node->key->name), + errdetail("Value must be in the range %d to %d.", + INT_MIN, INT_MAX)))); if (dest != NULL) - from_char_set_int(dest, (int) result, node); + { + from_char_set_int(dest, (int) result, node, have_error); + CHECK_ERROR; + } + return *src - init; + +on_error: + return -1; } /* @@ -2349,9 +2418,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node) * required length explicitly. */ static int -from_char_parse_int(int *dest, char **src, FormatNode *node) +from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error) { - return from_char_parse_int_len(dest, src, node->key->len, node); + return from_char_parse_int_len(dest, src, node->key->len, node, have_error); } /* ---------- @@ -2434,11 +2503,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len) * pointed to by 'dest', advance 'src' to the end of the part of the string * which matched, and return the number of characters consumed. * - * If the string doesn't match, throw an error. + * If the string doesn't match, throw an error if 'have_error' is NULL, + * otherwise set '*have_error' and return -1. */ static int -from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, - FormatNode *node) +from_char_seq_search(int *dest, char **src, const char *const *array, int type, + int max, FormatNode *node, bool *have_error) { int len; @@ -2450,15 +2520,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type, Assert(max <= DCH_MAX_ITEM_SIZ); strlcpy(copy, *src, max + 1); - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid value \"%s\" for \"%s\"", - copy, node->key->name), - errdetail("The given value did not match any of the allowed " - "values for this field."))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("The given value did not match any of " + "the allowed values for this field.")))); } *src += len; return len; + +on_error: + return -1; } /* ---------- @@ -3060,10 +3133,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col * * Note: we currently don't have any to_interval() function, so there * is no need here for INVALID_FOR_INTERVAL checks. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set. * ---------- */ static void -DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) +DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict, + bool *have_error) { FormatNode *n; char *s; @@ -3146,7 +3222,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) continue; } - from_char_set_mode(out, n->key->date_mode); + from_char_set_mode(out, n->key->date_mode, have_error); + CHECK_ERROR; switch (n->key->id) { @@ -3158,8 +3235,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) case DCH_a_m: case DCH_p_m: from_char_seq_search(&value, &s, ampm_strings_long, - ALL_UPPER, n->key->len, n); - from_char_set_int(&out->pm, value % 2, n); + ALL_UPPER, n->key->len, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->pm, value % 2, n, have_error); + CHECK_ERROR; out->clock = CLOCK_12_HOUR; break; case DCH_AM: @@ -3167,30 +3246,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) case DCH_am: case DCH_pm: from_char_seq_search(&value, &s, ampm_strings, - ALL_UPPER, n->key->len, n); - from_char_set_int(&out->pm, value % 2, n); + ALL_UPPER, n->key->len, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->pm, value % 2, n, have_error); + CHECK_ERROR; out->clock = CLOCK_12_HOUR; break; case DCH_HH: case DCH_HH12: - from_char_parse_int_len(&out->hh, &s, 2, n); + from_char_parse_int_len(&out->hh, &s, 2, n, have_error); + CHECK_ERROR; out->clock = CLOCK_12_HOUR; SKIP_THth(s, n->suffix); break; case DCH_HH24: - from_char_parse_int_len(&out->hh, &s, 2, n); + from_char_parse_int_len(&out->hh, &s, 2, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_MI: - from_char_parse_int(&out->mi, &s, n); + from_char_parse_int(&out->mi, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_SS: - from_char_parse_int(&out->ss, &s, n); + from_char_parse_int(&out->ss, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_MS: /* millisecond */ - len = from_char_parse_int_len(&out->ms, &s, 3, n); + len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error); + CHECK_ERROR; /* * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25 @@ -3211,7 +3297,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) case DCH_US: /* microsecond */ len = from_char_parse_int_len(&out->us, &s, n->key->id == DCH_US ? 6 : - out->ff, n); + out->ff, n, have_error); + CHECK_ERROR; out->us *= len == 1 ? 100000 : len == 2 ? 10000 : @@ -3222,16 +3309,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) SKIP_THth(s, n->suffix); break; case DCH_SSSS: - from_char_parse_int(&out->ssss, &s, n); + from_char_parse_int(&out->ssss, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_tz: case DCH_TZ: case DCH_OF: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("formatting field \"%s\" is only supported in to_char", - n->key->name))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("formatting field \"%s\" is only supported in to_char", + n->key->name)))); + CHECK_ERROR; break; case DCH_TZH: @@ -3255,82 +3344,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) out->tzsign = +1; } - from_char_parse_int_len(&out->tzh, &s, 2, n); + from_char_parse_int_len(&out->tzh, &s, 2, n, have_error); + CHECK_ERROR; break; case DCH_TZM: /* assign positive timezone sign if TZH was not seen before */ if (!out->tzsign) out->tzsign = +1; - from_char_parse_int_len(&out->tzm, &s, 2, n); + from_char_parse_int_len(&out->tzm, &s, 2, n, have_error); + CHECK_ERROR; break; case DCH_A_D: case DCH_B_C: case DCH_a_d: case DCH_b_c: from_char_seq_search(&value, &s, adbc_strings_long, - ALL_UPPER, n->key->len, n); - from_char_set_int(&out->bc, value % 2, n); + ALL_UPPER, n->key->len, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->bc, value % 2, n, have_error); + CHECK_ERROR; break; case DCH_AD: case DCH_BC: case DCH_ad: case DCH_bc: from_char_seq_search(&value, &s, adbc_strings, - ALL_UPPER, n->key->len, n); - from_char_set_int(&out->bc, value % 2, n); + ALL_UPPER, n->key->len, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->bc, value % 2, n, have_error); + CHECK_ERROR; break; case DCH_MONTH: case DCH_Month: case DCH_month: from_char_seq_search(&value, &s, months_full, ONE_UPPER, - MAX_MONTH_LEN, n); - from_char_set_int(&out->mm, value + 1, n); + MAX_MONTH_LEN, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->mm, value + 1, n, have_error); + CHECK_ERROR; break; case DCH_MON: case DCH_Mon: case DCH_mon: from_char_seq_search(&value, &s, months, ONE_UPPER, - MAX_MON_LEN, n); - from_char_set_int(&out->mm, value + 1, n); + MAX_MON_LEN, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->mm, value + 1, n, have_error); + CHECK_ERROR; break; case DCH_MM: - from_char_parse_int(&out->mm, &s, n); + from_char_parse_int(&out->mm, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_DAY: case DCH_Day: case DCH_day: from_char_seq_search(&value, &s, days, ONE_UPPER, - MAX_DAY_LEN, n); - from_char_set_int(&out->d, value, n); + MAX_DAY_LEN, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->d, value, n, have_error); + CHECK_ERROR; out->d++; break; case DCH_DY: case DCH_Dy: case DCH_dy: from_char_seq_search(&value, &s, days, ONE_UPPER, - MAX_DY_LEN, n); - from_char_set_int(&out->d, value, n); + MAX_DY_LEN, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->d, value, n, have_error); + CHECK_ERROR; out->d++; break; case DCH_DDD: - from_char_parse_int(&out->ddd, &s, n); + from_char_parse_int(&out->ddd, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_IDDD: - from_char_parse_int_len(&out->ddd, &s, 3, n); + from_char_parse_int_len(&out->ddd, &s, 3, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_DD: - from_char_parse_int(&out->dd, &s, n); + from_char_parse_int(&out->dd, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_D: - from_char_parse_int(&out->d, &s, n); + from_char_parse_int(&out->d, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_ID: - from_char_parse_int_len(&out->d, &s, 1, n); + from_char_parse_int_len(&out->d, &s, 1, n, have_error); + CHECK_ERROR; /* Shift numbering to match Gregorian where Sunday = 1 */ if (++out->d > 7) out->d = 1; @@ -3338,7 +3447,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) break; case DCH_WW: case DCH_IW: - from_char_parse_int(&out->ww, &s, n); + from_char_parse_int(&out->ww, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_Q: @@ -3353,11 +3463,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) * We still parse the source string for an integer, but it * isn't stored anywhere in 'out'. */ - from_char_parse_int((int *) NULL, &s, n); + from_char_parse_int((int *) NULL, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_CC: - from_char_parse_int(&out->cc, &s, n); + from_char_parse_int(&out->cc, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_Y_YYY: @@ -3369,11 +3481,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch); if (matched < 2) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid input string for \"Y,YYY\""))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid input string for \"Y,YYY\"")))); years += (millennia * 1000); - from_char_set_int(&out->year, years, n); + from_char_set_int(&out->year, years, n, have_error); + CHECK_ERROR; out->yysz = 4; s += nch; SKIP_THth(s, n->suffix); @@ -3381,47 +3494,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) break; case DCH_YYYY: case DCH_IYYY: - from_char_parse_int(&out->year, &s, n); + from_char_parse_int(&out->year, &s, n, have_error); + CHECK_ERROR; out->yysz = 4; SKIP_THth(s, n->suffix); break; case DCH_YYY: case DCH_IYY: - if (from_char_parse_int(&out->year, &s, n) < 4) + len = from_char_parse_int(&out->year, &s, n, have_error); + CHECK_ERROR; + if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 3; SKIP_THth(s, n->suffix); break; case DCH_YY: case DCH_IY: - if (from_char_parse_int(&out->year, &s, n) < 4) + len = from_char_parse_int(&out->year, &s, n, have_error); + CHECK_ERROR; + if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 2; SKIP_THth(s, n->suffix); break; case DCH_Y: case DCH_I: - if (from_char_parse_int(&out->year, &s, n) < 4) + len = from_char_parse_int(&out->year, &s, n, have_error); + CHECK_ERROR; + if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 1; SKIP_THth(s, n->suffix); break; case DCH_RM: from_char_seq_search(&value, &s, rm_months_upper, - ALL_UPPER, MAX_RM_LEN, n); - from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n); + ALL_UPPER, MAX_RM_LEN, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, + n, have_error); + CHECK_ERROR; break; case DCH_rm: from_char_seq_search(&value, &s, rm_months_lower, - ALL_LOWER, MAX_RM_LEN, n); - from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n); + ALL_LOWER, MAX_RM_LEN, n, have_error); + CHECK_ERROR; + from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, + n, have_error); + CHECK_ERROR; break; case DCH_W: - from_char_parse_int(&out->w, &s, n); + from_char_parse_int(&out->w, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; case DCH_J: - from_char_parse_int(&out->j, &s, n); + from_char_parse_int(&out->j, &s, n, have_error); + CHECK_ERROR; SKIP_THth(s, n->suffix); break; } @@ -3441,19 +3569,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) if (strict) { if (n->type != NODE_TYPE_END) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("input string is too short for datetime format"))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("input string is too short for datetime format")))); while (*s != '\0' && isspace((unsigned char) *s)) s++; if (*s != '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("trailing characters remain in input string after " - "datetime format"))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("trailing characters remain in input string " + "after datetime format")))); } + +on_error: + return; } /* @@ -3474,9 +3605,13 @@ DCH_prevent_counter_overflow(void) } } -/* Get mask of date/time/zone components present in format nodes. */ +/* + * Get mask of date/time/zone components present in format nodes. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set. + */ static int -DCH_datetime_type(FormatNode *node) +DCH_datetime_type(FormatNode *node, bool *have_error) { FormatNode *n; int flags = 0; @@ -3517,10 +3652,10 @@ DCH_datetime_type(FormatNode *node) case DCH_tz: case DCH_TZ: case DCH_OF: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("formatting field \"%s\" is only supported in to_char", - n->key->name))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("formatting field \"%s\" is only supported in to_char", + n->key->name)))); flags |= DCH_ZONED; break; case DCH_TZH: @@ -3574,6 +3709,7 @@ DCH_datetime_type(FormatNode *node) } } +on_error: return flags; } @@ -3867,7 +4003,7 @@ to_timestamp(PG_FUNCTION_ARGS) fsec_t fsec; int fprec; - do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL); + do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL); /* Use the specified time zone, if any. */ if (tm.tm_zone) @@ -3906,7 +4042,7 @@ to_date(PG_FUNCTION_ARGS) struct pg_tm tm; fsec_t fsec; - do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL); + do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL); /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) @@ -3933,17 +4069,21 @@ to_date(PG_FUNCTION_ARGS) * the presence of date/time/zone components in the format string. * * When timezone component is present, the corresponding offset is set to '*tz'. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set + * and zero value is returned. */ Datum parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, - int32 *typmod, int *tz) + int32 *typmod, int *tz, bool *have_error) { struct pg_tm tm; fsec_t fsec; int fprec = 0; int flags; - do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags); + do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error); + CHECK_ERROR; *typmod = fprec ? fprec : -1; /* fractional part precision */ @@ -3964,16 +4104,22 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, } else { - if (*tz == PG_INT32_MIN) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("missing time zone in input string for type timestamptz"))); + /* + * Time zone is present in format string, but not in input + * string. Assuming do_to_timestamp() triggers no error + * this should be possible only in non-strict case. + */ + Assert(!strict); + + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time zone in input string for type timestamptz")))); } if (tm2timestamp(&tm, fsec, tz, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamptz out of range"))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamptz out of range")))); AdjustTimestampForTypmod(&result, *typmod); @@ -3985,9 +4131,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, Timestamp result; if (tm2timestamp(&tm, fsec, NULL, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range")))); AdjustTimestampForTypmod(&result, *typmod); @@ -3999,9 +4145,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, { if (flags & DCH_ZONED) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("datetime format is zoned but not timed"))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is zoned but not timed")))); } else { @@ -4009,20 +4155,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range: \"%s\"", - text_to_cstring(date_txt)))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt))))); result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; /* Now check for just-out-of-range dates */ if (!IS_VALID_DATE(result)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range: \"%s\"", - text_to_cstring(date_txt)))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt))))); *typid = DATEOID; return DateADTGetDatum(result); @@ -4040,20 +4186,26 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz); if (dterr) - DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"); + RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz")); } else { - if (*tz == PG_INT32_MIN) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("missing time zone in input string for type timetz"))); + /* + * Time zone is present in format string, but not in input + * string. Assuming do_to_timestamp() triggers no error this + * should be possible only in non-strict case. + */ + Assert(!strict); + + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time zone in input string for type timetz")))); } if (tm2timetz(&tm, fsec, *tz, result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timetz out of range"))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timetz out of range")))); AdjustTimeForTypmod(&result->time, *typmod); @@ -4065,9 +4217,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, TimeADT result; if (tm2time(&tm, fsec, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("time out of range"))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("time out of range")))); AdjustTimeForTypmod(&result, *typmod); @@ -4077,11 +4229,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, } else { - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("datetime format is not dated and not timed"))); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is not dated and not timed")))); } +on_error: return (Datum) 0; } @@ -4102,16 +4255,19 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, * * 'strict' enables error reporting on unmatched trailing characters in input * or format string patterns. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set. */ static void -do_to_timestamp(text *date_txt, text *fmt, bool strict, - struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags) +do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm, + fsec_t *fsec, int *fprec, int *flags, bool *have_error) { - FormatNode *format; + FormatNode *format = NULL; TmFromChar tmfc; int fmt_len; char *date_str; int fmask; + bool incache = false; date_str = text_to_cstring(date_txt); @@ -4125,7 +4281,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, if (fmt_len) { char *fmt_str; - bool incache; fmt_str = text_to_cstring(fmt); @@ -4135,8 +4290,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, * Allocate new memory if format picture is bigger than static * cache and do not use cache (call parser always) */ - incache = false; - format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); parse_format(format, fmt_str, DCH_keywords, @@ -4158,15 +4311,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, /* dump_index(DCH_keywords, DCH_index); */ #endif - DCH_from_char(format, date_str, &tmfc, strict); + DCH_from_char(format, date_str, &tmfc, strict, have_error); + CHECK_ERROR; pfree(fmt_str); if (flags) - *flags = DCH_datetime_type(format); + *flags = DCH_datetime_type(format, have_error); if (!incache) + { pfree(format); + format = NULL; + } + + CHECK_ERROR; } DEBUG_TMFC(&tmfc); @@ -4195,11 +4354,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, if (tmfc.clock == CLOCK_12_HOUR) { if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("hour \"%d\" is invalid for the 12-hour clock", - tm->tm_hour), - errhint("Use the 24-hour clock, or give an hour between 1 and 12."))); + { + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("hour \"%d\" is invalid for the 12-hour clock", + tm->tm_hour), + errhint("Use the 24-hour clock, or give an hour between 1 and 12.")))); + } if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2) tm->tm_hour += HOURS_PER_DAY / 2; @@ -4303,9 +4464,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, */ if (!tm->tm_year && !tmfc.bc) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("cannot calculate day of year without year information"))); + { + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("cannot calculate day of year without year information")))); + } if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK) { @@ -4362,7 +4525,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an * irrelevant hint about datestyle. */ - DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"); + RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp")); } } @@ -4371,7 +4534,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR || tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE || *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC) - DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"); + { + RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp")); + } /* Save parsed time-zone into tm->tm_zone if it was specified */ if (tmfc.tzsign) @@ -4380,7 +4545,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR || tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR) - DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"); + { + RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp")); + } tz = psprintf("%c%02d:%02d", tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm); @@ -4390,6 +4557,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, DEBUG_TM(tm); +on_error: + + if (format && !incache) + pfree(format); + pfree(date_str); } diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 2931bd5..84bc97d 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS) } /* - * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod + * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod * Works for either timestamp or timestamptz. */ -void -AdjustTimestampForTypmod(Timestamp *time, int32 typmod) +bool +AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error) { static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = { INT64CONST(1000000), @@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod) && (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION)) { if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION) + { + if (error) + { + *error = true; + return false; + } + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("timestamp(%d) precision must be between %d and %d", typmod, 0, MAX_TIMESTAMP_PRECISION))); + } if (*time >= INT64CONST(0)) { @@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod) * TimestampScales[typmod]); } } + + return true; } +void +AdjustTimestampForTypmod(Timestamp *time, int32 typmod) +{ + (void) AdjustTimestampForTypmodError(time, typmod, NULL); +} /* timestamptz_in() * Convert a string to internal form. @@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp)); } -static TimestampTz -timestamp2timestamptz(Timestamp timestamp) +/* + * Convert timestamp to timestamp with time zone. + * + * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set + * and zero is returned. + */ + +TimestampTz +timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error) { TimestampTz result; struct pg_tm tt, @@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp) int tz; if (TIMESTAMP_NOT_FINITE(timestamp)) - result = timestamp; - else - { - if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + return timestamp; + if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL)) + { tz = DetermineTimeZoneOffset(tm, session_timezone); - if (tm2timestamp(tm, fsec, &tz, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + if (!tm2timestamp(tm, fsec, &tz, &result)) + return result; } - return result; + if (have_error) + *have_error = true; + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + return 0; +} + +/* + * Single-argument version of timestamp2timestamptz_opt_error(). + */ +static TimestampTz +timestamp2timestamptz(Timestamp timestamp) +{ + return timestamp2timestamptz_opt_error(timestamp, NULL); } /* timestamptz_timestamp() diff --git a/src/include/utils/date.h b/src/include/utils/date.h index bd15bfa..c29f13a 100644 --- a/src/include/utils/date.h +++ b/src/include/utils/date.h @@ -70,6 +70,8 @@ typedef struct /* date.c */ extern int32 anytime_typmod_check(bool istz, int32 typmod); extern double date2timestamp_no_overflow(DateADT dateVal); +extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error); +extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error); extern void EncodeSpecialDate(DateADT dt, char *str); extern DateADT GetSQLCurrentDate(void); extern TimeTzADT *GetSQLCurrentTime(int32 typmod); diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 0cafdd2..5ebf336 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); +extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, + bool *error); #endif /* DATETIME_H */ diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h index a390302..1a2e2a9 100644 --- a/src/include/utils/formatting.h +++ b/src/include/utils/formatting.h @@ -29,6 +29,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes); extern char *asc_initcap(const char *buff, size_t nbytes); extern Datum parse_datetime(text *date_txt, text *fmt, bool strict, - Oid *typid, int32 *typmod, int *tz); + Oid *typid, int32 *typmod, int *tz, + bool *have_error); #endif diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index ea16190..e884d44 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -97,6 +97,9 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2); /* timestamp comparison works for timestamptz also */ #define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2) +extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp, + bool *have_error); + extern int isoweek2j(int year, int week); extern void isoweek2date(int woy, int *year, int *mon, int *mday); extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);