Index: doc/src/sgml/datatype.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/datatype.sgml,v
retrieving revision 1.131
diff -u -r1.131 datatype.sgml
--- doc/src/sgml/datatype.sgml 16 Nov 2003 20:29:16 -0000 1.131
+++ doc/src/sgml/datatype.sgml 1 Dec 2003 20:45:58 -0000
@@ -1772,6 +1772,57 @@
p should be between 0 and 6, and
defaults to the precision of the input literal.
+
+
+
+ Alternatively, interval values can 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.
+
+
+
+ Format: PnYnMnDTnHnMnS
+
+
+ In this format, 'n'> gets replaced by a number, and
+ Y> represents years,
+ M> (in the date part) months,
+ D> months,
+ H> hours,
+ M> (in the time part) minutes,
+ and S> seconds.
+
+
+
+
+ Interval Example
+
+
+
+ Traditional
+ ISO-8601 time-interval
+
+
+
+
+ 1 month
+ P1M
+
+
+ 1 hour 30 minutes
+ PT1H30M
+
+
+ 2 years 10 months 15 days 10 hours 30 minutes 20 seconds
+ P2Y10M15DT10H30M20S
+
+
+
+
+
+
@@ -1928,6 +1979,11 @@
regional style
17.12.1997 07:37:16.00 PST
+
+ ISO8601basic
+ ISO 8601 basic format
+ 19971217T073716-08
+
@@ -1982,6 +2038,11 @@
quantity> unit> ... > > days> > hours>:minutes>:sekunden>
+
+
+
+ If the datestyle> is set to iso8601basic, the interval
+ output is a ISO-8601 time interval with time-unit designator (like P1Y6M or PT23H59M59S).
Index: src/backend/commands/variable.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/commands/variable.c,v
retrieving revision 1.89
diff -u -r1.89 variable.c
--- src/backend/commands/variable.c 6 Nov 2003 22:08:14 -0000 1.89
+++ src/backend/commands/variable.c 1 Dec 2003 20:45:59 -0000
@@ -82,7 +82,12 @@
/* 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++;
@@ -197,6 +202,9 @@
{
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: /projects/cvsroot/pgsql-server/src/backend/utils/adt/datetime.c,v
retrieving revision 1.119
diff -u -r1.119 datetime.c
--- src/backend/utils/adt/datetime.c 16 Nov 2003 20:29:16 -0000 1.119
+++ src/backend/utils/adt/datetime.c 1 Dec 2003 20:46:02 -0000
@@ -37,6 +37,7 @@
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,6 +2889,246 @@
}
+/*
+ * 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,7 +3138,11 @@
*
* 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,6 +3167,23 @@
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,6 +3261,7 @@
if (type == IGNORE_DTF)
type = DTK_SECOND;
+ /* should this allow ',' for locales that use it ? */
if (*cp == '.')
{
fval = strtod(cp, &cp);
@@ -3370,6 +3633,16 @@
-(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,6 +3798,51 @@
}
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,6 +4006,15 @@
} /* 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,6 +4022,14 @@
* 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,6 +4112,48 @@
}
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,7 +4278,7 @@
}
/* identically zero? then put in a unitless zero... */
- if (!is_nonzero)
+ if (!is_nonzero && (style!=USE_ISO8601BASIC_DATES))
{
strcat(cp, "0");
cp += strlen(cp);
Index: src/include/miscadmin.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/miscadmin.h,v
retrieving revision 1.137
diff -u -r1.137 miscadmin.h
--- src/include/miscadmin.h 13 Nov 2003 14:57:15 -0000 1.137
+++ src/include/miscadmin.h 1 Dec 2003 20:46:04 -0000
@@ -150,6 +150,8 @@
* 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 @@
#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: /projects/cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/dt.h,v
retrieving revision 1.14
diff -u -r1.14 dt.h
--- src/interfaces/ecpg/pgtypeslib/dt.h 5 Oct 2003 11:12:00 -0000 1.14
+++ src/interfaces/ecpg/pgtypeslib/dt.h 1 Dec 2003 20:46:04 -0000
@@ -21,6 +21,7 @@
#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: /projects/cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/dt_common.c,v
retrieving revision 1.12
diff -u -r1.12 dt_common.c
--- src/interfaces/ecpg/pgtypeslib/dt_common.c 5 Oct 2003 11:12:00 -0000 1.12
+++ src/interfaces/ecpg/pgtypeslib/dt_common.c 1 Dec 2003 20:46:05 -0000
@@ -704,6 +704,16 @@
-(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,6 +812,51 @@
}
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: /projects/cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/interval.c,v
retrieving revision 1.7
diff -u -r1.7 interval.c
--- src/interfaces/ecpg/pgtypeslib/interval.c 3 Oct 2003 10:07:28 -0000 1.7
+++ src/interfaces/ecpg/pgtypeslib/interval.c 1 Dec 2003 20:46:05 -0000
@@ -442,6 +442,17 @@
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 @@
* 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 @@
*/
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)
{
@@ -530,6 +554,48 @@
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;