Support for FF1-FF6 datetime format patterns
authorAlexander Korotkov <akorotkov@postgresql.org>
Mon, 16 Sep 2019 18:02:14 +0000 (21:02 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Mon, 16 Sep 2019 18:14:32 +0000 (21:14 +0300)
SQL Standard 2016 defines FF1-FF9 format patters for fractions of seconds in
jsonpath .datetime() method and CAST (... FORMAT ...) SQL clause.  Parsing
engine of upcoming .datetime() method will be shared with to_date()/
to_timestamp().

This patch implements FF1-FF6 format patterns for upcoming jsonpath .datetime()
method.  to_date()/to_timestamp() functions will also get support of this
format patterns as positive side effect.  FF7-FF9 are not supported due to
lack of precision in our internal timestamp representation.

Extracted from original patch by Nikita Glukhov, Teodor Sigaev, Oleg Bartunov.
Heavily revised by me.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Nikita Glukhov, Teodor Sigaev, Oleg Bartunov, Alexander Korotkov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
doc/src/sgml/func.sgml
src/backend/utils/adt/formatting.c
src/backend/utils/adt/timestamp.c
src/include/utils/datetime.h
src/test/regress/expected/horology.out
src/test/regress/expected/timestamp.out
src/test/regress/expected/timestamptz.out
src/test/regress/sql/horology.sql
src/test/regress/sql/timestamp.sql
src/test/regress/sql/timestamptz.sql

index cdf41ddef442579437b6675809854e26b4693836..9a61f27674741bbdb3bf3c04e11b79853c72b63f 100644 (file)
@@ -6150,6 +6150,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         <entry><literal>US</literal></entry>
         <entry>microsecond (000000-999999)</entry>
        </row>
+       <row>
+        <entry><literal>FF1</literal></entry>
+        <entry>tenth of second (0-9)</entry>
+       </row>
+       <row>
+        <entry><literal>FF2</literal></entry>
+        <entry>hundredth of second (00-99)</entry>
+       </row>
+       <row>
+        <entry><literal>FF3</literal></entry>
+        <entry>millisecond (000-999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF4</literal></entry>
+        <entry>tenth of a millisecond (0000-9999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF5</literal></entry>
+        <entry>hundredth of a millisecond (00000-99999)</entry>
+       </row>
+       <row>
+        <entry><literal>FF6</literal></entry>
+        <entry>microsecond (000000-999999)</entry>
+       </row>
        <row>
         <entry><literal>SSSS</literal></entry>
         <entry>seconds past midnight (0-86399)</entry>
index 755ca6e277ca70fb61804fec2576a744f7b4ab10..c6f992bfc1b8f68192ce8cda14e68ff8ca52bb32 100644 (file)
@@ -86,6 +86,7 @@
 #endif
 
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "mb/pg_wchar.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -434,7 +435,8 @@ typedef struct
                clock,          /* 12 or 24 hour clock? */
                tzsign,         /* +1, -1 or 0 if timezone info is absent */
                tzh,
-               tzm;
+               tzm,
+               ff;             /* fractional precision */
 } TmFromChar;
 
 #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -594,6 +596,12 @@ typedef enum
    DCH_Day,
    DCH_Dy,
    DCH_D,
+   DCH_FF1,
+   DCH_FF2,
+   DCH_FF3,
+   DCH_FF4,
+   DCH_FF5,
+   DCH_FF6,
    DCH_FX,                     /* global suffix */
    DCH_HH24,
    DCH_HH12,
@@ -643,6 +651,12 @@ typedef enum
    DCH_dd,
    DCH_dy,
    DCH_d,
+   DCH_ff1,
+   DCH_ff2,
+   DCH_ff3,
+   DCH_ff4,
+   DCH_ff5,
+   DCH_ff6,
    DCH_fx,
    DCH_hh24,
    DCH_hh12,
@@ -743,7 +757,13 @@ static const KeyWord DCH_keywords[] = {
    {"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
    {"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
    {"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-   {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},  /* F */
+   {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},    /* F */
+   {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+   {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+   {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+   {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+   {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+   {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
    {"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},   /* H */
    {"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
    {"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -792,7 +812,13 @@ static const KeyWord DCH_keywords[] = {
    {"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
    {"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
    {"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
-   {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},  /* f */
+   {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE},    /* f */
+   {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+   {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+   {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+   {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+   {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+   {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
    {"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},   /* h */
    {"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
    {"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -893,10 +919,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
-   DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+   DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
    DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
    -1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
-   DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+   DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
    -1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
    -1, DCH_y_yyy, -1, -1, -1, -1
 
@@ -960,7 +986,6 @@ typedef struct NUMProc
               *L_currency_symbol;
 } NUMProc;
 
-
 /* ----------
  * Functions
  * ----------
@@ -993,7 +1018,7 @@ static int from_char_parse_int(int *dest, char **src, FormatNode *node);
 static int seq_search(char *name, const char *const *array, int type, int max, int *len);
 static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
 static void do_to_timestamp(text *date_txt, text *fmt,
-                           struct pg_tm *tm, fsec_t *fsec);
+                           struct pg_tm *tm, fsec_t *fsec, int *fprec);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2518,18 +2543,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                    str_numth(s, s, S_TH_TYPE(n->suffix));
                s += strlen(s);
                break;
-           case DCH_MS:        /* millisecond */
-               sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
-               if (S_THth(n->suffix))
-                   str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+               sprintf(s, frac_fmt, (int) (frac_val)); \
+               if (S_THth(n->suffix)) \
+                   str_numth(s, s, S_TH_TYPE(n->suffix)); \
                s += strlen(s);
+           case DCH_FF1:       /* tenth of second */
+               DCH_to_char_fsec("%01d", in->fsec / 100000);
+               break;
+           case DCH_FF2:       /* hundredth of second */
+               DCH_to_char_fsec("%02d", in->fsec / 10000);
+               break;
+           case DCH_FF3:
+           case DCH_MS:        /* millisecond */
+               DCH_to_char_fsec("%03d", in->fsec / 1000);
                break;
+           case DCH_FF4:       /* tenth of a millisecond */
+               DCH_to_char_fsec("%04d", in->fsec / 100);
+               break;
+           case DCH_FF5:       /* hundredth of a millisecond */
+               DCH_to_char_fsec("%05d", in->fsec / 10);
+               break;
+           case DCH_FF6:
            case DCH_US:        /* microsecond */
-               sprintf(s, "%06d", (int) in->fsec);
-               if (S_THth(n->suffix))
-                   str_numth(s, s, S_TH_TYPE(n->suffix));
-               s += strlen(s);
+               DCH_to_char_fsec("%06d", in->fsec);
                break;
+#undef DCH_to_char_fsec
            case DCH_SSSS:
                sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
                        tm->tm_min * SECS_PER_MINUTE +
@@ -3154,8 +3193,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 
                SKIP_THth(s, n->suffix);
                break;
+           case DCH_FF1:
+           case DCH_FF2:
+           case DCH_FF3:
+           case DCH_FF4:
+           case DCH_FF5:
+           case DCH_FF6:
+               out->ff = n->key->id - DCH_FF1 + 1;
+               /* fall through */
            case DCH_US:        /* microsecond */
-               len = from_char_parse_int_len(&out->us, &s, 6, n);
+               len = from_char_parse_int_len(&out->us, &s,
+                                             n->key->id == DCH_US ? 6 :
+                                             out->ff, n);
 
                out->us *= len == 1 ? 100000 :
                    len == 2 ? 10000 :
@@ -3689,8 +3738,9 @@ to_timestamp(PG_FUNCTION_ARGS)
    int         tz;
    struct pg_tm tm;
    fsec_t      fsec;
+   int         fprec;
 
-   do_to_timestamp(date_txt, fmt, &tm, &fsec);
+   do_to_timestamp(date_txt, fmt, &tm, &fsec, &fprec);
 
    /* Use the specified time zone, if any. */
    if (tm.tm_zone)
@@ -3708,6 +3758,10 @@ to_timestamp(PG_FUNCTION_ARGS)
                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                 errmsg("timestamp out of range")));
 
+   /* Use the specified fractional precision, if any. */
+   if (fprec)
+       AdjustTimestampForTypmod(&result, fprec);
+
    PG_RETURN_TIMESTAMP(result);
 }
 
@@ -3725,7 +3779,7 @@ to_date(PG_FUNCTION_ARGS)
    struct pg_tm tm;
    fsec_t      fsec;
 
-   do_to_timestamp(date_txt, fmt, &tm, &fsec);
+   do_to_timestamp(date_txt, fmt, &tm, &fsec, NULL);
 
    /* Prevent overflow in Julian-day routines */
    if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3749,8 +3803,8 @@ to_date(PG_FUNCTION_ARGS)
 /*
  * do_to_timestamp: shared code for to_timestamp and to_date
  *
- * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm,
+ * fractional seconds, and fractional precision.
  *
  * We parse 'fmt' into a list of FormatNodes, which is then passed to
  * DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3761,7 +3815,7 @@ to_date(PG_FUNCTION_ARGS)
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt,
-               struct pg_tm *tm, fsec_t *fsec)
+               struct pg_tm *tm, fsec_t *fsec, int *fprec)
 {
    FormatNode *format;
    TmFromChar  tmfc;
@@ -3817,6 +3871,7 @@ do_to_timestamp(text *date_txt, text *fmt,
        DCH_from_char(format, date_str, &tmfc);
 
        pfree(fmt_str);
+
        if (!incache)
            pfree(format);
    }
@@ -3998,6 +4053,8 @@ do_to_timestamp(text *date_txt, text *fmt,
        *fsec += tmfc.ms * 1000;
    if (tmfc.us)
        *fsec += tmfc.us;
+   if (fprec)
+       *fprec = tmfc.ff;       /* fractional precision, if specified */
 
    /* Range-check date fields according to bit mask computed above */
    if (fmask != 0)
index 5861ffbbc970c6630bbc3951723ccd7c16e34440..2931bd58c0df85e410524a533645df775126296d 100644 (file)
@@ -70,7 +70,6 @@ typedef struct
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
 static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -333,7 +332,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
  * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-static void
+void
 AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 {
    static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
index b8a199cddedf569542d1c5f03f8e5103bcc907bf..0cafdd26538e46f5b11d8bf64ed23b37dbe1bd00 100644 (file)
@@ -336,4 +336,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
                                                   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
 #endif                         /* DATETIME_H */
index b2b45773339d7d28851381d223a933fbc53e0e27..74ecb7c10e678fa10cb3e2cf5a9c9390d94c4be3 100644 (file)
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
  Sun Dec 18 03:18:00 2011 PST
 (1 row)
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |         to_timestamp         
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp          
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |          to_timestamp           
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp           
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |           to_timestamp            
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp            
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i |            to_timestamp             
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR:  date/time field value out of range: "2018-11-02 12:34:56.123456789"
 --
 -- Check handling of multiple spaces in format and/or input
 --
index 715680e33025fdf9d6aa67e124d0d9c29cdb082c..f772b07d5a45cb260bb556a931e76b820a869cc1 100644 (file)
@@ -1584,6 +1584,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
         make_timestamp        
index 5551fa6610eabc60692aaf1bc312429353e2492c..2d6a71ca64b56ad8232e2767f82f7c5d769770e1 100644 (file)
@@ -1704,6 +1704,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (66 rows)
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+ to_char_12 |                              to_char                               
+------------+--------------------------------------------------------------------
+            | 0 00 000 0000 00000 000000  0 00 000 0000 00000 000000  000 000000
+            | 7 78 780 7800 78000 780000  7 78 780 7800 78000 780000  780 780000
+            | 7 78 789 7890 78901 789010  7 78 789 7890 78901 789010  789 789010
+            | 7 78 789 7890 78901 789012  7 78 789 7890 78901 789012  789 789012
+(4 rows)
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
index e356dd563ee5896598f78a01073faaa95e6d7bb4..3c8580397ac3c73c092db5111a7ce8deaf135b1b 100644 (file)
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
 SELECT to_timestamp('2011-12-18 11:38 20',     'YYYY-MM-DD HH12:MI TZM');
 
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
 --
 -- Check handling of multiple spaces in format and/or input
 --
index 031b22bc3c16daa5dc76359850e6ce31c4d1d425..329987f7eaa85cf1bc794e10e7de6f7bf63a4724 100644 (file)
@@ -224,5 +224,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamp),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
index 28c76d6b72c87bfdb5c074715de414aa677a5b71..f5fee639a01b1b27a6074f150dcbad8b4d3015c4 100644 (file)
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMPTZ_TBL;
 
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6  ff1 ff2 ff3 ff4 ff5 ff6  MS US')
+   FROM (VALUES
+       ('2018-11-02 12:34:56'::timestamptz),
+       ('2018-11-02 12:34:56.78'),
+       ('2018-11-02 12:34:56.78901'),
+       ('2018-11-02 12:34:56.78901234')
+   ) d(d);
+
 -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
 SET timezone = '00:00';
 SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";