Skip to content

Commit

Permalink
*: improve time codec. (pingcap#1551)
Browse files Browse the repository at this point in the history
  • Loading branch information
coocood authored Aug 5, 2016
1 parent dad48b9 commit 1d5fdbf
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 99 deletions.
130 changes: 64 additions & 66 deletions mysql/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ var (
ZeroDuration = Duration{Duration: time.Duration(0), Fsp: DefaultFsp}

// ZeroTime is the zero value for time.Time type.
ZeroTime = time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC)
ZeroTime = time.Date(0, 0, 0, 0, 0, 0, 0, time.Local)

// ZeroDatetime is the zero value for datetime Time.
ZeroDatetime = Time{
Expand All @@ -83,6 +83,8 @@ var (
Type: TypeDate,
Fsp: DefaultFsp,
}

local = time.Local
)

var (
Expand Down Expand Up @@ -159,71 +161,6 @@ func (t Time) IsZero() bool {
return t.Time.Equal(ZeroTime)
}

// Marshal returns the binary encoding of time.
func (t Time) Marshal() ([]byte, error) {
var (
b []byte
err error
)

switch t.Type {
case TypeDatetime, TypeDate:
// We must use t's Zone not current Now Zone,
// For EDT/EST, even we create the time with time.Local location,
// we may still have a different zone with current Now time.
_, offset := t.Zone()
// For datetime and date type, we have a trick to marshal.
// e.g, if local time is 2010-10-10T10:10:10 UTC+8
// we will change this to 2010-10-10T10:10:10 UTC and then marshal.
b, err = t.Time.Add(time.Duration(offset) * time.Second).UTC().MarshalBinary()
case TypeTimestamp:
b, err = t.Time.UTC().MarshalBinary()
default:
err = errors.Errorf("invalid time type %d", t.Type)
}

if err != nil {
return nil, errors.Trace(err)
}

return b, nil
}

// Unmarshal decodes the binary data into Time with current local time.
func (t *Time) Unmarshal(b []byte) error {
return t.UnmarshalInLocation(b, time.Local)
}

// UnmarshalInLocation decodes the binary data
// into Time with a specific time Location.
func (t *Time) UnmarshalInLocation(b []byte, loc *time.Location) error {
if err := t.Time.UnmarshalBinary(b); err != nil {
return errors.Trace(err)
}

if t.IsZero() {
return nil
}

if t.Type == TypeDatetime || t.Type == TypeDate {
// e.g, for 2010-10-10T10:10:10 UTC, we will unmarshal to 2010-10-10T10:10:10 location
_, offset := t.Time.In(loc).Zone()

t.Time = t.Time.Add(-time.Duration(offset) * time.Second).In(loc)
if t.Type == TypeDate {
// for date type ,we will only use year, month and day.
year, month, day := t.Time.Date()
t.Time = time.Date(year, month, day, 0, 0, 0, 0, loc)
}
} else if t.Type == TypeTimestamp {
t.Time = t.Time.In(loc)
} else {
return errors.Errorf("invalid time type %d", t.Type)
}

return nil
}

const numberFormat = "20060102150405"
const dateFormat = "20060102"

Expand Down Expand Up @@ -346,6 +283,67 @@ func (t Time) RoundFrac(fsp int) (Time, error) {
return Time{Time: nt, Type: t.Type, Fsp: fsp}, nil
}

// ToPackedUint encodes Time to a packed uint64 value.
//
// 1 bit 0
// 17 bits year*13+month (year 0-9999, month 0-12)
// 5 bits day (0-31)
// 5 bits hour (0-23)
// 6 bits minute (0-59)
// 6 bits second (0-59)
// 24 bits microseconds (0-999999)
//
// Total: 64 bits = 8 bytes
//
// 0YYYYYYY.YYYYYYYY.YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
//
func (t Time) ToPackedUint() uint64 {
tm := t.Time
if t.IsZero() {
return 0
}
if t.Type == TypeTimestamp {
tm = t.UTC()
}
year, month, day := tm.Date()
hour, minute, sec := tm.Clock()
ymd := uint64(((year*13 + int(month)) << 5) | day)
hms := uint64(hour<<12 | minute<<6 | sec)
micro := uint64(tm.Nanosecond() / 1000)
return ((ymd<<17 | hms) << 24) | micro
}

// FromPackedUint decodes Time from a packed uint64 value.
func (t *Time) FromPackedUint(packed uint64) error {
if packed == 0 {
t.Time = ZeroTime
return nil
}
ymdhms := packed >> 24
ymd := ymdhms >> 17
day := int(ymd & (1<<5 - 1))
ym := ymd >> 5
month := int(ym % 13)
year := int(ym / 13)

hms := ymdhms & (1<<17 - 1)
second := int(hms & (1<<6 - 1))
minute := int((hms >> 6) & (1<<6 - 1))
hour := int(hms >> 12)

nanosec := int(packed%(1<<24)) * 1000
err := checkTime(year, month, day, hour, minute, second, nanosec)
if err != nil {
return errors.Trace(err)
}
loc := local
if t.Type == TypeTimestamp {
loc = time.UTC
}
t.Time = time.Date(year, time.Month(month), day, hour, minute, second, nanosec, loc).In(local)
return nil
}

func parseDateFormat(format string) []string {
format = strings.TrimSpace(format)

Expand Down
30 changes: 13 additions & 17 deletions mysql/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,49 +321,46 @@ func (s *testTimeSuite) TestCodec(c *C) {
defer testleak.AfterTest(c)()
t, err := ParseTimestamp("2010-10-10 10:11:11")
c.Assert(err, IsNil)
b, err := t.Marshal()
c.Assert(err, IsNil)
packed := t.ToPackedUint()

var t1 Time
t1.Type = TypeTimestamp

z := s.getLocation(c)

err = t1.UnmarshalInLocation(b, z)
local = z
err = t1.FromPackedUint(packed)
c.Assert(err, IsNil)
c.Assert(t.String(), Not(Equals), t1.String())

err = t1.UnmarshalInLocation(b, time.Local)
local = time.Local
err = t1.FromPackedUint(packed)
c.Assert(err, IsNil)
c.Assert(t.String(), Equals, t1.String())

t1.Time = time.Now()
b, err = t1.Marshal()
c.Assert(err, IsNil)
packed = t1.ToPackedUint()

var t2 Time
t2.Type = TypeTimestamp
err = t2.Unmarshal(b)
err = t2.FromPackedUint(packed)
c.Assert(err, IsNil)
c.Assert(t1.String(), Equals, t2.String())

b, err = ZeroDatetime.Marshal()
c.Assert(err, IsNil)
packed = ZeroDatetime.ToPackedUint()

var t3 Time
t3.Type = TypeDatetime
err = t3.Unmarshal(b)
err = t3.FromPackedUint(packed)
c.Assert(err, IsNil)
c.Assert(t3.String(), Equals, ZeroDatetime.String())

t, err = ParseDatetime("0001-01-01 00:00:00")
c.Assert(err, IsNil)
b, err = t.Marshal()
c.Assert(err, IsNil)
packed = t.ToPackedUint()

var t4 Time
t4.Type = TypeDatetime
err = t4.Unmarshal(b)
err = t4.FromPackedUint(packed)
c.Assert(err, IsNil)
c.Assert(t.String(), Equals, t4.String())

Expand All @@ -378,13 +375,12 @@ func (s *testTimeSuite) TestCodec(c *C) {
t, err := ParseTime(test, TypeDatetime, MaxFsp)
c.Assert(err, IsNil)

b, err := t.Marshal()
c.Assert(err, IsNil)
packed = t.ToPackedUint()

var dest Time
dest.Type = TypeDatetime
dest.Fsp = MaxFsp
err = dest.Unmarshal(b)
err = dest.FromPackedUint(packed)
c.Assert(err, IsNil)
c.Assert(dest.String(), Equals, test)
}
Expand Down
20 changes: 6 additions & 14 deletions tablecodec/tablecodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,7 @@ func flatten(data types.Datum) (types.Datum, error) {
switch data.Kind() {
case types.KindMysqlTime:
// for mysql datetime, timestamp and date type
b, err := data.GetMysqlTime().Marshal()
if err != nil {
return types.NewDatum(nil), errors.Trace(err)
}
return types.NewDatum(b), nil
return types.NewUintDatum(data.GetMysqlTime().ToPackedUint()), nil
case types.KindMysqlDuration:
// for mysql time type
data.SetInt64(int64(data.GetMysqlDuration().Duration))
Expand Down Expand Up @@ -308,16 +304,12 @@ func Unflatten(datum types.Datum, ft *types.FieldType, inIndex bool) (types.Datu
return datum, nil
case mysql.TypeDate, mysql.TypeDatetime, mysql.TypeTimestamp:
var t mysql.Time
t.Type = ft.Tp
t.Fsp = ft.Decimal
var err error
if inIndex {
t, err = mysql.ParseTime(datum.GetString(), ft.Tp, ft.Decimal)
} else {
t.Type = ft.Tp
t.Fsp = ft.Decimal
err = t.Unmarshal(datum.GetBytes())
if err != nil {
return datum, errors.Trace(err)
}
err = t.FromPackedUint(datum.GetUint64())
if err != nil {
return datum, errors.Trace(err)
}
datum.SetMysqlTime(t)
return datum, nil
Expand Down
3 changes: 2 additions & 1 deletion util/codec/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func encode(b []byte, vals []types.Datum, comparable bool) ([]byte, error) {
case types.KindString, types.KindBytes:
b = encodeBytes(b, val.GetBytes(), comparable)
case types.KindMysqlTime:
b = encodeBytes(b, []byte(val.GetMysqlTime().String()), comparable)
b = append(b, uintFlag)
b = EncodeUint(b, val.GetMysqlTime().ToPackedUint())
case types.KindMysqlDuration:
// duration may have negative value, so we cannot use String to encode directly.
b = append(b, durationFlag)
Expand Down
5 changes: 4 additions & 1 deletion util/codec/codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,10 @@ func (s *testCodecSuite) TestTime(c *C) {
c.Assert(err, IsNil)
v, err := Decode(b)
c.Assert(err, IsNil)
c.Assert(v, DeepEquals, types.MakeDatums([]byte(t)))
var t mysql.Time
t.Type = mysql.TypeDatetime
t.FromPackedUint(v[0].GetUint64())
c.Assert(types.NewDatum(t), DeepEquals, m)
}

tblCmp := []struct {
Expand Down

0 comments on commit 1d5fdbf

Please sign in to comment.