From ab14a73a6ca5cc4750f0e00a48bdc25a2293034a Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Tue, 3 Jun 2014 18:26:47 -0400 Subject: [PATCH] Use EncodeDateTime instead of to_char to render JSON timestamps. Per gripe from Peter Eisentraut and Tom Lane. The output is slightly different, but still ISO 8601 compliant: to_char doesn't output the minutes when time zone offset is an integer number of hours, while EncodeDateTime outputs ":00". The code is slightly adapted from code in xml.c --- src/backend/utils/adt/json.c | 77 ++++++++++++++++++---------- src/test/regress/expected/json.out | 6 +-- src/test/regress/expected/json_1.out | 6 +-- 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 8ca1ede83f..972a22f65e 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -21,10 +21,11 @@ #include "lib/stringinfo.h" #include "libpq/pqformat.h" #include "mb/pg_wchar.h" +#include "miscadmin.h" #include "parser/parse_coerce.h" #include "utils/array.h" #include "utils/builtins.h" -#include "utils/formatting.h" +#include "utils/datetime.h" #include "utils/lsyscache.h" #include "utils/json.h" #include "utils/jsonapi.h" @@ -63,13 +64,6 @@ typedef enum /* type categories for datum_to_json */ JSONTYPE_OTHER /* all else */ } JsonTypeCategory; -/* - * to_char formats to turn timestamps and timpstamptzs into json strings - * that are ISO 8601 compliant - */ -#define TS_ISO8601_FMT "\\\"YYYY-MM-DD\"T\"HH24:MI:SS.US\\\"" -#define TSTZ_ISO8601_FMT "\\\"YYYY-MM-DD\"T\"HH24:MI:SS.USOF\\\"" - static inline void json_lex(JsonLexContext *lex); static inline void json_lex_string(JsonLexContext *lex); static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err); @@ -1394,27 +1388,56 @@ datum_to_json(Datum val, bool is_null, StringInfo result, pfree(outputstr); break; case JSONTYPE_TIMESTAMP: - /* - * The timestamp format used here provides for quoting the string, - * so no escaping is required. - */ - jsontext = DatumGetTextP( - DirectFunctionCall2(timestamp_to_char, val, - CStringGetTextDatum(TS_ISO8601_FMT))); - outputstr = text_to_cstring(jsontext); - appendStringInfoString(result, outputstr); - pfree(outputstr); - pfree(jsontext); + { + Timestamp timestamp; + struct pg_tm tm; + fsec_t fsec; + char buf[MAXDATELEN + 1]; + + timestamp = DatumGetTimestamp(val); + + /* XSD doesn't support infinite values */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"), + errdetail("JSON does not support infinite timestamp values."))); + else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) + EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + appendStringInfo(result,"\"%s\"",buf); + } break; case JSONTYPE_TIMESTAMPTZ: - /* same comment as for timestamp above */ - jsontext = DatumGetTextP( - DirectFunctionCall2(timestamptz_to_char, val, - CStringGetTextDatum(TSTZ_ISO8601_FMT))); - outputstr = text_to_cstring(jsontext); - appendStringInfoString(result, outputstr); - pfree(outputstr); - pfree(jsontext); + { + TimestampTz timestamp; + struct pg_tm tm; + int tz; + fsec_t fsec; + const char *tzn = NULL; + char buf[MAXDATELEN + 1]; + + timestamp = DatumGetTimestamp(val); + + /* XSD doesn't support infinite values */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"), + errdetail("JSON does not support infinite timestamp values."))); + else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) + EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + appendStringInfo(result,"\"%s\"",buf); + } break; case JSONTYPE_JSON: /* JSON and JSONB output will already be escaped */ diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index 43341aa9bb..8b8556b2e0 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -420,9 +420,9 @@ select to_json(timestamptz '2014-05-28 12:22:35.614298-04'); SET LOCAL TIME ZONE -8; select to_json(timestamptz '2014-05-28 12:22:35.614298-04'); - to_json ---------------------------------- - "2014-05-28T08:22:35.614298-08" + to_json +------------------------------------ + "2014-05-28T08:22:35.614298-08:00" (1 row) COMMIT; diff --git a/src/test/regress/expected/json_1.out b/src/test/regress/expected/json_1.out index 953324637d..b32c3ed09f 100644 --- a/src/test/regress/expected/json_1.out +++ b/src/test/regress/expected/json_1.out @@ -420,9 +420,9 @@ select to_json(timestamptz '2014-05-28 12:22:35.614298-04'); SET LOCAL TIME ZONE -8; select to_json(timestamptz '2014-05-28 12:22:35.614298-04'); - to_json ---------------------------------- - "2014-05-28T08:22:35.614298-08" + to_json +------------------------------------ + "2014-05-28T08:22:35.614298-08:00" (1 row) COMMIT; -- 2.40.0