From 560e2300b2ab395a367153e37d445fd584281cf7 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 17 Aug 2014 22:57:20 -0400 Subject: [PATCH] Use ISO 8601 format for dates converted to JSON, too. Commit f30015b6d794c15d52abbb3df3a65081fbefb1ed made this happen for timestamp and timestamptz, but it seems pretty inconsistent to not do it for simple dates as well. (In passing, I re-pgindent'd json.c.) --- doc/src/sgml/release-9.4.sgml | 7 +++-- src/backend/utils/adt/json.c | 55 +++++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/doc/src/sgml/release-9.4.sgml b/doc/src/sgml/release-9.4.sgml index 7ae67e0cec..5330c425d3 100644 --- a/doc/src/sgml/release-9.4.sgml +++ b/doc/src/sgml/release-9.4.sgml @@ -120,7 +120,7 @@ - When converting values of type timestamp + When converting values of type date, timestamp or timestamptz to JSON, render the values in a format compliant with ISO 8601 (Andrew Dunstan) @@ -129,7 +129,10 @@ Previously such values were rendered according to the current setting; but many JSON processors - require timestamps to be in ISO 8601 format. + require timestamps to be in ISO 8601 format. If necessary, the + previous behavior can be obtained by explicitly casting the datetime + value to text before passing it to the JSON conversion + function. diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 68132aea4b..494a028526 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -25,6 +25,7 @@ #include "parser/parse_coerce.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/date.h" #include "utils/datetime.h" #include "utils/lsyscache.h" #include "utils/json.h" @@ -55,8 +56,9 @@ typedef enum /* type categories for datum_to_json */ JSONTYPE_NULL, /* null, so we didn't bother to identify */ JSONTYPE_BOOL, /* boolean (built-in types only) */ JSONTYPE_NUMERIC, /* numeric (ditto) */ - JSONTYPE_TIMESTAMP, /* we use special formatting for timestamp */ - JSONTYPE_TIMESTAMPTZ, /* ... and timestamptz */ + JSONTYPE_DATE, /* we use special formatting for datetimes */ + JSONTYPE_TIMESTAMP, + JSONTYPE_TIMESTAMPTZ, JSONTYPE_JSON, /* JSON itself (and JSONB) */ JSONTYPE_ARRAY, /* array */ JSONTYPE_COMPOSITE, /* composite */ @@ -1267,6 +1269,10 @@ json_categorize_type(Oid typoid, *tcategory = JSONTYPE_NUMERIC; break; + case DATEOID: + *tcategory = JSONTYPE_DATE; + break; + case TIMESTAMPOID: *tcategory = JSONTYPE_TIMESTAMP; break; @@ -1348,7 +1354,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, tcategory == JSONTYPE_CAST)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("key value must be scalar, not array, composite, or json"))); + errmsg("key value must be scalar, not array, composite, or json"))); switch (tcategory) { @@ -1388,6 +1394,30 @@ datum_to_json(Datum val, bool is_null, StringInfo result, } pfree(outputstr); break; + case JSONTYPE_DATE: + { + DateADT date; + struct pg_tm tm; + char buf[MAXDATELEN + 1]; + + date = DatumGetDateADT(val); + + /* XSD doesn't support infinite values */ + if (DATE_NOT_FINITE(date)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range"), + errdetail("JSON does not support infinite date values."))); + else + { + j2date(date + POSTGRES_EPOCH_JDATE, + &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); + EncodeDateOnly(&tm, USE_XSD_DATES, buf); + } + + appendStringInfo(result, "\"%s\"", buf); + } + break; case JSONTYPE_TIMESTAMP: { Timestamp timestamp; @@ -1410,7 +1440,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - appendStringInfo(result,"\"%s\"",buf); + appendStringInfo(result, "\"%s\"", buf); } break; case JSONTYPE_TIMESTAMPTZ: @@ -1437,7 +1467,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - appendStringInfo(result,"\"%s\"",buf); + appendStringInfo(result, "\"%s\"", buf); } break; case JSONTYPE_JSON: @@ -2305,20 +2335,21 @@ escape_json(StringInfo buf, const char *str) appendStringInfoString(buf, "\\\""); break; case '\\': + /* * Unicode escapes are passed through as is. There is no * requirement that they denote a valid character in the * server encoding - indeed that is a big part of their * usefulness. * - * All we require is that they consist of \uXXXX where - * the Xs are hexadecimal digits. It is the responsibility - * of the caller of, say, to_json() to make sure that the - * unicode escape is valid. + * All we require is that they consist of \uXXXX where the Xs + * are hexadecimal digits. It is the responsibility of the + * caller of, say, to_json() to make sure that the unicode + * escape is valid. * - * In the case of a jsonb string value being escaped, the - * only unicode escape that should be present is \u0000, - * all the other unicode escapes will have been resolved. + * In the case of a jsonb string value being escaped, the only + * unicode escape that should be present is \u0000, all the + * other unicode escapes will have been resolved. */ if (p[1] == 'u' && isxdigit((unsigned char) p[2]) && -- 2.40.0