Thread: Re: [COMMITTERS] pgsql-server/ oc/src/sgml/datatype.sgml rc/bac ...

Re: [COMMITTERS] pgsql-server/ oc/src/sgml/datatype.sgml rc/bac ...

From
Bruce Momjian
Date:
Tom Lane wrote:
> momjian@svr1.postgresql.org (Bruce Momjian) writes:
> >     Attached is a patch that addressed all the discussed issues that
> >     did not break backward compatability,
>
> Was this patch actually agreed to?  I hadn't gotten around to looking
> at it because I thought Peter was still objecting to the whole idea.
>
> >       including the ability to
> >     output ISO-8601 compliant intervals by setting datestyle to
> >     iso8601basic.
>
> This is a horrid, horrid idea.  Datestyle is already a complete mess
> because it is being used to control several things; it should have been
> two or possibly three GUC variables not one.  Sticking in yet another
> behavior is just not acceptable IMHO, especially when it's defined as
> non-orthogonally as that.
>
> Please revert that part of the patch and instead invent a new GUC
> variable that's specifically for interval formatting.
>
> BTW, I can tell without looking that the patch is deficient in
> documentation; if it has effects on GUC variables, why is there no
> mod in runtime.sgml?

OK, I have backed out the patch.  Peter did object, but I said I thought
the submitter had addressed the objections (backward compatibility) and
I asked for someone to tell me the objections, and no one replied, so I
assumed it was OK and it went through the standard patch application
process.

The patch is attached if someone wants to modify it to head in the
direction that most developers prefer.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
? log
? config.log
? GNUmakefile
? config.status
? doc/src/sgml/HISTORY
? src/Makefile.custom
? src/Makefile.global
? src/log
? src/backend/postgres
? src/backend/catalog/postgres.bki
? src/backend/catalog/postgres.description
? src/backend/utils/mb/conversion_procs/conversion_create.sql
? src/backend/utils/mb/conversion_procs/ascii_and_mic/libascii_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/cyrillic_and_mic/libcyrillic_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_cn_and_mic/libeuc_cn_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_jp_and_sjis/libeuc_jp_and_sjis.so.0.0
? src/backend/utils/mb/conversion_procs/euc_kr_and_mic/libeuc_kr_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_tw_and_big5/libeuc_tw_and_big5.so.0.0
? src/backend/utils/mb/conversion_procs/latin2_and_win1250/liblatin2_and_win1250.so.0.0
? src/backend/utils/mb/conversion_procs/latin_and_mic/liblatin_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_ascii/libutf8_and_ascii.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_big5/libutf8_and_big5.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_cyrillic/libutf8_and_cyrillic.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_cn/libutf8_and_euc_cn.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_jp/libutf8_and_euc_jp.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_kr/libutf8_and_euc_kr.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_tw/libutf8_and_euc_tw.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_gb18030/libutf8_and_gb18030.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_gbk/libutf8_and_gbk.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_iso8859/libutf8_and_iso8859.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_iso8859_1/libutf8_and_iso8859_1.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_johab/libutf8_and_johab.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_sjis/libutf8_and_sjis.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_tcvn/libutf8_and_tcvn.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_uhc/libutf8_and_uhc.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_win1250/libutf8_and_win1250.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_win1256/libutf8_and_win1256.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_win874/libutf8_and_win874.so.0.0
? src/bin/initdb/initdb
? src/bin/initlocation/initlocation
? src/bin/ipcclean/ipcclean
? src/bin/pg_config/pg_config
? src/bin/pg_controldata/pg_controldata
? src/bin/pg_ctl/pg_ctl
? src/bin/pg_dump/pg_dump
? src/bin/pg_dump/pg_restore
? src/bin/pg_dump/pg_dumpall
? src/bin/pg_encoding/pg_encoding
? src/bin/pg_resetxlog/pg_resetxlog
? src/bin/pgtclsh/pgtclsh
? src/bin/pgtclsh/pgtksh
? src/bin/psql/psql
? src/bin/scripts/createdb
? src/bin/scripts/dropdb
? src/bin/scripts/createlang
? src/bin/scripts/createuser
? src/bin/scripts/droplang
? src/bin/scripts/dropuser
? src/bin/scripts/clusterdb
? src/bin/scripts/vacuumdb
? src/include/pg_config.h
? src/include/stamp-h
? src/interfaces/ecpg/compatlib/libecpg_compat.so.1.1
? src/interfaces/ecpg/ecpglib/libecpg.so.4.1
? src/interfaces/ecpg/pgtypeslib/libpgtypes.so.1.1
? src/interfaces/ecpg/preproc/ecpg
? src/interfaces/libpgtcl/libpgtcl.so.2.5
? src/interfaces/libpq/libpq.so.3.2
? src/pl/plperl/SPI.c
? src/pl/plperl/libplperl.so.0.0
? src/pl/plpgsql/src/libplpgsql.so.1.0
? src/pl/tcl/libpltcl.so.2.0
? src/pl/tcl/modules/pltcl_loadmod
? src/pl/tcl/modules/pltcl_delmod
? src/pl/tcl/modules/pltcl_listmod
? src/test/regress/pg_regress
? src/test/regress/log
? src/test/regress/tmp_check
? src/test/regress/results
? src/test/regress/expected/copy.out
? src/test/regress/expected/create_function_1.out
? src/test/regress/expected/create_function_2.out
? src/test/regress/expected/misc.out
? src/test/regress/expected/constraints.out
? src/test/regress/sql/copy.sql
? src/test/regress/sql/create_function_1.sql
? src/test/regress/sql/create_function_2.sql
? src/test/regress/sql/misc.sql
? src/test/regress/sql/constraints.sql
? src/tools/thread/thread_test
Index: doc/src/sgml/datatype.sgml
===================================================================
RCS file: /cvsroot/pgsql-server/doc/src/sgml/datatype.sgml,v
retrieving revision 1.135
retrieving revision 1.136
diff -c -r1.135 -r1.136
*** doc/src/sgml/datatype.sgml    1 Dec 2003 22:07:55 -0000    1.135
--- doc/src/sgml/datatype.sgml    20 Dec 2003 15:32:54 -0000    1.136
***************
*** 1,5 ****
  <!--
! $PostgreSQL: pgsql-server/doc/src/sgml/datatype.sgml,v 1.135 2003/12/01 22:07:55 momjian Exp $
  -->

   <chapter id="datatype">
--- 1,5 ----
  <!--
! $PostgreSQL: pgsql-server/doc/src/sgml/datatype.sgml,v 1.136 2003/12/20 15:32:54 momjian Exp $
  -->

   <chapter id="datatype">
***************
*** 1785,1790 ****
--- 1785,1841 ----
        <replaceable>p</replaceable> should be between 0 and 6, and
        defaults to the precision of the input literal.
       </para>
+
+
+      <para>
+       Alternatively, <type>interval</type> values can be written as
+       ISO 8601 time intervals, using the "Format with time-unit designators".
+       This format always starts with the character <literal>'P'</>, followed
+       by a string of values followed by single character time-unit designators.
+       A <literal>'T'</> separates the date and time parts of the interval.
+      </para>
+
+      <para>
+        Format:  PnYnMnDTnHnMnS
+      </para>
+      <para>
+        In this format, <literal>'n'</> gets replaced by a number, and
+        <literal>Y</> represents years,
+        <literal>M</> (in the date part) months,
+        <literal>D</> months,
+        <literal>H</> hours,
+        <literal>M</> (in the time part) minutes,
+        and <literal>S</> seconds.
+      </para>
+
+
+      <table id="interval-example-table">
+        <title>Interval Example</title>
+        <tgroup cols="2">
+         <thead>
+          <row>
+           <entry>Traditional</entry>
+           <entry>ISO-8601 time-interval</entry>
+          </row>
+         </thead>
+         <tbody>
+          <row>
+           <entry>1 month</entry>
+           <entry>P1M</entry>
+          </row>
+          <row>
+           <entry>1 hour 30 minutes</entry>
+           <entry>PT1H30M</entry>
+          </row>
+          <row>
+           <entry>2 years 10 months 15 days 10 hours 30 minutes 20 seconds</entry>
+           <entry>P2Y10M15DT10H30M20S</entry>
+          </row>
+         </tbody>
+        </thead>
+       </table>
+
+      </para>
      </sect3>

      <sect3>
***************
*** 1941,1946 ****
--- 1992,2002 ----
           <entry>regional style</entry>
           <entry>17.12.1997 07:37:16.00 PST</entry>
          </row>
+     <row>
+      <entry>ISO8601basic</entry>
+      <entry>ISO 8601 basic format</entry>
+      <entry>19971217T073716-08</entry>
+     </row>
         </tbody>
        </tgroup>
       </table>
***************
*** 1995,2000 ****
--- 2051,2061 ----
  <programlisting>
  <optional> <replaceable>quantity</> <replaceable>unit</> <optional> ... </> </> <optional> <replaceable>days</> </>
<optional><replaceable>hours</>:<replaceable>minutes</>:<replaceable>sekunden</> </optional> 
  </programlisting>
+     </para>
+
+     <para>
+      If the <varname>datestyle</> is set to iso8601basic, the interval
+      output is a ISO-8601 time interval with time-unit designator (like P1Y6M or PT23H59M59S).
      </para>

      <para>
Index: src/backend/commands/variable.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/backend/commands/variable.c,v
retrieving revision 1.90
retrieving revision 1.91
diff -c -r1.90 -r1.91
*** src/backend/commands/variable.c    29 Nov 2003 19:51:48 -0000    1.90
--- src/backend/commands/variable.c    20 Dec 2003 15:32:54 -0000    1.91
***************
*** 9,15 ****
   *
   *
   * IDENTIFICATION
!  *      $PostgreSQL: pgsql-server/src/backend/commands/variable.c,v 1.90 2003/11/29 19:51:48 pgsql Exp $
   *
   *-------------------------------------------------------------------------
   */
--- 9,15 ----
   *
   *
   * IDENTIFICATION
!  *      $PostgreSQL: pgsql-server/src/backend/commands/variable.c,v 1.91 2003/12/20 15:32:54 momjian Exp $
   *
   *-------------------------------------------------------------------------
   */
***************
*** 82,88 ****

          /* Ugh. Somebody ought to write a table driven version -- mjl */

!         if (strcasecmp(tok, "ISO") == 0)
          {
              newDateStyle = USE_ISO_DATES;
              scnt++;
--- 82,93 ----

          /* Ugh. Somebody ought to write a table driven version -- mjl */

!         if (strcasecmp(tok, "ISO8601BASIC") == 0)
!         {
!             newDateStyle = USE_ISO8601BASIC_DATES;
!             scnt++;
!         }
!         else if (strcasecmp(tok, "ISO") == 0)
          {
              newDateStyle = USE_ISO_DATES;
              scnt++;
***************
*** 197,202 ****
--- 202,210 ----
      {
          case USE_ISO_DATES:
              strcpy(result, "ISO");
+             break;
+         case USE_ISO8601BASIC_DATES:
+             strcpy(result, "ISO8601BASIC");
              break;
          case USE_SQL_DATES:
              strcpy(result, "SQL");
Index: src/backend/utils/adt/datetime.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/backend/utils/adt/datetime.c,v
retrieving revision 1.121
retrieving revision 1.122
diff -c -r1.121 -r1.122
*** src/backend/utils/adt/datetime.c    17 Dec 2003 21:45:44 -0000    1.121
--- src/backend/utils/adt/datetime.c    20 Dec 2003 15:32:54 -0000    1.122
***************
*** 8,14 ****
   *
   *
   * IDENTIFICATION
!  *      $PostgreSQL: pgsql-server/src/backend/utils/adt/datetime.c,v 1.121 2003/12/17 21:45:44 tgl Exp $
   *
   *-------------------------------------------------------------------------
   */
--- 8,14 ----
   *
   *
   * IDENTIFICATION
!  *      $PostgreSQL: pgsql-server/src/backend/utils/adt/datetime.c,v 1.122 2003/12/20 15:32:54 momjian Exp $
   *
   *-------------------------------------------------------------------------
   */
***************
*** 37,42 ****
--- 37,43 ----
  static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
  static int    DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
  static void TrimTrailingZeros(char *str);
+ static int  DecodeISO8601Interval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec);


  int            day_tab[2][13] = {
***************
*** 2888,2893 ****
--- 2889,3134 ----
  }


+ /*
+  * A small helper function to avoid cut&paste code in DecodeIso8601Interval
+  */
+ static void adjust_fval(double fval,struct tm * tm, fsec_t *fsec, int scale)
+ {
+     int    sec;
+     fval       *= scale;
+     sec            = fval;
+     tm->tm_sec += sec;
+ #ifdef HAVE_INT64_TIMESTAMP
+     *fsec       += ((fval - sec) * 1000000);
+ #else
+     *fsec       += (fval - sec);
+ #endif
+ }
+
+
+ /* DecodeISO8601Interval()
+  *
+  *  Check if it's a ISO 8601 Section 5.5.4.2 "Representation of
+  *  time-interval by duration only."
+  *  Basic extended format:  PnYnMnDTnHnMnS
+  *                          PnW
+  *  For more info.
+  *  http://www.astroclark.freeserve.co.uk/iso8601/index.html
+  *  ftp://ftp.qsl.net/pub/g1smd/154N362_.PDF
+  *
+  *  Examples:  P1D  for 1 day
+  *             PT1H for 1 hour
+  *             P2Y6M7DT1H30M for 2 years, 6 months, 7 days 1 hour 30 min
+  *
+  *  The first field is exactly "p" or "pt" it may be of this type.
+  *
+  *  Returns -1 if the field is not of this type.
+  *
+  *  It pretty strictly checks the spec, with the two exceptions
+  *  that a week field ('W') may coexist with other units, and that
+  *  this function allows decimals in fields other than the least
+  *  significant units.
+  */
+ int
+ DecodeISO8601Interval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec)
+ {
+     char       *cp;
+     int            fmask = 0,
+                 tmask;
+     int            val;
+     double        fval;
+     int            arg;
+     int            datepart;
+
+     /*
+      * An ISO 8601 "time-interval by duration only" must start
+      * with a 'P'.  If it contains a date-part, 'p' will be the
+      * only character in the field.  If it contains no date part
+      * it will contain exactly to characters 'PT' indicating a
+      * time part.
+      * Anything else is illegal and will be treated like a
+      * traditional postgresql interval.
+      */
+     if (!(field[0][0] == 'p' &&
+           ((field[0][1] == 0) || (field[0][1] == 't' && field[0][2] == 0))))
+     {
+       return -1;
+     }
+
+
+     /*
+      * If the first field is exactly 1 character ('P'), it starts
+      * with date elements.  Otherwise it's two characters ('PT');
+      * indicating it starts with a time part.
+      */
+     datepart = (field[0][1] == 0);
+
+     /*
+      * Every value must have a unit, so we require an even
+      * number of value/unit pairs. Therefore we require an
+      * odd nubmer of fields, including the prefix 'P'.
+      */
+     if ((nf & 1) == 0)
+         return -1;
+
+     /*
+      * Process pairs of fields at a time.
+      */
+     for (arg = 1 ; arg < nf ; arg+=2)
+     {
+         char * value = field[arg  ];
+         char * units = field[arg+1];
+
+         /*
+          * The value part must be a number.
+          */
+         if (ftype[arg] != DTK_NUMBER)
+             return -1;
+
+         /*
+          * extract the number, almost exactly like the non-ISO interval.
+          */
+         val = strtol(value, &cp, 10);
+
+         /*
+          * One difference from the normal postgresql interval below...
+          * ISO 8601 states that "Of these, the comma is the preferred
+          * sign" so I allow it here for locales that support it.
+          * Note: Perhaps the old-style interval code below should
+          * allow for this too, but I didn't want to risk backward
+          * compatability.
+          */
+         if (*cp == '.' || *cp == ',')
+         {
+             fval = strtod(cp, &cp);
+             if (*cp != '\0')
+                 return -1;
+
+             if (val < 0)
+                 fval = -(fval);
+         }
+         else if (*cp == '\0')
+             fval = 0;
+         else
+             return -1;
+
+
+         if (datepart)
+         {
+             /*
+              * All the 8601 unit specifiers are 1 character, but may
+              * be followed by a 'T' character if transitioning between
+              * the date part and the time part.  If it's not either
+              * one character or two characters with the second being 't'
+              * it's an error.
+              */
+             if (!(units[1] == 0 || (units[1] == 't' && units[2] == 0)))
+                 return -1;
+
+             if (units[1] == 't')
+                 datepart = 0;
+
+             switch (units[0]) /* Y M D W */
+             {
+                 case 'd':
+                     tm->tm_mday += val;
+                     if (fval != 0)
+                       adjust_fval(fval,tm,fsec, 86400);
+                     tmask = ((fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY));
+                     break;
+
+                 case 'w':
+                     tm->tm_mday += val * 7;
+                     if (fval != 0)
+                       adjust_fval(fval,tm,fsec,7 * 86400);
+                     tmask = ((fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY));
+                     break;
+
+                 case 'm':
+                     tm->tm_mon += val;
+                     if (fval != 0)
+                       adjust_fval(fval,tm,fsec,30 * 86400);
+                     tmask = DTK_M(MONTH);
+                     break;
+
+                 case 'y':
+                     /*
+                      * Why can fractional months produce seconds,
+                      * but fractional years can't?  Well the older
+                      * interval code below has the same property
+                      * so this one follows the other one too.
+                      */
+                     tm->tm_year += val;
+                     if (fval != 0)
+                         tm->tm_mon += (fval * 12);
+                     tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR));
+                     break;
+
+                 default:
+                     return -1;  /* invald date unit prefix */
+             }
+         }
+         else
+         {
+             /*
+              * ISO 8601 time part.
+              * In the time part, only one-character
+              * unit prefixes are allowed.  If it's more
+              * than one character, it's not a valid ISO 8601
+              * time interval by duration.
+              */
+             if (units[1] != 0)
+                 return -1;
+
+             switch (units[0]) /* H M S */
+             {
+                 case 's':
+                     tm->tm_sec += val;
+ #ifdef HAVE_INT64_TIMESTAMP
+                     *fsec += (fval * 1000000);
+ #else
+                     *fsec += fval;
+ #endif
+                     tmask = DTK_M(SECOND);
+                     break;
+
+                 case 'm':
+                     tm->tm_min += val;
+                     if (fval != 0)
+                       adjust_fval(fval,tm,fsec,60);
+                     tmask = DTK_M(MINUTE);
+                     break;
+
+                 case 'h':
+                     tm->tm_hour += val;
+                     if (fval != 0)
+                       adjust_fval(fval,tm,fsec,3600);
+                     tmask = DTK_M(HOUR);
+                     break;
+
+                 default:
+                     return -1; /* invald time unit prefix */
+             }
+         }
+         fmask |= tmask;
+     }
+
+     if (*fsec != 0)
+     {
+         int            sec;
+
+ #ifdef HAVE_INT64_TIMESTAMP
+         sec = (*fsec / INT64CONST(1000000));
+         *fsec -= (sec * INT64CONST(1000000));
+ #else
+         TMODULO(*fsec, sec, 1e0);
+ #endif
+         tm->tm_sec += sec;
+     }
+     return (fmask != 0) ? 0 : -1;
+ }
+
+
  /* DecodeInterval()
   * Interpret previously parsed fields for general time interval.
   * Returns 0 if successful, DTERR code if bogus input detected.
***************
*** 2897,2903 ****
--- 3138,3148 ----
   *
   * Allow ISO-style time span, with implicit units on number of days
   *    preceding an hh:mm:ss field. - thomas 1998-04-30
+  *
+  * Allow ISO-8601 style "Representation of time-interval by duration only"
+  *  of the format 'PnYnMnDTnHnMnS' and 'PnW' - ron 2003-08-30
   */
+
  int
  DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec)
  {
***************
*** 2922,2927 ****
--- 3167,3189 ----
      tm->tm_sec = 0;
      *fsec = 0;

+     /*
+      *  Check if it's a ISO 8601 Section 5.5.4.2 "Representation of
+      *  time-interval by duration only."
+      *  Basic extended format:  PnYnMnDTnHnMnS
+      *                          PnW
+      *  http://www.astroclark.freeserve.co.uk/iso8601/index.html
+      *  ftp://ftp.qsl.net/pub/g1smd/154N362_.PDF
+      *  Examples:  P1D  for 1 day
+      *             PT1H for 1 hour
+      *             P2Y6M7DT1H30M for 2 years, 6 months, 7 days 1 hour 30 min
+      *
+      *  The first field is exactly "p" or "pt" it may be of this type.
+      */
+     if (DecodeISO8601Interval(field,ftype,nf,dtype,tm,fsec) == 0) {
+         return 0;
+     }
+
      /* read through list backwards to pick up units before values */
      for (i = nf - 1; i >= 0; i--)
      {
***************
*** 2999,3004 ****
--- 3261,3267 ----
                  if (type == IGNORE_DTF)
                      type = DTK_SECOND;

+                 /* should this allow ',' for locales that use it ? */
                  if (*cp == '.')
                  {
                      fval = strtod(cp, &cp);
***************
*** 3370,3375 ****
--- 3633,3648 ----
                        -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
              break;

+         case USE_ISO8601BASIC_DATES:
+             /* compatible with ISO date formats */
+             if (tm->tm_year > 0)
+                 sprintf(str, "%04d%02d%02d",
+                         tm->tm_year, tm->tm_mon, tm->tm_mday);
+             else
+                 sprintf(str, "%04d%02d%02d %s",
+                       -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
+             break;
+
          case USE_SQL_DATES:
              /* compatible with Oracle/Ingres date formats */
              if (DateOrder == DATEORDER_DMY)
***************
*** 3525,3530 ****
--- 3798,3848 ----
              }
              break;

+         case USE_ISO8601BASIC_DATES: // BASIC
+             /* Compatible with ISO-8601 date formats */
+
+             sprintf(str, "%04d%02d%02dT%02d%02d",
+                   ((tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)),
+                     tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
+
+             /*
+              * Print fractional seconds if any.  The field widths here
+              * should be at least equal to MAX_TIMESTAMP_PRECISION.
+              *
+              * In float mode, don't print fractional seconds before 1 AD,
+              * since it's unlikely there's any precision left ...
+              */
+ #ifdef HAVE_INT64_TIMESTAMP
+             if (fsec != 0)
+             {
+                 sprintf((str + strlen(str)), "%02d.%06d", tm->tm_sec, fsec);
+ #else
+             if ((fsec != 0) && (tm->tm_year > 0))
+             {
+                 sprintf((str + strlen(str)), "%09.6f", tm->tm_sec + fsec);
+ #endif
+                 TrimTrailingZeros(str);
+             }
+             else
+                 sprintf((str + strlen(str)), "%02d", tm->tm_sec);
+
+             if (tm->tm_year <= 0)
+                 sprintf((str + strlen(str)), " BC");
+
+             /*
+              * tzp == NULL indicates that we don't want *any* time zone
+              * info in the output string. *tzn != NULL indicates that we
+              * have alpha time zone info available. tm_isdst != -1
+              * indicates that we have a valid time zone translation.
+              */
+             if ((tzp != NULL) && (tm->tm_isdst >= 0))
+             {
+                 hour = -(*tzp / 3600);
+                 min = ((abs(*tzp) / 60) % 60);
+                 sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min);
+             }
+             break;
+
          case USE_SQL_DATES:
              /* Compatible with Oracle/Ingres date formats */

***************
*** 3688,3693 ****
--- 4006,4020 ----
  }    /* EncodeDateTime() */


+ /*
+  * Small helper function to avoid cut&paste in EncodeInterval below
+  */
+ static char * AppendISO8601Fragment(char * cp, int value, char character)
+ {
+     sprintf(cp,"%d%c",value,character);
+     return cp + strlen(cp);
+ }
+
  /* EncodeInterval()
   * Interpret time structure as a delta time and convert to string.
   *
***************
*** 3695,3700 ****
--- 4022,4035 ----
   * 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
+  * If you want this (perhaps for interoperability with computers
+  * rather than humans), datestyle 'iso8601basic' will output these.
+  * - ron 2003-07-14
   */
  int
  EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
***************
*** 3777,3782 ****
--- 4112,4159 ----
              }
              break;

+         case USE_ISO8601BASIC_DATES:
+             sprintf(cp,"P");
+             cp++;
+             if (tm->tm_year != 0) cp = AppendISO8601Fragment(cp,tm->tm_year,'Y');
+             if (tm->tm_mon  != 0) cp = AppendISO8601Fragment(cp,tm->tm_mon ,'M');
+             if (tm->tm_mday != 0) cp = AppendISO8601Fragment(cp,tm->tm_mday,'D');
+             if ((tm->tm_hour != 0) || (tm->tm_min != 0) ||
+                 (tm->tm_sec  != 0) || (fsec       != 0))
+             {
+                 sprintf(cp,"T"),
+                 cp++;
+             }
+             if (tm->tm_hour != 0) cp = AppendISO8601Fragment(cp,tm->tm_hour,'H');
+             if (tm->tm_min  != 0) cp = AppendISO8601Fragment(cp,tm->tm_min ,'M');
+
+             if ((tm->tm_year == 0) && (tm->tm_mon == 0) && (tm->tm_mday == 0) &&
+                 (tm->tm_hour == 0) && (tm->tm_min == 0) && (tm->tm_sec  == 0) &&
+                 (fsec        == 0))
+             {
+                 sprintf(cp,"T0S"),
+                 cp+=2;
+             }
+             else if (fsec != 0)
+             {
+ #ifdef HAVE_INT64_TIMESTAMP
+                 sprintf(cp, "%d", abs(tm->tm_sec));
+                 cp += strlen(cp);
+                 sprintf(cp, ".%6dS", ((fsec >= 0) ? fsec : -(fsec)));
+ #else
+                 fsec += tm->tm_sec;
+                 sprintf(cp, "%fS", fabs(fsec));
+ #endif
+                 TrimTrailingZeros(cp);
+                 cp += strlen(cp);
+             }
+             else if (tm->tm_sec != 0)
+             {
+                 cp = AppendISO8601Fragment(cp,tm->tm_sec ,'S');
+                 cp += strlen(cp);
+             }
+             break;
+
          case USE_POSTGRES_DATES:
          default:
              strcpy(cp, "@ ");
***************
*** 3901,3907 ****
      }

      /* identically zero? then put in a unitless zero... */
!     if (!is_nonzero)
      {
          strcat(cp, "0");
          cp += strlen(cp);
--- 4278,4284 ----
      }

      /* identically zero? then put in a unitless zero... */
!     if (!is_nonzero && (style!=USE_ISO8601BASIC_DATES))
      {
          strcat(cp, "0");
          cp += strlen(cp);
Index: src/include/miscadmin.h
===================================================================
RCS file: /cvsroot/pgsql-server/src/include/miscadmin.h,v
retrieving revision 1.138
retrieving revision 1.139
diff -c -r1.138 -r1.139
*** src/include/miscadmin.h    29 Nov 2003 22:40:53 -0000    1.138
--- src/include/miscadmin.h    20 Dec 2003 15:32:55 -0000    1.139
***************
*** 12,18 ****
   * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
   * Portions Copyright (c) 1994, Regents of the University of California
   *
!  * $PostgreSQL: pgsql-server/src/include/miscadmin.h,v 1.138 2003/11/29 22:40:53 pgsql Exp $
   *
   * NOTES
   *      some of the information in this file should be moved to
--- 12,18 ----
   * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
   * Portions Copyright (c) 1994, Regents of the University of California
   *
!  * $PostgreSQL: pgsql-server/src/include/miscadmin.h,v 1.139 2003/12/20 15:32:55 momjian Exp $
   *
   * NOTES
   *      some of the information in this file should be moved to
***************
*** 150,155 ****
--- 150,157 ----
   *    USE_ISO_DATES specifies ISO-compliant format
   *    USE_SQL_DATES specifies Oracle/Ingres-compliant format
   *    USE_GERMAN_DATES specifies German-style dd.mm/yyyy
+  *    USE_ISO8601BASIC_DATES specifies ISO-8601-basic format (including
+  *                         ISO compliant but non-human-friendly intervals)
   *
   * DateOrder defines the field order to be assumed when reading an
   * ambiguous date (anything not in YYYY-MM-DD format, with a four-digit
***************
*** 169,174 ****
--- 171,177 ----
  #define USE_ISO_DATES            1
  #define USE_SQL_DATES            2
  #define USE_GERMAN_DATES        3
+ #define USE_ISO8601BASIC_DATES    4

  /* valid DateOrder values */
  #define DATEORDER_YMD            0
Index: src/interfaces/ecpg/pgtypeslib/dt.h
===================================================================
RCS file: /cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/dt.h,v
retrieving revision 1.14
retrieving revision 1.15
diff -c -r1.14 -r1.15
*** src/interfaces/ecpg/pgtypeslib/dt.h    5 Oct 2003 11:12:00 -0000    1.14
--- src/interfaces/ecpg/pgtypeslib/dt.h    20 Dec 2003 15:32:55 -0000    1.15
***************
*** 21,26 ****
--- 21,27 ----
  #define USE_ISO_DATES                    1
  #define USE_SQL_DATES                    2
  #define USE_GERMAN_DATES                3
+ #define USE_ISO8601BASIC_DATES            4

  #define DAGO            "ago"
  #define EPOCH            "epoch"
Index: src/interfaces/ecpg/pgtypeslib/dt_common.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/dt_common.c,v
retrieving revision 1.12
retrieving revision 1.13
diff -c -r1.12 -r1.13
*** src/interfaces/ecpg/pgtypeslib/dt_common.c    5 Oct 2003 11:12:00 -0000    1.12
--- src/interfaces/ecpg/pgtypeslib/dt_common.c    20 Dec 2003 15:32:55 -0000    1.13
***************
*** 704,709 ****
--- 704,719 ----
                        -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
              break;

+         case USE_ISO8601BASIC_DATES:
+             /* compatible with ISO date formats */
+             if (tm->tm_year > 0)
+                 sprintf(str, "%04d%02d%02d",
+                         tm->tm_year, tm->tm_mon, tm->tm_mday);
+             else
+                 sprintf(str, "%04d%02d%02d %s",
+                       -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
+             break;
+
          case USE_SQL_DATES:
              /* compatible with Oracle/Ingres date formats */
              if (EuroDates)
***************
*** 802,807 ****
--- 812,862 ----
              }
              else
                  sprintf((str + strlen(str)), ":%02d", tm->tm_sec);
+
+             if (tm->tm_year <= 0)
+                 sprintf((str + strlen(str)), " BC");
+
+             /*
+              * tzp == NULL indicates that we don't want *any* time zone
+              * info in the output string. *tzn != NULL indicates that we
+              * have alpha time zone info available. tm_isdst != -1
+              * indicates that we have a valid time zone translation.
+              */
+             if ((tzp != NULL) && (tm->tm_isdst >= 0))
+             {
+                 hour = -(*tzp / 3600);
+                 min = ((abs(*tzp) / 60) % 60);
+                 sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min);
+             }
+             break;
+
+         case USE_ISO8601BASIC_DATES:
+             /* Compatible with ISO-8601 date formats */
+
+             sprintf(str, "%04d%02d%02dT%02d%02d",
+                   ((tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)),
+                     tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
+
+             /*
+              * Print fractional seconds if any.  The field widths here
+              * should be at least equal to MAX_TIMESTAMP_PRECISION.
+              *
+              * In float mode, don't print fractional seconds before 1 AD,
+              * since it's unlikely there's any precision left ...
+              */
+ #ifdef HAVE_INT64_TIMESTAMP
+             if (fsec != 0)
+             {
+                 sprintf((str + strlen(str)), "%02d.%06d", tm->tm_sec, fsec);
+ #else
+             if ((fsec != 0) && (tm->tm_year > 0))
+             {
+                 sprintf((str + strlen(str)), "%09.6f", tm->tm_sec + fsec);
+ #endif
+                 TrimTrailingZeros(str);
+             }
+             else
+                 sprintf((str + strlen(str)), "%02d", tm->tm_sec);

              if (tm->tm_year <= 0)
                  sprintf((str + strlen(str)), " BC");
Index: src/interfaces/ecpg/pgtypeslib/interval.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/interval.c,v
retrieving revision 1.7
retrieving revision 1.8
diff -c -r1.7 -r1.8
*** src/interfaces/ecpg/pgtypeslib/interval.c    3 Oct 2003 10:07:28 -0000    1.7
--- src/interfaces/ecpg/pgtypeslib/interval.c    20 Dec 2003 15:32:55 -0000    1.8
***************
*** 442,447 ****
--- 442,458 ----
      return (fmask != 0) ? 0 : -1;
  }    /* DecodeInterval() */

+
+ /*
+  * Small helper function to avoid cut&paste in EncodeInterval below
+  */
+ static char * AppendISO8601Fragment(char * cp, int value, char character)
+ {
+     sprintf(cp,"%d%c",value,character);
+     return cp + strlen(cp);
+ }
+
+
  /* EncodeInterval()
   * Interpret time structure as a delta time and convert to string.
   *
***************
*** 449,454 ****
--- 460,473 ----
   * 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
+  * If you want this (perhaps for interoperability with computers
+  * rather than humans), datestyle 'iso8601basic' will output these.
+  * - ron 2003-07-14
   */
  int
  EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
***************
*** 465,471 ****
       */
      switch (style)
      {
!             /* compatible with ISO date formats */
          case USE_ISO_DATES:
              if (tm->tm_year != 0)
              {
--- 484,495 ----
       */
      switch (style)
      {
!             /* compatible with ISO date formats
!                ([ram] Not for ISO 8601, perhaps some other ISO format.
!                but I'm leaving it that way because it's more human
!                readable than ISO8601 time intervals and for backwards
!                compatability.)
!             */
          case USE_ISO_DATES:
              if (tm->tm_year != 0)
              {
***************
*** 530,535 ****
--- 554,601 ----
                      cp += strlen(cp);
                      is_nonzero = TRUE;
                  }
+             }
+             break;
+
+         case USE_ISO8601BASIC_DATES:
+             sprintf(cp,"P");
+             cp++;
+             if (tm->tm_year != 0) cp = AppendISO8601Fragment(cp,tm->tm_year,'Y');
+             if (tm->tm_mon  != 0) cp = AppendISO8601Fragment(cp,tm->tm_mon ,'M');
+             if (tm->tm_mday != 0) cp = AppendISO8601Fragment(cp,tm->tm_mday,'D');
+             if ((tm->tm_hour != 0) || (tm->tm_min != 0) ||
+                 (tm->tm_sec  != 0) || (fsec       != 0))
+             {
+                 sprintf(cp,"T"),
+                 cp++;
+             }
+             if (tm->tm_hour != 0) cp = AppendISO8601Fragment(cp,tm->tm_hour,'H');
+             if (tm->tm_min  != 0) cp = AppendISO8601Fragment(cp,tm->tm_min ,'M');
+
+             if ((tm->tm_year == 0) && (tm->tm_mon == 0) && (tm->tm_mday == 0) &&
+                 (tm->tm_hour == 0) && (tm->tm_min == 0) && (tm->tm_sec  == 0) &&
+                 (fsec        == 0))
+             {
+                 sprintf(cp,"T0S"),
+                 cp+=2;
+             }
+             else if (fsec != 0)
+             {
+ #ifdef HAVE_INT64_TIMESTAMP
+                 sprintf(cp, "%d", abs(tm->tm_sec));
+                 cp += strlen(cp);
+                 sprintf(cp, ".%6dS", ((fsec >= 0) ? fsec : -(fsec)));
+ #else
+                 fsec += tm->tm_sec;
+                 sprintf(cp, "%fS", fabs(fsec));
+ #endif
+                 TrimTrailingZeros(cp);
+                 cp += strlen(cp);
+             }
+             else if (tm->tm_sec != 0)
+             {
+                 cp = AppendISO8601Fragment(cp,tm->tm_sec ,'S');
+                 cp += strlen(cp);
              }
              break;


Re: [COMMITTERS] pgsql-server/ oc/src/sgml/datatype.sgml

From
Ron Mayer
Date:
On Sat, 20 Dec 2003, Bruce Momjian wrote:
>Tom Lane wrote:
>>This is a horrid, horrid idea.  Datestyle is already a complete mess
>>  ...
>>Please revert that part of the patch and instead invent a new GUC
>>variable that's specifically for interval formatting.
>
> OK, I have backed out the patch.  [...]


Short summary...

  Before I try this, through email someone suggested yet a
  different idea...

  Would formatting functions for intervals such as...
   to_iso8601basic_char(interval)  -- return ISO-8601 basic fmt interval
   to_iso8601basic_char(timestamp) -- return ISO-8601 basic fmt date/time
  would be better than a new GUC variable?

Longer...

  Tom Lane wrote:
  >
  > This is a horrid, horrid idea.  Datestyle is already a complete mess
  > because it is being used to control several things; it should have
  > been two or possibly three GUC variables not one.  Sticking in yet
  > another behavior is just not acceptable IMHO, especially when it's
  > defined as non-orthogonally as that.
  >
  > Please revert that part of the patch and instead invent a new GUC
  > variable that's specifically for interval formatting.

  First I just wanted to say how it ended up using datestyle...

  In the earlier discussion when Andrew asked about a way of outputting
  ISO-8601 Basic Format time intervals, the use of datestyle came up,
  and noone objected to the use of datestyle at that point.

  ... Tom was suggesting:
  http://archives.postgresql.org/pgsql-patches/2003-09/msg00122.php
  TL>
  TL> Perhaps call it "compact" or "terse" datestyle?

  ... and Peter suggested:
  http://archives.postgresql.org/pgsql-patches/2003-09/msg00129.php
  PE>
  PE> iso8601
  PE>
  PE> Keep in mind that SQL itself is also a kind of ISO, so being
  PE> more specific is useful.


 Regarding the non-orthogonality, I was suspecting that most
 applications that use ISO-8601 Basic Formats would use them
 consistently for dates (19990131) and intervals (P1Y1M).
 But I do see your point and agree this isn't a good solution.

 If the developers would like separate GUC variables for
 formatting dates vs intervals, I would be happy to do so.
 On the other hand, if the idea of outputting ISO-8601
 intervals is likely to be rejected anyway, I'd be happy
 to not do it too.  :-)

 Or, how would people feel instead about formatting functions
 to produce the various ISO-8601 formats?
    to_iso8601basic_char(timestamp)
    to_iso8601basic_char(interval)
 I think this could be especially useful since the docs:
  http://developer.postgresql.org/docs/postgres/functions-formatting.html
 say that "to_char(interval, text)" is being deprecated,
 meaning that converting intervals to formats other systems
 accept will soon become harder.


Personally, though, I'm most interested in the input side.

  I have an application that uses ISO-8601 Basic Format
  for all it's time information (Dates, Times, and Intervals), and
  wanted to load this information into PostgreSQL.  I was happy
  to see that Dates and Times loaded.

  Unfortunately intervals did not.

  A quick investigation showed that PostgreSQL currently has an
  undocumented shorthand is similar but frustratingly different
  from ISO-8601:
  (i.e. '1Y1M'::interval  means '1 year 1 minute' to PostgreSQL 7.3X,
  while 'P1Y1M'           means '1 year 1 month'  to ISO-8601).
  Even if nothing is done to the output side, allowing inputting
  of such intervals would benefit me.

  Would the developers prefer a patch allowing the inputting
  of such intervals, and not support outputting at all?


> BTW, I can tell without looking that the patch is deficient in
> documentation; if it has effects on GUC variables, why is there no
> mod in runtime.sgml?

Point well taken.  Before I submit any future patches I will try
to be more careful in this regard.

   Ron

PS: The spec I'm referring to is ISO-8601... Section 5.5.4.2
http://www.webaugur.com/bibliotheca/standards/iso8601/154N362/index-25.html


Re: [COMMITTERS] pgsql-server/ oc/src/sgml/datatype.sgml

From
Tom Lane
Date:
Ron Mayer <ron@cheapcomplexdevices.com> writes:
>   Would formatting functions for intervals such as...
>    to_iso8601basic_char(interval)  -- return ISO-8601 basic fmt interval
>    to_iso8601basic_char(timestamp) -- return ISO-8601 basic fmt date/time
>   would be better than a new GUC variable?

I could see arguments for both --- in the timestamp world we have
DateStyle to control the default output format, plus to_char when you
want something different.  I'd suggest continuing with your plan of
providing GUC control over the default interval format (just use a
separate GUC variable, please, for orthogonality).  If you want to add
an explicit formatting function later, you can do that too.

BTW, I do not recall exactly why Karel wants to deprecate
to_char(interval), but I don't think it's because of any fundamental
objection to the notion of a formatting function.  I think it was
because the present definition is badly designed and needs to be
replaced with a different API.

            regards, tom lane

Re: [COMMITTERS] pgsql-server/ oc/src/sgml/datatype.sgml

From
Bruce Momjian
Date:
Added to TODO:

   * Add GUC variable to allow output of interval values in ISO8601 format


---------------------------------------------------------------------------

Tom Lane wrote:
> Ron Mayer <ron@cheapcomplexdevices.com> writes:
> >   Would formatting functions for intervals such as...
> >    to_iso8601basic_char(interval)  -- return ISO-8601 basic fmt interval
> >    to_iso8601basic_char(timestamp) -- return ISO-8601 basic fmt date/time
> >   would be better than a new GUC variable?
>
> I could see arguments for both --- in the timestamp world we have
> DateStyle to control the default output format, plus to_char when you
> want something different.  I'd suggest continuing with your plan of
> providing GUC control over the default interval format (just use a
> separate GUC variable, please, for orthogonality).  If you want to add
> an explicit formatting function later, you can do that too.
>
> BTW, I do not recall exactly why Karel wants to deprecate
> to_char(interval), but I don't think it's because of any fundamental
> objection to the notion of a formatting function.  I think it was
> because the present definition is badly designed and needs to be
> replaced with a different API.
>
>             regards, tom lane
>
> ---------------------------(end of broadcast)---------------------------
> TIP 8: explain analyze is your friend
>

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073