diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index a90009e900b87d7481af2c884a28d955a5af0a24..715eb44e010143fe405814f3b0ebee8e7d589283 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.193 2008/11/04 22:40:40 momjian Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.194 2008/11/09 00:28:34 tgl Exp $ --> <chapter Id="runtime-config"> <title>Server Configuration</title> @@ -4014,6 +4014,33 @@ SET XML OPTION { DOCUMENT | CONTENT }; </listitem> </varlistentry> + <varlistentry id="guc-intervalstyle" xreflabel="IntervalStyle"> + <term><varname>IntervalStyle</varname> (<type>string</type>)</term> + <indexterm> + <primary><varname>IntervalStyle</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Sets the display format for interval values. + The value <literal>sql_standard</> will produce + output matching <acronym>SQL</acronym> standard interval literals. + The value <literal>postgres</> (which is the default) will produce + output matching <productname>PostgreSQL</> releases prior to 8.4 + when the <xref linkend="guc-datestyle"> + parameter was set to <literal>ISO</>. + The value <literal>postgres_verbose</> will produce output + matching <productname>PostgreSQL</> releases prior to 8.4 + when the <varname>DateStyle</> + parameter was set to non-<literal>ISO</> output. + </para> + <para> + The <varname>IntervalStyle</> parameter also affects the + interpretation of ambiguous interval input. See + <xref linkend="datatype-interval-input"> for more information. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-timezone" xreflabel="timezone"> <term><varname>timezone</varname> (<type>string</type>)</term> <indexterm> diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index d3d2bb5c07b31bafb6a68c57e887da801d2824a6..10da67ef5c61ea3dee95228b444abf6d87128517 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.231 2008/11/03 22:14:40 petere Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.232 2008/11/09 00:28:34 tgl Exp $ --> <chapter id="datatype"> <title id="datatype-title">Data Types</title> @@ -1419,14 +1419,6 @@ SELECT b, char_length(b) FROM test2; <entry>294276 AD</entry> <entry>1 microsecond / 14 digits</entry> </row> - <row> - <entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry> - <entry>12 bytes</entry> - <entry>time intervals</entry> - <entry>-178000000 years</entry> - <entry>178000000 years</entry> - <entry>1 microsecond / 14 digits</entry> - </row> <row> <entry><type>date</type></entry> <entry>4 bytes</entry> @@ -1451,6 +1443,14 @@ SELECT b, char_length(b) FROM test2; <entry>24:00:00-1459</entry> <entry>1 microsecond / 14 digits</entry> </row> + <row> + <entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry> + <entry>12 bytes</entry> + <entry>time intervals</entry> + <entry>-178000000 years</entry> + <entry>178000000 years</entry> + <entry>1 microsecond / 14 digits</entry> + </row> </tbody> </tgroup> </table> @@ -1928,65 +1928,6 @@ January 8 04:05:06 1999 PST </para> </sect3> - <sect3> - <title>Intervals</title> - - <indexterm> - <primary>interval</primary> - </indexterm> - - <para> - <type>interval</type> values can be written with the following syntax: - -<programlisting> -<optional>@</> <replaceable>quantity</> <replaceable>unit</> <optional><replaceable>quantity</> <replaceable>unit</>...</> <optional><replaceable>direction</></optional> -</programlisting> - - Where: <replaceable>quantity</> is a number (possibly signed); - <replaceable>unit</> is <literal>microsecond</literal>, - <literal>millisecond</literal>, <literal>second</literal>, - <literal>minute</literal>, <literal>hour</literal>, <literal>day</literal>, - <literal>week</literal>, <literal>month</literal>, <literal>year</literal>, - <literal>decade</literal>, <literal>century</literal>, <literal>millennium</literal>, - or abbreviations or plurals of these units; - <replaceable>direction</> can be <literal>ago</literal> or - empty. The at sign (<literal>@</>) is optional noise. The amounts - of different units are implicitly added up with appropriate - sign accounting. <literal>ago</literal> negates all the fields. - </para> - - <para> - Quantities of days, hours, minutes, and seconds can be specified without - explicit unit markings. For example, <literal>'1 12:59:10'</> is read - the same as <literal>'1 day 12 hours 59 min 10 sec'</>. Also, - a combination of years and months can be specified with a dash; - for example <literal>'200-10'</> is read the same as <literal>'200 years - 10 months'</>. (These shorter forms are in fact the only ones allowed - by the SQL standard.) - </para> - - <para> - When writing an interval constant with a <replaceable>fields</> - specification, or when assigning to an interval column that was defined - with a <replaceable>fields</> specification, the interpretation of - unmarked quantities depends on the <replaceable>fields</>. For - example <literal>INTERVAL '1' YEAR</> is read as 1 year, whereas - <literal>INTERVAL '1'</> means 1 second. - </para> - - <para> - Internally <type>interval</> values are stored as months, days, - and seconds. This is done because the number of days in a month - varies, and a day can have 23 or 25 hours if a daylight savings - time adjustment is involved. Because intervals are usually created - from constant strings or <type>timestamp</> subtraction, this - storage method works well in most cases. Functions - <function>justify_days</> and <function>justify_hours</> are - available for adjusting days and hours that overflow their normal - periods. - </para> - </sect3> - <sect3> <title>Special Values</title> @@ -2189,18 +2130,6 @@ January 8 04:05:06 1999 PST </tgroup> </table> - <para> - <type>interval</type> output looks like the input format, except - that units like <literal>century</literal> or - <literal>week</literal> are converted to years and days and - <literal>ago</literal> is converted to an appropriate sign. In - ISO mode the output looks like: - -<programlisting> -<optional> <replaceable>quantity</> <replaceable>unit</> <optional> ... </> </> <optional> <replaceable>days</> </> <optional> <replaceable>hours</>:<replaceable>minutes</>:<replaceable>seconds</> </optional> -</programlisting> - </para> - <para> The date/time styles can be selected by the user using the <command>SET datestyle</command> command, the <xref @@ -2209,7 +2138,7 @@ January 8 04:05:06 1999 PST <envar>PGDATESTYLE</envar> environment variable on the server or client. The formatting function <function>to_char</function> (see <xref linkend="functions-formatting">) is also available as - a more flexible way to format the date/time output. + a more flexible way to format date/time output. </para> </sect2> @@ -2413,6 +2342,163 @@ January 8 04:05:06 1999 PST </para> </sect2> + <sect2 id="datatype-interval-input"> + <title>Interval Input</title> + + <indexterm> + <primary>interval</primary> + </indexterm> + + <para> + <type>interval</type> values can be written with the following + verbose syntax: + +<programlisting> +<optional>@</> <replaceable>quantity</> <replaceable>unit</> <optional><replaceable>quantity</> <replaceable>unit</>...</> <optional><replaceable>direction</></optional> +</programlisting> + + where <replaceable>quantity</> is a number (possibly signed); + <replaceable>unit</> is <literal>microsecond</literal>, + <literal>millisecond</literal>, <literal>second</literal>, + <literal>minute</literal>, <literal>hour</literal>, <literal>day</literal>, + <literal>week</literal>, <literal>month</literal>, <literal>year</literal>, + <literal>decade</literal>, <literal>century</literal>, <literal>millennium</literal>, + or abbreviations or plurals of these units; + <replaceable>direction</> can be <literal>ago</literal> or + empty. The at sign (<literal>@</>) is optional noise. The amounts + of different units are implicitly added up with appropriate + sign accounting. <literal>ago</literal> negates all the fields. + This syntax is also used for interval output, if + <xref linkend="guc-intervalstyle"> is set to + <literal>postgres_verbose</>. + </para> + + <para> + Quantities of days, hours, minutes, and seconds can be specified without + explicit unit markings. For example, <literal>'1 12:59:10'</> is read + the same as <literal>'1 day 12 hours 59 min 10 sec'</>. Also, + a combination of years and months can be specified with a dash; + for example <literal>'200-10'</> is read the same as <literal>'200 years + 10 months'</>. (These shorter forms are in fact the only ones allowed + by the <acronym>SQL</acronym> standard, and are used for output when + <varname>IntervalStyle</> is set to <literal>sql_standard</literal>.) + </para> + + <para> + When writing an interval constant with a <replaceable>fields</> + specification, or when assigning to an interval column that was defined + with a <replaceable>fields</> specification, the interpretation of + unmarked quantities depends on the <replaceable>fields</>. For + example <literal>INTERVAL '1' YEAR</> is read as 1 year, whereas + <literal>INTERVAL '1'</> means 1 second. + </para> + + <para> + According to the <acronym>SQL</> standard all fields of an interval + value must have the same sign, so a leading negative sign applies to all + fields; for example the negative sign in the interval literal + <literal>'-1 2:03:04'</> applies to both the days and hour/minute/second + parts. <productname>PostgreSQL</> allows the fields to have different + signs, and traditionally treats each field in the textual representation + as independently signed, so that the hour/minute/second part is + considered positive in this example. If <varname>IntervalStyle</> is + set to <literal>sql_standard</literal> then a leading sign is considered + to apply to all fields (but only if no additional signs appear). + Otherwise the traditional <productname>PostgreSQL</> interpretation is + used. To avoid ambiguity, it's recommended to attach an explicit sign + to each field if any field is negative. + </para> + + <para> + Internally <type>interval</> values are stored as months, days, + and seconds. This is done because the number of days in a month + varies, and a day can have 23 or 25 hours if a daylight savings + time adjustment is involved. Because intervals are usually created + from constant strings or <type>timestamp</> subtraction, this + storage method works well in most cases. Functions + <function>justify_days</> and <function>justify_hours</> are + available for adjusting days and hours that overflow their normal + ranges. + </para> + </sect2> + + <sect2 id="datatype-interval-output"> + <title>Interval Output</title> + + <indexterm> + <primary>interval</primary> + <secondary>output format</secondary> + <seealso>formatting</seealso> + </indexterm> + + <para> + The output format of the interval type can be set to one of the + three styles <literal>sql_standard</>, + <literal>postgres</>, or <literal>postgres_verbose</>, + using the command <literal>SET intervalstyle</literal>. + The default is the <literal>postgres</> format. + <xref linkend="interval-style-output-table"> shows examples of each + output style. + </para> + + <para> + The <literal>sql_standard</> style produces output that conforms to + the SQL standard's specification for interval literal strings, if + the interval value meets the standard's restrictions (either year-month + only or day-time only, with no mixing of positive + and negative components). Otherwise the output looks like a standard + year-month literal string followed by a day-time literal string, + with explicit signs added to disambiguate mixed-sign intervals. + </para> + + <para> + The output of the <literal>postgres</> style matches the output of + <productname>PostgreSQL</> releases prior to 8.4 when the + <xref linkend="guc-datestyle"> parameter was set to <literal>ISO</>. + </para> + + <para> + The output of the <literal>postgres_verbose</> style matches the output of + <productname>PostgreSQL</> releases prior to 8.4 when the + <varname>DateStyle</> parameter was set to non-<literal>ISO</> output. + </para> + + <table id="interval-style-output-table"> + <title>Interval Output Style Examples</title> + <tgroup cols="4"> + <thead> + <row> + <entry>Style Specification</entry> + <entry>Year-Month Interval</entry> + <entry>Day-Time Interval</entry> + <entry>Mixed Interval</entry> + </row> + </thead> + <tbody> + <row> + <entry><literal>sql_standard</></entry> + <entry>1-2</entry> + <entry>3 4:05:06</entry> + <entry>-1-2 +3 -4:05:06</entry> + </row> + <row> + <entry><literal>postgres</></entry> + <entry>1 year 2 mons</entry> + <entry>3 days 04:05:06</entry> + <entry>-1 year -2 mons +3 days -04:05:06</entry> + </row> + <row> + <entry><literal>postgres_verbose</></entry> + <entry>@ 1 year 2 mons</entry> + <entry>@ 3 days 4 hours 5 mins 6 secs</entry> + <entry>@ 1 year 2 mons -3 days 4 hours 5 mins 6 secs ago</entry> + </row> + </tbody> + </tgroup> + </table> + + </sect2> + <sect2 id="datatype-datetime-internals"> <title>Internals</title> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index d0a0284e8778534e3f63864527d4df43d41de93c..6f977f9083eeb73fe571f86f165e47e1e5e108b7 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.267 2008/11/04 22:36:07 momjian Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.268 2008/11/09 00:28:34 tgl Exp $ --> <chapter id="libpq"> <title><application>libpq</application> - C Library</title> @@ -1019,13 +1019,15 @@ PQconninfoOption *PQconninfoParse(const char *conninfo, char **errmsg); <literal>is_superuser</>, <literal>session_authorization</>, <literal>DateStyle</>, + <literal>IntervalStyle</>, <literal>TimeZone</>, <literal>integer_datetimes</>, and <literal>standard_conforming_strings</>. (<literal>server_encoding</>, <literal>TimeZone</>, and <literal>integer_datetimes</> were not reported by releases before 8.0; <literal>standard_conforming_strings</> was not reported by releases - before 8.1.) + before 8.1; <literal>IntervalStyle</> was not reported by releases + before 8.4.) Note that <literal>server_version</>, <literal>server_encoding</> and @@ -5762,6 +5764,17 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) </para> </listitem> + <listitem> + <para> + <indexterm> + <primary><envar>PGINTERVALSTYLE</envar></primary> + </indexterm> + <envar>PGINTERVALSTYLE</envar> sets the default style of interval + representation. (Equivalent to <literal>SET intervalstyle TO + ...</literal>.) + </para> + </listitem> + <listitem> <para> <indexterm> diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index c9a0c7abde79dc0eff1a31a741fecf5dfd256305..3b115fec430d6375539679909495dc9b1c12d33f 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.74 2008/10/28 12:10:42 mha Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.75 2008/11/09 00:28:34 tgl Exp $ --> <chapter id="protocol"> <title>Frontend/Backend Protocol</title> @@ -1091,13 +1091,15 @@ <literal>is_superuser</>, <literal>session_authorization</>, <literal>DateStyle</>, + <literal>IntervalStyle</>, <literal>TimeZone</>, <literal>integer_datetimes</>, and <literal>standard_conforming_strings</>. (<literal>server_encoding</>, <literal>TimeZone</>, and <literal>integer_datetimes</> were not reported by releases before 8.0; <literal>standard_conforming_strings</> was not reported by releases - before 8.1.) + before 8.1; <literal>IntervalStyle</> was not reported by releases + before 8.4.) Note that <literal>server_version</>, <literal>server_encoding</> and diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml index 762446778ec29b1dd597145e6772304e60fc2f45..276e3d6d91409629cff5f0d4f4f7bdb56bf54311 100644 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/copy.sgml,v 1.82 2008/10/10 21:46:34 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/copy.sgml,v 1.83 2008/11/09 00:28:34 tgl Exp $ PostgreSQL documentation --> @@ -349,7 +349,11 @@ COPY <replaceable class="parameter">count</replaceable> <productname>PostgreSQL</productname> installations that might use non-default <varname>DateStyle</varname> settings, <varname>DateStyle</varname> should be set to <literal>ISO</> before - using <command>COPY TO</>. + using <command>COPY TO</>. It is also a good idea to avoid dumping + data with <varname>IntervalStyle</varname> set to + <literal>sql_standard</>, because negative interval values might be + misinterpreted by a server that has a different setting for + <varname>IntervalStyle</varname>. </para> <para> diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 8efe2b22f2c256ecf35570c95f374b6ddc8445a5..e91c470304fa7ca7f613de74cd7236a80dd8bfee 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.196 2008/11/08 20:51:49 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.197 2008/11/09 00:28:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3101,6 +3101,11 @@ DecodeInterval(char **field, int *ftype, int nf, int range, fmask |= tmask; } + /* ensure that at least one time field has been found */ + if (fmask == 0) + return DTERR_BAD_FORMAT; + + /* ensure fractional seconds are fractional */ if (*fsec != 0) { int sec; @@ -3114,6 +3119,60 @@ DecodeInterval(char **field, int *ftype, int nf, int range, tm->tm_sec += sec; } + /*---------- + * The SQL standard defines the interval literal + * '-1 1:00:00' + * to mean "negative 1 days and negative 1 hours", while Postgres + * traditionally treats this as meaning "negative 1 days and positive + * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign + * to all fields if there are no other explicit signs. + * + * We leave the signs alone if there are additional explicit signs. + * This protects us against misinterpreting postgres-style dump output, + * since the postgres-style output code has always put an explicit sign on + * all fields following a negative field. But note that SQL-spec output + * is ambiguous and can be misinterpreted on load! (So it's best practice + * to dump in postgres style, not SQL style.) + *---------- + */ + if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-') + { + /* Check for additional explicit signs */ + bool more_signs = false; + + for (i = 1; i < nf; i++) + { + if (*field[i] == '-' || *field[i] == '+') + { + more_signs = true; + break; + } + } + + if (!more_signs) + { + /* + * Rather than re-determining which field was field[0], just + * force 'em all negative. + */ + if (*fsec > 0) + *fsec = -(*fsec); + if (tm->tm_sec > 0) + tm->tm_sec = -tm->tm_sec; + if (tm->tm_min > 0) + tm->tm_min = -tm->tm_min; + if (tm->tm_hour > 0) + tm->tm_hour = -tm->tm_hour; + if (tm->tm_mday > 0) + tm->tm_mday = -tm->tm_mday; + if (tm->tm_mon > 0) + tm->tm_mon = -tm->tm_mon; + if (tm->tm_year > 0) + tm->tm_year = -tm->tm_year; + } + } + + /* finally, AGO negates everything */ if (is_before) { *fsec = -(*fsec); @@ -3125,10 +3184,6 @@ DecodeInterval(char **field, int *ftype, int nf, int range, tm->tm_year = -tm->tm_year; } - /* ensure that at least one time field has been found */ - if (fmask == 0) - return DTERR_BAD_FORMAT; - return 0; } @@ -3606,6 +3661,29 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, } +/* + * Helper function to avoid duplicated code in EncodeInterval below. + * Note that any sign is stripped from the input seconds values. + */ +static void +AppendSeconds(char *cp, int sec, fsec_t fsec) +{ + if (fsec == 0) + { + sprintf(cp, ":%02d", abs(sec)); + } + else + { +#ifdef HAVE_INT64_TIMESTAMP + sprintf(cp, ":%02d.%06d", abs(sec), Abs(fsec)); +#else + sprintf(cp, ":%012.9f", fabs(sec + fsec)); +#endif + TrimTrailingZeros(cp); + } +} + + /* EncodeInterval() * Interpret time structure as a delta time and convert to string. * @@ -3613,23 +3691,115 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, * Actually, afaik ISO does not address time interval formatting, * but this looks similar to the spec for absolute date/time. * - thomas 1998-04-30 + * + * Actually, afaik, ISO 8601 does specify formats for "time + * intervals...[of the]...format with time-unit designators", which + * are pretty ugly. The format looks something like + * P1Y1M1DT1H1M1.12345S + * but useful for exchanging data with computers instead of humans. + * - ron 2003-07-14 + * + * And ISO's SQL 2008 standard specifies standards for + * "year-month literal"s (that look like '2-3') and + * "day-time literal"s (that look like ('4 5:6:7') */ int EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str) { + char *cp = str; + int year = tm->tm_year; + int mon = tm->tm_mon; + int mday = tm->tm_mday; + int hour = tm->tm_hour; + int min = tm->tm_min; + int sec = tm->tm_sec; bool is_before = FALSE; bool is_nonzero = FALSE; - char *cp = str; /* * The sign of year and month are guaranteed to match, since they are * stored internally as "month". But we'll need to check for is_before and - * is_nonzero when determining the signs of hour/minute/seconds fields. + * is_nonzero when determining the signs of day and hour/minute/seconds + * fields. */ switch (style) { - /* compatible with ISO date formats */ - case USE_ISO_DATES: + /* SQL Standard interval format */ + case INTSTYLE_SQL_STANDARD: + { + bool has_negative = year < 0 || mon < 0 || + mday < 0 || hour < 0 || + min < 0 || sec < 0 || fsec < 0; + bool has_positive = year > 0 || mon > 0 || + mday > 0 || hour > 0 || + min > 0 || sec > 0 || fsec > 0; + bool has_year_month = year != 0 || mon != 0; + bool has_day_time = mday != 0 || hour != 0 || + min != 0 || sec != 0 || fsec != 0; + bool has_day = mday != 0; + bool sql_standard_value = !(has_negative && has_positive) && + !(has_year_month && has_day_time); + + /* + * SQL Standard wants only 1 "<sign>" preceding the whole + * interval ... but can't do that if mixed signs. + */ + if (has_negative && sql_standard_value) + { + *cp++ = '-'; + year = -year; + mon = -mon; + mday = -mday; + hour = -hour; + min = -min; + sec = -sec; + fsec = -fsec; + } + + if (!has_negative && !has_positive) + { + sprintf(cp, "0"); + } + else if (!sql_standard_value) + { + /* + * For non sql-standard interval values, + * force outputting the signs to avoid + * ambiguities with intervals with mixed + * sign components. + */ + char year_sign = (year < 0 || mon < 0) ? '-' : '+'; + char day_sign = (mday < 0) ? '-' : '+'; + char sec_sign = (hour < 0 || min < 0 || sec < 0 || fsec < 0) ? '-' : '+'; + + sprintf(cp, "%c%d-%d %c%d %c%d:%02d", + year_sign, abs(year), abs(mon), + day_sign, abs(mday), + sec_sign, abs(hour), abs(min)); + cp += strlen(cp); + AppendSeconds(cp, sec, fsec); + } + else if (has_year_month) + { + sprintf(cp, "%d-%d", year, mon); + } + else if (has_day) + { + sprintf(cp, "%d %d:%02d", mday, hour, min); + cp += strlen(cp); + AppendSeconds(cp, sec, fsec); + } + else + { + sprintf(cp, "%d:%02d", hour, min); + cp += strlen(cp); + AppendSeconds(cp, sec, fsec); + } + } + break; + + /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */ + case INTSTYLE_POSTGRES: if (tm->tm_year != 0) { sprintf(cp, "%d year%s", @@ -3669,32 +3839,20 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str) (minus ? "-" : (is_before ? "+" : "")), abs(tm->tm_hour), abs(tm->tm_min)); cp += strlen(cp); - /* Mark as "non-zero" since the fields are now filled in */ + AppendSeconds(cp, tm->tm_sec, fsec); + cp += strlen(cp); is_nonzero = TRUE; - - /* need fractional seconds? */ - if (fsec != 0) - { -#ifdef HAVE_INT64_TIMESTAMP - sprintf(cp, ":%02d", abs(tm->tm_sec)); - cp += strlen(cp); - sprintf(cp, ".%06d", Abs(fsec)); -#else - fsec += tm->tm_sec; - sprintf(cp, ":%012.9f", fabs(fsec)); -#endif - TrimTrailingZeros(cp); - cp += strlen(cp); - } - else - { - sprintf(cp, ":%02d", abs(tm->tm_sec)); - cp += strlen(cp); - } + } + /* identically zero? then put in a unitless zero... */ + if (!is_nonzero) + { + strcat(cp, "0"); + cp += strlen(cp); } break; - case USE_POSTGRES_DATES: + /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */ + case INTSTYLE_POSTGRES_VERBOSE: default: strcpy(cp, "@ "); cp += strlen(cp); @@ -3821,22 +3979,20 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str) is_before = (tm->tm_sec < 0); is_nonzero = TRUE; } + /* identically zero? then put in a unitless zero... */ + if (!is_nonzero) + { + strcat(cp, "0"); + cp += strlen(cp); + } + if (is_before) + { + strcat(cp, " ago"); + cp += strlen(cp); + } break; } - /* identically zero? then put in a unitless zero... */ - if (!is_nonzero) - { - strcat(cp, "0"); - cp += strlen(cp); - } - - if (is_before && (style != USE_ISO_DATES)) - { - strcat(cp, " ago"); - cp += strlen(cp); - } - return 0; } /* EncodeInterval() */ diff --git a/src/backend/utils/adt/nabstime.c b/src/backend/utils/adt/nabstime.c index 4a505c341e9f8f17898300ffd399e729a9b5c6d9..6744818e412d5425bf95cb77c37209924221031d 100644 --- a/src/backend/utils/adt/nabstime.c +++ b/src/backend/utils/adt/nabstime.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.156 2008/09/10 18:29:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.157 2008/11/09 00:28:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -671,7 +671,7 @@ reltimeout(PG_FUNCTION_ARGS) char buf[MAXDATELEN + 1]; reltime2tm(time, tm); - EncodeInterval(tm, 0, DateStyle, buf); + EncodeInterval(tm, 0, IntervalStyle, buf); result = pstrdup(buf); PG_RETURN_CSTRING(result); diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 7f80fc94211a5915d3858f7cf4ef8524b239b466..ce633c7a4fdde758344431005c5b54211055361a 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.193 2008/10/14 15:44:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.194 2008/11/09 00:28:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -677,7 +677,7 @@ interval_out(PG_FUNCTION_ARGS) if (interval2tm(*span, tm, &fsec) != 0) elog(ERROR, "could not convert interval to tm"); - if (EncodeInterval(tm, fsec, DateStyle, buf) != 0) + if (EncodeInterval(tm, fsec, IntervalStyle, buf) != 0) elog(ERROR, "could not format interval"); result = pstrdup(buf); diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index d0ce929d7d086e40f996d8acdd145a3c740e6d94..d187ce4d73e17d71c057a634cf498398213124ad 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/globals.c,v 1.105 2008/02/17 02:09:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/globals.c,v 1.106 2008/11/09 00:28:35 tgl Exp $ * * NOTES * Globals used all over the place should be declared here and not @@ -88,6 +88,7 @@ bool ExitOnAnyError = false; int DateStyle = USE_ISO_DATES; int DateOrder = DATEORDER_MDY; +int IntervalStyle = INTSTYLE_POSTGRES; bool HasCTZSet = false; int CTimeZone = 0; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 9fec5753a8196cdc5f95e12486749ba02f34f8af..6a5faa725da51efaef2664d77197d22e8f718f7b 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10,7 +10,7 @@ * Written by Peter Eisentraut <peter_e@gmx.net>. * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.475 2008/10/06 13:05:36 mha Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.476 2008/11/09 00:28:35 tgl Exp $ * *-------------------------------------------------------------------- */ @@ -213,6 +213,13 @@ static const struct config_enum_entry server_message_level_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry intervalstyle_options[] = { + {"postgres", INTSTYLE_POSTGRES, false}, + {"postgres_verbose", INTSTYLE_POSTGRES_VERBOSE, false}, + {"sql_standard", INTSTYLE_SQL_STANDARD, false}, + {NULL, 0, false} +}; + static const struct config_enum_entry log_error_verbosity_options[] = { {"terse", PGERROR_TERSE, false}, {"default", PGERROR_DEFAULT, false}, @@ -2519,6 +2526,16 @@ static struct config_enum ConfigureNamesEnum[] = XACT_READ_COMMITTED, isolation_level_options, NULL, NULL }, + { + {"IntervalStyle", PGC_USERSET, CLIENT_CONN_LOCALE, + gettext_noop("Sets the display format for interval values."), + NULL, + GUC_REPORT + }, + &IntervalStyle, + INTSTYLE_POSTGRES, intervalstyle_options, NULL, NULL + }, + { {"log_error_verbosity", PGC_SUSET, LOGGING_WHEN, gettext_noop("Sets the verbosity of logged messages."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 56afb2e48856de73e68ade729e749600ae823c9b..f886ef74b218cce575364de8e51dfe552866500b 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -419,6 +419,7 @@ # - Locale and Formatting - #datestyle = 'iso, mdy' +#intervalstyle = 'postgres' #timezone = unknown # actually, defaults to TZ environment # setting #timezone_abbreviations = 'Default' # Select the set of available time zone diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ac05a4a5b70f2ec9ed7bd1aceb02abe7ffe1f3ff..936ff42bb871297ac064ea51dfdb4786d1dbba96 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.503 2008/10/31 08:39:21 heikki Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.504 2008/11/09 00:28:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -568,6 +568,10 @@ main(int argc, char **argv) /* Set the datestyle to ISO to ensure the dump's portability */ do_sql_command(g_conn, "SET DATESTYLE = ISO"); + /* Likewise, avoid using sql_standard intervalstyle */ + if (g_fout->remoteVersion >= 80400) + do_sql_command(g_conn, "SET INTERVALSTYLE = POSTGRES"); + /* * If supported, set extra_float_digits so that we can dump float data * exactly (given correctly implemented float I/O code, anyway) diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 24e50d071d924382337089077924a10616f635c5..8c38aaf95bdceaa4a0e762ccff050ac33b2bab0e 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2008, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.174 2008/11/07 18:25:07 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.175 2008/11/09 00:28:35 tgl Exp $ */ /*---------------------------------------------------------------------- @@ -1956,6 +1956,13 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_LIST(my_list); } + else if (pg_strcasecmp(prev2_wd, "IntervalStyle") == 0) + { + static const char *const my_list[] = + {"postgres", "postgres_verbose", "sql_standard", NULL}; + + COMPLETE_WITH_LIST(my_list); + } else if (pg_strcasecmp(prev2_wd, "GEQO") == 0) { static const char *const my_list[] = diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 98e28e0d10bd7558c9a7ed659103a0d465d95382..9348a527aa65dc1cf3264cbf62cea1db25b61813 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -13,7 +13,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.203 2008/10/09 17:24:05 alvherre Exp $ + * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.204 2008/11/09 00:28:35 tgl Exp $ * * NOTES * some of the information in this file should be moved to other files. @@ -191,6 +191,18 @@ extern PGDLLIMPORT Oid MyDatabaseTableSpace; extern int DateStyle; extern int DateOrder; + +/* + * IntervalStyles + * INTSTYLE_POSTGRES Like Postgres < 8.4 when DateStyle = 'iso' + * INTSTYLE_POSTGRES_VERBOSE Like Postgres < 8.4 when DateStyle != 'iso' + * INTSTYLE_SQL_STANDARD SQL standard interval literals + */ +#define INTSTYLE_POSTGRES 0 +#define INTSTYLE_POSTGRES_VERBOSE 1 +#define INTSTYLE_SQL_STANDARD 2 + +extern int IntervalStyle; /* * HasCTZSet is true if user has set timezone as a numeric offset from UTC. diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 34c9cf11ae89fb4e3a0035e95431181dc24b9616..d8b243b8d69ea7328f1bda6d9fa7991ce750f43e 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.366 2008/11/03 14:18:57 mha Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.367 2008/11/09 00:28:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -208,6 +208,9 @@ static const PQEnvironmentOption EnvironmentOptions[] = { "PGDATESTYLE", "datestyle" }, + { + "PGINTERVALSTYLE", "intervalstyle" + }, { "PGTZ", "timezone" }, diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 82705163888d81e1bf9275bba983ddad8f58afde..e8fee7a38e9fea4d41e3a8a177a88763545efeba 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -2,6 +2,7 @@ -- INTERVAL -- SET DATESTYLE = 'ISO'; +SET IntervalStyle to postgres; -- check acceptance of "time zone style" SELECT INTERVAL '01:00' AS "One hour"; One hour @@ -273,6 +274,7 @@ FROM INTERVAL_MULDIV_TBL; DROP TABLE INTERVAL_MULDIV_TBL; SET DATESTYLE = 'postgres'; +SET IntervalStyle to postgres_verbose; SELECT '' AS ten, * FROM INTERVAL_TBL; ten | f1 -----+------------------------------- @@ -326,6 +328,7 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour"; -- test fractional second input, and detection of duplicate units SET DATESTYLE = 'ISO'; +SET IntervalStyle TO postgres; SELECT '1 millisecond'::interval, '1 microsecond'::interval, '500 seconds 99 milliseconds 51 microseconds'::interval; interval | interval | interval @@ -609,3 +612,37 @@ SELECT interval '1 2:03:04.5678' minute to second(2); 00:03:04.57 (1 row) +-- test inputting and outputting SQL standard interval literals +SET IntervalStyle TO sql_standard; +SELECT interval '0' AS "zero", + interval '1-2' year to month AS "year-month", + interval '1 2:03:04' day to second AS "day-time", + - interval '1-2' AS "negative year-month", + - interval '1 2:03:04' AS "negative day-time"; + zero | year-month | day-time | negative year-month | negative day-time +------+------------+-----------+---------------------+------------------- + 0 | 1-2 | 1 2:03:04 | -1-2 | -1 2:03:04 +(1 row) + +-- test input of some not-quite-standard interval values in the sql style +SET IntervalStyle TO postgres; +SELECT interval '+1 -1:00:00', + interval '-1 +1:00:00', + interval '+1-2 -3 +4:05:06.789', + interval '-1-2 +3 -4:05:06.789'; + interval | interval | interval | interval +-----------------+-------------------+-------------------------------------+---------------------------------------- + 1 day -01:00:00 | -1 days +01:00:00 | 1 year 2 mons -3 days +04:05:06.789 | -1 years -2 mons +3 days -04:05:06.789 +(1 row) + +-- test output of couple non-standard interval values in the sql style +SET IntervalStyle TO sql_standard; +SELECT interval '1 day -1 hours', + interval '-1 days +1 hours', + interval '1 years 2 months -3 days 4 hours 5 minutes 6.789 seconds', + - interval '1 years 2 months -3 days 4 hours 5 minutes 6.789 seconds'; + interval | interval | interval | ?column? +------------------+------------------+----------------------+---------------------- + +0-0 +1 -1:00:00 | +0-0 -1 +1:00:00 | +1-2 -3 +4:05:06.789 | -1-2 +3 -4:05:06.789 +(1 row) + diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 93f99eba713a3831fb98ef7f44e5551be6cc736a..8d4c3dda339ff38ecf6b147c78ca764bb0224610 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -11,7 +11,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/test/regress/pg_regress.c,v 1.48 2008/10/01 22:38:57 petere Exp $ + * $PostgreSQL: pgsql/src/test/regress/pg_regress.c,v 1.49 2008/11/09 00:28:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -708,6 +708,7 @@ initialize_environment(void) */ putenv("PGTZ=PST8PDT"); putenv("PGDATESTYLE=Postgres, MDY"); + putenv("PGINTERVALSTYLE=postgres_verbose"); if (temp_install) { diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 732ca026f9f0cc00460afaf6c1eca5cfe769d9f2..9b32dd6f3b3074f346e61682c1c7844d796384e9 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -3,6 +3,7 @@ -- SET DATESTYLE = 'ISO'; +SET IntervalStyle to postgres; -- check acceptance of "time zone style" SELECT INTERVAL '01:00' AS "One hour"; @@ -94,6 +95,7 @@ FROM INTERVAL_MULDIV_TBL; DROP TABLE INTERVAL_MULDIV_TBL; SET DATESTYLE = 'postgres'; +SET IntervalStyle to postgres_verbose; SELECT '' AS ten, * FROM INTERVAL_TBL; @@ -118,6 +120,8 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour"; -- test fractional second input, and detection of duplicate units SET DATESTYLE = 'ISO'; +SET IntervalStyle TO postgres; + SELECT '1 millisecond'::interval, '1 microsecond'::interval, '500 seconds 99 milliseconds 51 microseconds'::interval; SELECT '3 days 5 milliseconds'::interval; @@ -174,3 +178,25 @@ SELECT interval '1 2:03:04.5678' hour to second(2); SELECT interval '1 2.3456' minute to second(2); SELECT interval '1 2:03.5678' minute to second(2); SELECT interval '1 2:03:04.5678' minute to second(2); + +-- test inputting and outputting SQL standard interval literals +SET IntervalStyle TO sql_standard; +SELECT interval '0' AS "zero", + interval '1-2' year to month AS "year-month", + interval '1 2:03:04' day to second AS "day-time", + - interval '1-2' AS "negative year-month", + - interval '1 2:03:04' AS "negative day-time"; + +-- test input of some not-quite-standard interval values in the sql style +SET IntervalStyle TO postgres; +SELECT interval '+1 -1:00:00', + interval '-1 +1:00:00', + interval '+1-2 -3 +4:05:06.789', + interval '-1-2 +3 -4:05:06.789'; + +-- test output of couple non-standard interval values in the sql style +SET IntervalStyle TO sql_standard; +SELECT interval '1 day -1 hours', + interval '-1 days +1 hours', + interval '1 years 2 months -3 days 4 hours 5 minutes 6.789 seconds', + - interval '1 years 2 months -3 days 4 hours 5 minutes 6.789 seconds';