mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
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
This commit is contained in:
parent
7be614a087
commit
54c8e821b8
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$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">
|
<chapter id="datatype">
|
||||||
@ -1785,6 +1785,57 @@ January 8 04:05:06 1999 PST
|
|||||||
<replaceable>p</replaceable> should be between 0 and 6, and
|
<replaceable>p</replaceable> should be between 0 and 6, and
|
||||||
defaults to the precision of the input literal.
|
defaults to the precision of the input literal.
|
||||||
</para>
|
</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>
|
||||||
|
|
||||||
<sect3>
|
<sect3>
|
||||||
@ -1941,6 +1992,11 @@ January 8 04:05:06 1999 PST
|
|||||||
<entry>regional style</entry>
|
<entry>regional style</entry>
|
||||||
<entry>17.12.1997 07:37:16.00 PST</entry>
|
<entry>17.12.1997 07:37:16.00 PST</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>ISO8601basic</entry>
|
||||||
|
<entry>ISO 8601 basic format</entry>
|
||||||
|
<entry>19971217T073716-08</entry>
|
||||||
|
</row>
|
||||||
</tbody>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
@ -1997,6 +2053,11 @@ January 8 04:05:06 1999 PST
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</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>
|
<para>
|
||||||
The date/time styles can be selected by the user using the
|
The date/time styles can be selected by the user using the
|
||||||
<command>SET datestyle</command> command, the
|
<command>SET datestyle</command> command, the
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 */
|
/* 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;
|
newDateStyle = USE_ISO_DATES;
|
||||||
scnt++;
|
scnt++;
|
||||||
@ -198,6 +203,9 @@ assign_datestyle(const char *value, bool doit, bool interactive)
|
|||||||
case USE_ISO_DATES:
|
case USE_ISO_DATES:
|
||||||
strcpy(result, "ISO");
|
strcpy(result, "ISO");
|
||||||
break;
|
break;
|
||||||
|
case USE_ISO8601BASIC_DATES:
|
||||||
|
strcpy(result, "ISO8601BASIC");
|
||||||
|
break;
|
||||||
case USE_SQL_DATES:
|
case USE_SQL_DATES:
|
||||||
strcpy(result, "SQL");
|
strcpy(result, "SQL");
|
||||||
break;
|
break;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.121 2003/12/17 21:45:44 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.122 2003/12/20 15:32:54 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -37,6 +37,7 @@ static int DecodeTimezone(char *str, int *tzp);
|
|||||||
static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
|
static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
|
||||||
static int DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
|
static int DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
|
||||||
static void TrimTrailingZeros(char *str);
|
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] = {
|
int day_tab[2][13] = {
|
||||||
@ -2888,6 +2889,246 @@ DecodeSpecial(int field, char *lowtoken, int *val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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()
|
/* DecodeInterval()
|
||||||
* Interpret previously parsed fields for general time interval.
|
* Interpret previously parsed fields for general time interval.
|
||||||
* Returns 0 if successful, DTERR code if bogus input detected.
|
* Returns 0 if successful, DTERR code if bogus input detected.
|
||||||
@ -2897,7 +3138,11 @@ DecodeSpecial(int field, char *lowtoken, int *val)
|
|||||||
*
|
*
|
||||||
* Allow ISO-style time span, with implicit units on number of days
|
* Allow ISO-style time span, with implicit units on number of days
|
||||||
* preceding an hh:mm:ss field. - thomas 1998-04-30
|
* 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
|
int
|
||||||
DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec)
|
DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec)
|
||||||
{
|
{
|
||||||
@ -2922,6 +3167,23 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse
|
|||||||
tm->tm_sec = 0;
|
tm->tm_sec = 0;
|
||||||
*fsec = 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 */
|
/* read through list backwards to pick up units before values */
|
||||||
for (i = nf - 1; i >= 0; i--)
|
for (i = nf - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
@ -2999,6 +3261,7 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse
|
|||||||
if (type == IGNORE_DTF)
|
if (type == IGNORE_DTF)
|
||||||
type = DTK_SECOND;
|
type = DTK_SECOND;
|
||||||
|
|
||||||
|
/* should this allow ',' for locales that use it ? */
|
||||||
if (*cp == '.')
|
if (*cp == '.')
|
||||||
{
|
{
|
||||||
fval = strtod(cp, &cp);
|
fval = strtod(cp, &cp);
|
||||||
@ -3370,6 +3633,16 @@ EncodeDateOnly(struct tm * tm, int style, char *str)
|
|||||||
-(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
|
-(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
|
||||||
break;
|
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:
|
case USE_SQL_DATES:
|
||||||
/* compatible with Oracle/Ingres date formats */
|
/* compatible with Oracle/Ingres date formats */
|
||||||
if (DateOrder == DATEORDER_DMY)
|
if (DateOrder == DATEORDER_DMY)
|
||||||
@ -3525,6 +3798,51 @@ EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, cha
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case USE_SQL_DATES:
|
||||||
/* Compatible with Oracle/Ingres date formats */
|
/* Compatible with Oracle/Ingres date formats */
|
||||||
|
|
||||||
@ -3688,6 +4006,15 @@ EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, cha
|
|||||||
} /* EncodeDateTime() */
|
} /* 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()
|
/* EncodeInterval()
|
||||||
* Interpret time structure as a delta time and convert to string.
|
* Interpret time structure as a delta time and convert to string.
|
||||||
*
|
*
|
||||||
@ -3695,6 +4022,14 @@ EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, cha
|
|||||||
* Actually, afaik ISO does not address time interval formatting,
|
* Actually, afaik ISO does not address time interval formatting,
|
||||||
* but this looks similar to the spec for absolute date/time.
|
* but this looks similar to the spec for absolute date/time.
|
||||||
* - thomas 1998-04-30
|
* - 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
|
int
|
||||||
EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
|
EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
|
||||||
@ -3777,6 +4112,48 @@ EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case USE_POSTGRES_DATES:
|
||||||
default:
|
default:
|
||||||
strcpy(cp, "@ ");
|
strcpy(cp, "@ ");
|
||||||
@ -3901,7 +4278,7 @@ EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* identically zero? then put in a unitless zero... */
|
/* identically zero? then put in a unitless zero... */
|
||||||
if (!is_nonzero)
|
if (!is_nonzero && (style!=USE_ISO8601BASIC_DATES))
|
||||||
{
|
{
|
||||||
strcat(cp, "0");
|
strcat(cp, "0");
|
||||||
cp += strlen(cp);
|
cp += strlen(cp);
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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
|
* NOTES
|
||||||
* some of the information in this file should be moved to
|
* 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_ISO_DATES specifies ISO-compliant format
|
||||||
* USE_SQL_DATES specifies Oracle/Ingres-compliant format
|
* USE_SQL_DATES specifies Oracle/Ingres-compliant format
|
||||||
* USE_GERMAN_DATES specifies German-style dd.mm/yyyy
|
* 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
|
* DateOrder defines the field order to be assumed when reading an
|
||||||
* ambiguous date (anything not in YYYY-MM-DD format, with a four-digit
|
* 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_ISO_DATES 1
|
||||||
#define USE_SQL_DATES 2
|
#define USE_SQL_DATES 2
|
||||||
#define USE_GERMAN_DATES 3
|
#define USE_GERMAN_DATES 3
|
||||||
|
#define USE_ISO8601BASIC_DATES 4
|
||||||
|
|
||||||
/* valid DateOrder values */
|
/* valid DateOrder values */
|
||||||
#define DATEORDER_YMD 0
|
#define DATEORDER_YMD 0
|
||||||
|
@ -21,6 +21,7 @@ typedef double fsec_t;
|
|||||||
#define USE_ISO_DATES 1
|
#define USE_ISO_DATES 1
|
||||||
#define USE_SQL_DATES 2
|
#define USE_SQL_DATES 2
|
||||||
#define USE_GERMAN_DATES 3
|
#define USE_GERMAN_DATES 3
|
||||||
|
#define USE_ISO8601BASIC_DATES 4
|
||||||
|
|
||||||
#define DAGO "ago"
|
#define DAGO "ago"
|
||||||
#define EPOCH "epoch"
|
#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");
|
-(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
|
||||||
break;
|
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:
|
case USE_SQL_DATES:
|
||||||
/* compatible with Oracle/Ingres date formats */
|
/* compatible with Oracle/Ingres date formats */
|
||||||
if (EuroDates)
|
if (EuroDates)
|
||||||
@ -820,6 +830,51 @@ EncodeDateTime(struct tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, cha
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case USE_SQL_DATES:
|
||||||
/* Compatible with Oracle/Ingres date formats */
|
/* 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;
|
return (fmask != 0) ? 0 : -1;
|
||||||
} /* DecodeInterval() */
|
} /* 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()
|
/* EncodeInterval()
|
||||||
* Interpret time structure as a delta time and convert to string.
|
* 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,
|
* Actually, afaik ISO does not address time interval formatting,
|
||||||
* but this looks similar to the spec for absolute date/time.
|
* but this looks similar to the spec for absolute date/time.
|
||||||
* - thomas 1998-04-30
|
* - 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
|
int
|
||||||
EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
|
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)
|
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:
|
case USE_ISO_DATES:
|
||||||
if (tm->tm_year != 0)
|
if (tm->tm_year != 0)
|
||||||
{
|
{
|
||||||
@ -533,6 +557,48 @@ EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str)
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case USE_POSTGRES_DATES:
|
||||||
default:
|
default:
|
||||||
strcpy(cp, "@ ");
|
strcpy(cp, "@ ");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user