Commit 54c8e821 authored by Bruce Momjian's avatar Bruce Momjian

In my mind there were two categories of open issues

  a) ones that are 100% backward (such as the comment about
     outputting this format)
and
  b) ones that aren't (such as deprecating the current
     postgresql shorthand of
         '1Y1M'::interval = 1 year 1 minute
     in favor of the ISO-8601
         'P1Y1M'::interval = 1 year 1 month.

Attached is a patch that addressed all the discussed issues that
did not break backward compatability, including the ability to
output ISO-8601 compliant intervals by setting datestyle to
iso8601basic.

Interval values can now be written as  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.

Ron Mayer
parent 7be614a0
<!--
$PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.135 2003/12/01 22:07:55 momjian Exp $
$PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.136 2003/12/20 15:32:54 momjian Exp $
-->
<chapter id="datatype">
......@@ -1785,6 +1785,57 @@ January 8 04:05:06 1999 PST
<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,6 +1992,11 @@ January 8 04:05:06 1999 PST
<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>
......@@ -1997,6 +2053,11 @@ January 8 04:05:06 1999 PST
</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>
The date/time styles can be selected by the user using the
<command>SET datestyle</command> command, the
......
......@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.90 2003/11/29 19:51:48 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.91 2003/12/20 15:32:54 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -82,7 +82,12 @@ assign_datestyle(const char *value, bool doit, bool interactive)
/* Ugh. Somebody ought to write a table driven version -- mjl */
if (strcasecmp(tok, "ISO") == 0)
if (strcasecmp(tok, "ISO8601BASIC") == 0)
{
newDateStyle = USE_ISO8601BASIC_DATES;
scnt++;
}
else if (strcasecmp(tok, "ISO") == 0)
{
newDateStyle = USE_ISO_DATES;
scnt++;
......@@ -198,6 +203,9 @@ assign_datestyle(const char *value, bool doit, bool interactive)
case USE_ISO_DATES:
strcpy(result, "ISO");
break;
case USE_ISO8601BASIC_DATES:
strcpy(result, "ISO8601BASIC");
break;
case USE_SQL_DATES:
strcpy(result, "SQL");
break;
......
This diff is collapsed.
......@@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.138 2003/11/29 22:40:53 pgsql Exp $
* $PostgreSQL: pgsql/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,6 +150,8 @@ extern DLLIMPORT Oid MyDatabaseId;
* 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,6 +171,7 @@ extern DLLIMPORT Oid MyDatabaseId;
#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
......
......@@ -21,6 +21,7 @@ typedef double fsec_t;
#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"
......
......@@ -704,6 +704,16 @@ EncodeDateOnly(struct tm * tm, int style, char *str, bool EuroDates)
-(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)
......@@ -820,6 +830,51 @@ EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, cha
}
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");
/*
* 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 */
......
......@@ -442,6 +442,17 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse
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,6 +460,14 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse
* 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,7 +484,12 @@ EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
*/
switch (style)
{
/* compatible with ISO date formats */
/* 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)
{
......@@ -533,6 +557,48 @@ EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
}
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, "@ ");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment