*** a/doc/src/sgml/datatype.sgml --- b/doc/src/sgml/datatype.sgml *************** *** 1977,1996 **** January 8 04:05:06 1999 PST Interval values can also be written as ISO 8601 time intervals, using the "format with time-unit designators", ! from section 5.5.4.2.1 (PnYnMnDTnHnMnS) or the "alternative format" ! from section 5.5.4.2.2 (PYYYY-MM-DDThh:mm:ss). In both cases, the ! string must start with a 'P', and a 'T' ! separates the date and time parts of the interval. ! In the former format, each 'n' gets replaced by a number, and ! Y represents years, ! M (in the date part) months, ! D months, ! H hours, ! M (in the time part) minutes, ! and S seconds. In the later format, the Y, M, D, h, m, s ! characters are replaced with digits. For example, ! P2Y10M15DT10H30M20S and P0002-10-15T10:30:20 both mean ! 2 years 10 months 15 days 10 hours 30 minutes and 20 seconds. --- 1977,2046 ---- Interval values can also be written as ISO 8601 time intervals, using the "format with time-unit designators", ! from section 4.4.4.2.1: ! ! ! P quantity unit quantity unit... T quantity unit... ! ! ! The string must start with a 'P', and may include a ! 'T' to separate the date units from the time units. The ! available unit abbreviations are given in . Units may be ! omitted, and may be specified in any order, but the meaning of ! 'M' depends on whether it is before or after ! 'T'. ! ! !
! ISO 8601 interval unit abbreviations ! ! ! ! Abbreviation ! Meaning ! ! ! ! ! Y ! Years ! ! ! M ! Months (in the date part) ! ! ! D ! Days ! ! ! H ! Hours ! ! ! M ! Minutes (in the time part) ! ! ! S ! Seconds ! ! ! !
! ! ! Postgres also supports the "alternative format" from ISO 8601 section ! 4.4.4.2.2, which has the following syntax: ! ! ! P years - months - days Thours : minutes : seconds ! ! ! Again, the string must begin with 'P', and a ! 'T' separates the date and time parts of the interval. In ! this format, the 'T' is mandatory. *************** *** 2017,2027 **** January 8 04:05:06 1999 PST 'P1Y2M3DT4H5M6S' ! An ISO 8601 "format with time-unit designators" interval meaning the same thing. 'P0001-02-03T04:05:06' ! An ISO 8601 "alternative format" interval meaning the same thing. --- 2067,2077 ---- 'P1Y2M3DT4H5M6S' ! An ISO 8601 "format with time-unit designators" interval with the same meaning as the above 'P0001-02-03T04:05:06' ! An ISO 8601 "alternative format" interval with the same meaning as the above *************** *** 2243,2260 **** January 8 04:05:06 1999 PST
- interval output looks like the input format, except - that units like century or - week are converted to years and days and - ago is converted to an appropriate sign. In - ISO mode the output looks like: - - - quantity unit ... days hours:minutes:seconds - - - - The date/time styles can be selected by the user using the SET datestyle command, the parameter in the --- 2293,2298 ---- *************** *** 2264,2381 **** January 8 04:05:06 1999 PST (see ) is also available as a more flexible way to format the date/time output. - - - - Interval Output - - - interval - output format - formatting - - - - The output format of the interval types can be set to one of the - four styles sql_standard, iso_8601, - postgres, or postgres_verbose. - The default is the postgres format. - shows examples of each - output style. - - - - The sql_standard style will output SQL standard - interval literal strings where the value of the interval - value consists of only a year-month component or a datetime - component (as required by the SQL standard). - For an interval containing both a year-month and a datetime - component, the output will be a SQL standard - unquoted year-month literal string concatenated with a - SQL standard unquoted datetime literal - string with a space in between; and '+' and '-' signs added - to disambiguate mixed-sign intervals. - - - - The iso_8601 style will output ISO 8601 - time intervals using the "format with time-unit designators" - This format always starts with the character - 'P', followed by a string of values followed by single - character time-unit designators. A 'T' separates the - date and time parts of the interval. - In this format, 'n' gets replaced by a number, and - Y represents years, - M (in the date part) months, - D months, - H hours, - M (in the time part) minutes, - and S seconds. - - - - The postgres style will output intervals - matching those output by PostgreSQL releases prior to 8.4 - when the - parameter was set to ISO. - - - - The postgres_verbose style will output intervals - matching those output by PostgreSQL releases prior to 8.4 - when the - parameter was set to SQL. - - - - Interval Style Example - - - - Style Specification - Year-Month Interval - DateTime Interval - Nonstandardrd Extended Interval - - - - - sql_standard - 1-2 - 3 4:05:06 - -1-2 +3 -4:05:06 - - - iso_8601 - P1Y2M - P3DT4H5M6 - P-1Y-2M3DT-4H-5M-6 - - - postgres - 1 year 2 mons - 3 days 04:05:06 - -1 year -2 mons +3 days -04:05:06 - - - postgres_verbose - @ 1 year 2 mons - @ 3 days 4 hours 5 mins 6 secs - @ 1 year 2 mons -3 days 4 hours 5 mins 6 secs ago - - - -
- - - Note that sql_standard style will only produce strictly - standards-conforming interval literals when given a strictly - SQL standard interval value - meaning that - it needs to be a pure year-month or datetime interval and not - mix positive and negative components. -
--- 2302,2416 ---- (see ) is also available as a more flexible way to format the date/time output. + + Interval Output + + + interval + output format + formatting + + + + The output format of the interval types can be set to one of the four + styles sql_standard, iso_8601, + postgres, or postgres_verbose using the + SET intervalstyle command. The default setting is the + postgres format. + shows examples of each output style. + + + + The sql_standard style will output SQL standard + interval literal strings where the value of the interval + value consists of only a year-month component or a datetime + component (as required by the SQL standard). + For an interval containing both a year-month and a datetime + component, the output will be a SQL standard + unquoted year-month literal string concatenated with a + SQL standard unquoted datetime literal + string with a space in between; and '+' and '-' signs added + to disambiguate mixed-sign intervals. + + + + The iso_8601 style will output ISO 8601 time intervals using + the "format with time-unit designators". This format always starts with + the character 'P', followed by a string of values followed by + single character time-unit designators. A 'T' separates the + date and time parts of the interval. + In this format + Y represents years, + M (in the date part) months, + D months, + H hours, + M (in the time part) minutes, + and S seconds. + + + + The postgres style will output intervals + matching those output by PostgreSQL releases prior to 8.4 + when the + parameter was set to ISO. + + + + The postgres_verbose style will output intervals + matching those output by PostgreSQL releases prior to 8.4 + when the + parameter was set to SQL. + + + + Interval Style Example + + + + Style Specification + Year-Month Interval + DateTime Interval + Nonstandard Extended Interval + + + + + sql_standard + 1-2 + 3 4:05:06 + -1-2 +3 -4:05:06 + + + iso_8601 + P1Y2M + P3DT4H5M6 + P-1Y-2M3DT-4H-5M-6 + + + postgres + 1 year 2 mons + 3 days 04:05:06 + -1 year -2 mons +3 days -04:05:06 + + + postgres_verbose + @ 1 year 2 mons + @ 3 days 4 hours 5 mins 6 secs + @ 1 year 2 mons -3 days 4 hours 5 mins 6 secs ago + + + +
+ + + Note that sql_standard style will only produce strictly + standards-conforming interval literals when given a strictly + SQL standard interval value - meaning that it needs to + be a pure year-month or datetime interval and not mix positive and + negative components. + +
*** a/src/backend/utils/adt/datetime.c --- b/src/backend/utils/adt/datetime.c *************** *** 2724,2736 **** DecodeSpecial(int field, char *lowtoken, int *val) /* ! * Small helper functions to avoid cut&paste code in DecodeIso8601Interval */ static void AdjustFractionalSeconds(double fval, struct pg_tm * tm, fsec_t *fsec, int scale) { int sec; ! if (fval == 0) return; fval *= scale; sec = fval; tm->tm_sec += sec; --- 2724,2738 ---- /* ! * Small helper functions to avoid cut&paste code in DecodeISO8601Interval */ static void AdjustFractionalSeconds(double fval, struct pg_tm * tm, fsec_t *fsec, int scale) { int sec; ! ! if (fval == 0) ! return; fval *= scale; sec = fval; tm->tm_sec += sec; *************** *** 2745,2751 **** static void AdjustFractionalDays(double fval, struct pg_tm * tm, fsec_t *fsec, int scale) { int extra_days; ! if (fval == 0) return; fval *= scale; extra_days = fval; tm->tm_mday += extra_days; --- 2747,2755 ---- AdjustFractionalDays(double fval, struct pg_tm * tm, fsec_t *fsec, int scale) { int extra_days; ! ! if (fval == 0) ! return; fval *= scale; extra_days = fval; tm->tm_mday += extra_days; *************** *** 2785,2796 **** DecodeISO8601Interval(char *str, struct pg_tm * tm, fsec_t *fsec) tm->tm_sec = 0; *fsec = 0; ! if (strlen(str)<3 || !(str[0] == 'P')) ! { return DTERR_BAD_FORMAT; ! } str++; - while (*str) { if (*str == 'T') /* T indicates the beginning of the time part */ --- 2789,2798 ---- tm->tm_sec = 0; *fsec = 0; ! if (strlen(str) < 3 || !(str[0] == 'P')) return DTERR_BAD_FORMAT; ! str++; while (*str) { if (*str == 'T') /* T indicates the beginning of the time part */ *************** *** 2799,2805 **** DecodeISO8601Interval(char *str, struct pg_tm * tm, fsec_t *fsec) str++; continue; } ! if (! (isdigit(*str) || *str == '-' || *str == '.') ) return DTERR_BAD_FORMAT; negative = (*str == '-'); --- 2801,2807 ---- str++; continue; } ! if (!(isdigit(*str) || *str == '-' || *str == '.') ) return DTERR_BAD_FORMAT; negative = (*str == '-'); *************** *** 2832,2838 **** DecodeISO8601Interval(char *str, struct pg_tm * tm, fsec_t *fsec) break; case 'T': /* ISO 8601 5.5.4.2.2 Alternative Format / Basic */ tm->tm_year += val / 10000; ! tm->tm_mon += (val/100) % 100; tm->tm_mday += val % 100; AdjustFractionalSeconds(fval, tm, fsec, SECS_PER_DAY); datepart = false; --- 2834,2840 ---- break; case 'T': /* ISO 8601 5.5.4.2.2 Alternative Format / Basic */ tm->tm_year += val / 10000; ! tm->tm_mon += (val / 100) % 100; tm->tm_mday += val % 100; AdjustFractionalSeconds(fval, tm, fsec, SECS_PER_DAY); datepart = false; *************** *** 2847,2853 **** DecodeISO8601Interval(char *str, struct pg_tm * tm, fsec_t *fsec) fval -= val; AdjustFractionalDays(fval, tm, fsec, DAYS_PER_MONTH); ! if (*str != '-') return DTERR_BAD_FORMAT; str++; fval = strtod(str, &str); --- 2849,2856 ---- fval -= val; AdjustFractionalDays(fval, tm, fsec, DAYS_PER_MONTH); ! if (*str != '-') ! return DTERR_BAD_FORMAT; str++; fval = strtod(str, &str); *************** *** 2856,2862 **** DecodeISO8601Interval(char *str, struct pg_tm * tm, fsec_t *fsec) fval -= val; AdjustFractionalSeconds(fval, tm, fsec, SECS_PER_DAY); ! if (*str != 'T') return DTERR_BAD_FORMAT; str++; datepart = false; --- 2859,2866 ---- fval -= val; AdjustFractionalSeconds(fval, tm, fsec, SECS_PER_DAY); ! if (*str != 'T') ! return DTERR_BAD_FORMAT; str++; datepart = false; *************** *** 2897,2903 **** DecodeISO8601Interval(char *str, struct pg_tm * tm, fsec_t *fsec) tm->tm_min += val; AdjustFractionalSeconds(fval, tm, fsec, SECS_PER_MINUTE); ! if (*str != ':') return DTERR_BAD_FORMAT; str++; fval = strtod(str, &str); --- 2901,2908 ---- tm->tm_min += val; AdjustFractionalSeconds(fval, tm, fsec, SECS_PER_MINUTE); ! if (*str != ':') ! return DTERR_BAD_FORMAT; str++; fval = strtod(str, &str); *************** *** 2906,2912 **** DecodeISO8601Interval(char *str, struct pg_tm * tm, fsec_t *fsec) tm->tm_sec += val; AdjustFractionalSeconds(fval, tm, fsec, 1); ! if (*str != 0) return DTERR_BAD_FORMAT; return 0; default: --- 2911,2918 ---- tm->tm_sec += val; AdjustFractionalSeconds(fval, tm, fsec, 1); ! if (*str != 0) ! return DTERR_BAD_FORMAT; return 0; default: *************** *** 3825,3831 **** EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, static char * AddISO8601IntervalPart(char * cp, int value, char units) { ! if (value==0) return cp; return cp + sprintf(cp, "%d%c", value, units); } --- 3831,3838 ---- static char * AddISO8601IntervalPart(char * cp, int value, char units) { ! if (value == 0) ! return cp; return cp + sprintf(cp, "%d%c", value, units); } *************** *** 3833,3841 **** static void AppendSeconds(char * cp, int sec, fsec_t fsec, bool fillzeros) { if (fsec == 0) - { sprintf(cp, fillzeros ? "%02d" : "%d" , abs(sec)); - } else { #ifdef HAVE_INT64_TIMESTAMP --- 3840,3846 ---- *************** *** 3848,3854 **** AppendSeconds(char * cp, int sec, fsec_t fsec, bool fillzeros) } - /* EncodeInterval() * Interpret time structure as a delta time and convert to string. * --- 3853,3858 ---- *************** *** 3972,3985 **** EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str) cp = AddISO8601IntervalPart(cp, mon , 'M'); cp = AddISO8601IntervalPart(cp, mday, 'D'); if ((hour != 0) || (min != 0) || (sec != 0) || (fsec != 0)) - { cp += sprintf(cp, "T"); ! } cp = AddISO8601IntervalPart(cp, hour, 'H'); cp = AddISO8601IntervalPart(cp, min , 'M'); if ((sec != 0) || (fsec != 0)) { ! cp += sprintf(cp, "%s", (sec<0 || fsec<0) ? "-" : ""); AppendSeconds(cp, sec, fsec, false); cp += strlen(cp); cp += sprintf(cp, "S"); --- 3976,3988 ---- cp = AddISO8601IntervalPart(cp, mon , 'M'); cp = AddISO8601IntervalPart(cp, mday, 'D'); if ((hour != 0) || (min != 0) || (sec != 0) || (fsec != 0)) cp += sprintf(cp, "T"); ! cp = AddISO8601IntervalPart(cp, hour, 'H'); cp = AddISO8601IntervalPart(cp, min , 'M'); if ((sec != 0) || (fsec != 0)) { ! cp += sprintf(cp, "%s", (sec < 0 || fsec < 0) ? "-" : ""); AppendSeconds(cp, sec, fsec, false); cp += strlen(cp); cp += sprintf(cp, "S");