From 8208796dc4fe35d7e17f1674d05c47cef5863e6f Mon Sep 17 00:00:00 2001 From: Vivek Menezes Date: Tue, 6 Oct 2015 21:55:53 -0400 Subject: [PATCH] Use time zone when casting date to timestamp. Fix current_date. --- sql/executor.go | 2 +- sql/parser/builtins.go | 2 +- sql/parser/datum.go | 9 +-------- sql/parser/eval.go | 29 +++++++++++++++++++---------- sql/table.go | 4 ++-- sql/testdata/datetime | 12 ++++++++++++ 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/sql/executor.go b/sql/executor.go index ae0176834da2..bd54cd8092ac 100644 --- a/sql/executor.go +++ b/sql/executor.go @@ -337,7 +337,7 @@ func (p parameters) Arg(name string) (parser.Datum, bool) { case *driver.Datum_StringVal: return parser.DString(t.StringVal), true case *driver.Datum_DateVal: - return parser.MakeDDate(t.DateVal.GoTime()), true + return parser.DDate{Time: t.DateVal.GoTime()}, true case *driver.Datum_TimeVal: return parser.DTimestamp{Time: t.TimeVal.GoTime()}, true case *driver.Datum_IntervalVal: diff --git a/sql/parser/builtins.go b/sql/parser/builtins.go index d5cba1b40d50..6fca48851632 100644 --- a/sql/parser/builtins.go +++ b/sql/parser/builtins.go @@ -454,7 +454,7 @@ var builtins = map[string][]builtin{ types: typeList{}, returnType: DummyDate, fn: func(e EvalContext, args DTuple) (Datum, error) { - return MakeDDate(e.StmtTimestamp.Time), nil + return e.makeDDate(e.StmtTimestamp.Time) }, }, }, diff --git a/sql/parser/datum.go b/sql/parser/datum.go index 465990168d4c..ed82cc14ee21 100644 --- a/sql/parser/datum.go +++ b/sql/parser/datum.go @@ -338,12 +338,6 @@ type DDate struct { time.Time // Must always be UTC! } -// MakeDDate constructs a DDate from a time.Time. -func MakeDDate(t time.Time) DDate { - year, month, day := t.Date() - return DDate{Time: time.Date(year, month, day, 0, 0, 0, 0, time.UTC)} -} - // Type implements the Datum interface. func (d DDate) Type() string { return "date" @@ -435,9 +429,8 @@ func (d DTimestamp) IsMin() bool { return d.Before(d.Add(-1)) } -// TODO:(vivek) implement SET TIME ZONE to improve presentation. func (d DTimestamp) String() string { - return d.Format(TimestampWithOffsetZoneFormat) + return d.UTC().Format(TimestampWithOffsetZoneFormat) } // DInterval is the interval Datum. diff --git a/sql/parser/eval.go b/sql/parser/eval.go index f8a1479ad3d2..a23978e0ac6a 100644 --- a/sql/parser/eval.go +++ b/sql/parser/eval.go @@ -566,6 +566,16 @@ var defaultContext = EvalContext{ }, } +// makeDDate constructs a DDate from a time.Time in the session time zone. +func (ctx EvalContext) makeDDate(t time.Time) (Datum, error) { + loc, err := ctx.GetLocation() + if err != nil { + return DNull, err + } + year, month, day := t.In(loc).Date() + return DDate{Time: time.Date(year, month, day, 0, 0, 0, 0, time.UTC)}, nil +} + // EvalExpr evaluates an SQL expression. Expression evaluation is a mostly // straightforward walk over the parse tree. The only significant complexity is // the handling of types and implicit conversions. See binOps and cmpOps for @@ -1133,11 +1143,7 @@ func (ctx EvalContext) evalCastExpr(expr *CastExpr) (Datum, error) { case DString: return ParseDate(d) case DTimestamp: - loc, err := ctx.GetLocation() - if err != nil { - return DNull, err - } - return MakeDDate(d.Time.In(loc)), nil + return ctx.makeDDate(d.Time) } case *TimestampType: @@ -1145,7 +1151,12 @@ func (ctx EvalContext) evalCastExpr(expr *CastExpr) (Datum, error) { case DString: return ctx.ParseTimestamp(d) case DDate: - return DTimestamp{Time: d.Time}, nil + loc, err := ctx.GetLocation() + if err != nil { + return DNull, err + } + year, month, day := d.Date() + return DTimestamp{Time: time.Date(year, month, day, 0, 0, 0, 0, loc)}, nil } case *IntervalType: @@ -1246,8 +1257,7 @@ func ParseDate(s DString) (DDate, error) { if err != nil { return DummyDate, err } - - return MakeDDate(t), nil + return DDate{Time: t}, nil } // ParseTimestamp parses the timestamp. @@ -1271,8 +1281,7 @@ func (ctx EvalContext) ParseTimestamp(s DString) (DTimestamp, error) { } { var t time.Time if t, err = time.ParseInLocation(format, str, loc); err == nil { - // Always return the time in the session time zone. - return DTimestamp{Time: t.In(loc)}, nil + return DTimestamp{Time: t}, nil } } diff --git a/sql/table.go b/sql/table.go index ac70874533d3..051c1eb83a0d 100644 --- a/sql/table.go +++ b/sql/table.go @@ -369,7 +369,7 @@ func decodeTableKey(valType parser.Datum, key []byte) (parser.Datum, []byte, err return parser.DBytes(r), rkey, err case parser.DDate: rkey, t, err := encoding.DecodeTime(key) - return parser.MakeDDate(t), rkey, err + return parser.DDate{Time: t}, rkey, err case parser.DTimestamp: rkey, t, err := encoding.DecodeTime(key) return parser.DTimestamp{Time: t}, rkey, err @@ -516,7 +516,7 @@ func unmarshalColumnValue(kind ColumnType_Kind, value *roachpb.Value) (parser.Da if err != nil { return nil, err } - return parser.MakeDDate(v), nil + return parser.DDate{Time: v}, nil case ColumnType_TIMESTAMP: v, err := value.GetTime() if err != nil { diff --git a/sql/testdata/datetime b/sql/testdata/datetime index 84fc7370ebe3..b3fcf5127231 100644 --- a/sql/testdata/datetime +++ b/sql/testdata/datetime @@ -317,6 +317,18 @@ SELECT '2015-08-24 21:45:45.53453'::timestamp ---- 2015-08-25 04:45:45.53453 +0000 UTC +# Check that casting from a timestamp to a date and vice versa +# uses the time zone. +query TTT +SELECT b::date FROM u WHERE a = 123 +---- +2015-08-29 + +query TTT +SELECT c::timestamp FROM u WHERE a = 123 +---- +2015-08-30 07:00:00 +0000 UTC + statement ok SET TIME ZONE -7