diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index 16b17dbe9f29..6854d9e8e720 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -626,7 +626,7 @@ func Example_sql_format() { // INSERT 1 // sql -e select * from t.times // bare withtz - // 2016-01-25 10:10:10+00:00 2016-01-25 15:10:10+00:00 + // 2016-01-25 10:10:10+00:00:00 2016-01-25 15:10:10+00:00:00 } func Example_sql_column_labels() { diff --git a/pkg/cli/sql_util.go b/pkg/cli/sql_util.go index 8adc9bf04c8d..81f8129f993e 100644 --- a/pkg/cli/sql_util.go +++ b/pkg/cli/sql_util.go @@ -1159,7 +1159,7 @@ func formatVal(val driver.Value, showPrintableUnicode bool, showNewLinesAndTabs case time.Time: // Since we do not know whether the datum is Timestamp or TimestampTZ, // output the full format. - return t.Format(tree.TimestampTZOutputFormat) + return t.Format(timeutil.FullTimeFormat) } return fmt.Sprint(val) diff --git a/pkg/internal/sqlsmith/bulkio.go b/pkg/internal/sqlsmith/bulkio.go index 0a038cb7a188..441507e9862f 100644 --- a/pkg/internal/sqlsmith/bulkio.go +++ b/pkg/internal/sqlsmith/bulkio.go @@ -71,7 +71,7 @@ func makeAsOf(s *Smither) tree.AsOfClause { case 1: expr = tree.NewStrVal("-2s") case 2: - expr = tree.NewStrVal(timeutil.Now().Add(-2 * time.Second).Format(tree.TimestampTZOutputFormat)) + expr = tree.NewStrVal(timeutil.Now().Add(-2 * time.Second).Format(timeutil.FullTimeFormat)) case 3: expr = rowenc.RandDatum(s.rnd, types.Interval, false /* nullOk */) case 4: diff --git a/pkg/sql/logictest/testdata/logic_test/timestamp b/pkg/sql/logictest/testdata/logic_test/timestamp index 9fd34c421829..d48996fc7120 100644 --- a/pkg/sql/logictest/testdata/logic_test/timestamp +++ b/pkg/sql/logictest/testdata/logic_test/timestamp @@ -272,6 +272,11 @@ subtest regression_django-cockroachdb_120 statement ok SET TIME ZONE 'America/Chicago' +query T +SELECT '1882-05-23T00:00:00-05:51'::timestamptz::text +---- +1882-05-23 00:00:24-05:50:24 + query B SELECT '2011-03-13'::date = '2011-03-13'::timestamp ---- @@ -302,6 +307,14 @@ SELECT '2011-03-14'::timestamp = '2011-03-14'::timestamptz ---- true +statement ok +SET TIME ZONE '-00:10:15' + +query T +SELECT '1882-05-23T00:00:00-05:51'::timestamptz::text +---- +1882-05-23 06:01:15+00:10:15 + statement ok SET TIME ZONE 0 @@ -402,8 +415,8 @@ SELECT FROM example ORDER BY a ---- -2010-11-07 22:59:00 -0600 CST 2010-11-07 23:59:00 -0600 CST 2010-12-06 23:59:00 -0600 CST 2010-11-05 23:59:00 -0500 CDT 2010-11-05 23:59:00 -0500 CDT 2010-10-06 23:59:00 -0500 CDT 00:00:00 -1 days -01:00:00 2010-11-06 23:59:00-05:00 -2010-11-08 23:59:00 -0600 CST 2010-11-08 23:59:00 -0600 CST 2010-12-07 23:59:00 -0600 CST 2010-11-07 00:59:00 -0500 CDT 2010-11-06 23:59:00 -0500 CDT 2010-10-07 23:59:00 -0500 CDT 1 day 01:00:00 00:00:00 2010-11-07 23:59:00-06:00 +2010-11-07 22:59:00 -0600 CST 2010-11-07 23:59:00 -0600 CST 2010-12-06 23:59:00 -0600 CST 2010-11-05 23:59:00 -0500 CDT 2010-11-05 23:59:00 -0500 CDT 2010-10-06 23:59:00 -0500 CDT 00:00:00 -1 days -01:00:00 2010-11-06 23:59:00-05:00 +2010-11-08 23:59:00 -0600 CST 2010-11-08 23:59:00 -0600 CST 2010-12-07 23:59:00 -0600 CST 2010-11-07 00:59:00 -0500 CDT 2010-11-06 23:59:00 -0500 CDT 2010-10-07 23:59:00 -0500 CDT 1 day 01:00:00 00:00:00 2010-11-07 23:59:00-06:00 statement ok DROP TABLE example diff --git a/pkg/sql/pgwire/testdata/pgtest/timestamptz b/pkg/sql/pgwire/testdata/pgtest/timestamptz new file mode 100644 index 000000000000..adab337e2f3b --- /dev/null +++ b/pkg/sql/pgwire/testdata/pgtest/timestamptz @@ -0,0 +1,22 @@ +send +Query {"String": "SET TIME ZONE \"America/Chicago\""} +---- + +until +ReadyForQuery +---- +{"Type":"ParameterStatus","Name":"TimeZone","Value":"America/Chicago"} +{"Type":"CommandComplete","CommandTag":"SET"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send +Query {"String": "SELECT '1882-05-23T00:00:00-05:51'::\"timestamptz\""} +---- + +until +ReadyForQuery +---- +{"Type":"RowDescription","Fields":[{"Name":"timestamptz","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":1184,"DataTypeSize":24,"TypeModifier":-1,"Format":0}]} +{"Type":"DataRow","Values":[{"text":"1882-05-23 00:00:24-05:50:36"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 1"} +{"Type":"ReadyForQuery","TxStatus":"I"} diff --git a/pkg/sql/pgwire/types.go b/pkg/sql/pgwire/types.go index 71f72bda4b34..919f359c7afe 100644 --- a/pkg/sql/pgwire/types.go +++ b/pkg/sql/pgwire/types.go @@ -526,7 +526,7 @@ const ( pgTimeTZFormat = pgTimeFormat + "-07:00" pgDateFormat = "2006-01-02" pgTimeStampFormatNoOffset = pgDateFormat + " " + pgTimeFormat - pgTimeStampFormat = pgTimeStampFormatNoOffset + "-07:00" + pgTimeStampFormat = pgTimeStampFormatNoOffset + "-07:00:00" pgTime2400Format = "24:00:00" ) diff --git a/pkg/sql/sem/tree/datum.go b/pkg/sql/sem/tree/datum.go index 1f96f24bcbaa..1aa5730ced0d 100644 --- a/pkg/sql/sem/tree/datum.go +++ b/pkg/sql/sem/tree/datum.go @@ -2162,10 +2162,12 @@ var dZeroTimestamp = &DTimestamp{} // time.Time formats. const ( - // TimestampTZOutputFormat is used to output all TimestampTZs. - TimestampTZOutputFormat = "2006-01-02 15:04:05.999999-07:00" - // TimestampOutputFormat is used to output all Timestamps. - TimestampOutputFormat = "2006-01-02 15:04:05.999999" + // timestampTZOutputFormat is used to output all TimestampTZs. + // Note the second offset is missing here -- this is to maintain + // backward compatibility with casting timestamptz to strings. + timestampTZOutputFormat = "2006-01-02 15:04:05.999999-07:00" + // timestampOutputFormat is used to output all Timestamps. + timestampOutputFormat = "2006-01-02 15:04:05.999999" ) // ParseDTimestamp parses and returns the *DTimestamp Datum value represented by @@ -2352,7 +2354,7 @@ func (d *DTimestamp) Format(ctx *FmtCtx) { if !bareStrings { ctx.WriteByte('\'') } - ctx.WriteString(d.UTC().Format(TimestampOutputFormat)) + ctx.WriteString(d.UTC().Format(timestampOutputFormat)) if !bareStrings { ctx.WriteByte('\'') } @@ -2509,7 +2511,18 @@ func (d *DTimestampTZ) Format(ctx *FmtCtx) { if !bareStrings { ctx.WriteByte('\'') } - ctx.WriteString(d.Time.Format(TimestampTZOutputFormat)) + ctx.WriteString(d.Time.Format(timestampTZOutputFormat)) + _, offsetSecs := d.Time.Zone() + // Only output remaining seconds offsets if it is available. + // This is to maintain backward compatibility with older CRDB versions, + // where we only output HH:MM. + if secondOffset := offsetSecs % 60; secondOffset != 0 { + if secondOffset < 0 { + secondOffset = 60 + secondOffset + } + ctx.WriteByte(':') + ctx.WriteString(fmt.Sprintf("%02d", secondOffset)) + } if !bareStrings { ctx.WriteByte('\'') } diff --git a/pkg/util/timeutil/timeutil.go b/pkg/util/timeutil/timeutil.go new file mode 100644 index 000000000000..3548a8a14620 --- /dev/null +++ b/pkg/util/timeutil/timeutil.go @@ -0,0 +1,15 @@ +// Copyright 2020 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package timeutil + +// FullTimeFormat is the time format used to display any timestamp +// with date, time and time zone data. +const FullTimeFormat = "2006-01-02 15:04:05.999999-07:00:00"