Skip to content

Commit 22de648

Browse files
committed
api: the Datetime type is immutable
The patch forces the use of objects of type Datetime instead of pointers. Part of #238
1 parent 5f76bc6 commit 22de648

File tree

5 files changed

+87
-75
lines changed

5 files changed

+87
-75
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
2020
- Use msgpack/v5 instead of msgpack.v2 (#236)
2121
- Call/NewCallRequest = Call17/NewCall17Request (#235)
2222
- Use objects of the Decimal type instead of pointers (#238)
23+
- Use objects of the Datetime type instead of pointers (#238)
2324

2425
### Deprecated
2526

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ faster than other packages according to public benchmarks.
2727
* [API reference](#api-reference)
2828
* [Walking\-through example](#walking-through-example)
2929
* [Migration to v2](#migration-to-v2)
30+
* [datetime package](#datetime-package)
3031
* [decimal package](#decimal-package)
3132
* [multi package](#multi-package)
3233
* [pool package](#pool-package)
@@ -149,6 +150,12 @@ by `Connect()`.
149150

150151
The article describes migration from go-tarantool to go-tarantool/v2.
151152

153+
#### datetime package
154+
155+
Now you need to use objects of the Datetime type instead of pointers to it. A
156+
new constructor `MakeDatetime` returns an object. `NewDatetime` has been
157+
removed.
158+
152159
#### decimal package
153160

154161
Now you need to use objects of the Decimal type instead of pointers to it. A

datetime/datetime.go

+38-28
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package datetime
1212
import (
1313
"encoding/binary"
1414
"fmt"
15+
"reflect"
1516
"time"
1617

1718
"github.com/vmihailenco/msgpack/v5"
@@ -35,7 +36,7 @@ import (
3536

3637
// Datetime external type. Supported since Tarantool 2.10. See more details in
3738
// issue https://github.com/tarantool/tarantool/issues/5946.
38-
const datetime_extId = 4
39+
const datetimeExtID = 4
3940

4041
// datetime structure keeps a number of seconds and nanoseconds since Unix Epoch.
4142
// Time is normalized by UTC, so time-zone offset is informative only.
@@ -93,41 +94,41 @@ const (
9394
offsetMax = 14 * 60 * 60
9495
)
9596

96-
// NewDatetime returns a pointer to a new datetime.Datetime that contains a
97+
// MakeDatetime returns a datetime.Datetime object that contains a
9798
// specified time.Time. It may return an error if the Time value is out of
9899
// supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z] or
99100
// an invalid timezone or offset value is out of supported range:
100101
// [-12 * 60 * 60, 14 * 60 * 60].
101102
//
102103
// NOTE: Tarantool's datetime.tz value is picked from t.Location().String().
103104
// "Local" location is unsupported, see ExampleNewDatetime_localUnsupported.
104-
func NewDatetime(t time.Time) (*Datetime, error) {
105+
func MakeDatetime(t time.Time) (Datetime, error) {
106+
dt := Datetime{}
105107
seconds := t.Unix()
106108

107109
if seconds < minSeconds || seconds > maxSeconds {
108-
return nil, fmt.Errorf("time %s is out of supported range", t)
110+
return dt, fmt.Errorf("time %s is out of supported range", t)
109111
}
110112

111113
zone := t.Location().String()
112114
_, offset := t.Zone()
113115
if zone != NoTimezone {
114116
if _, ok := timezoneToIndex[zone]; !ok {
115-
return nil, fmt.Errorf("unknown timezone %s with offset %d",
117+
return dt, fmt.Errorf("unknown timezone %s with offset %d",
116118
zone, offset)
117119
}
118120
}
119121

120122
if offset < offsetMin || offset > offsetMax {
121-
return nil, fmt.Errorf("offset must be between %d and %d hours",
123+
return dt, fmt.Errorf("offset must be between %d and %d hours",
122124
offsetMin, offsetMax)
123125
}
124126

125-
dt := new(Datetime)
126127
dt.time = t
127128
return dt, nil
128129
}
129130

130-
func intervalFromDatetime(dtime *Datetime) (ival Interval) {
131+
func intervalFromDatetime(dtime Datetime) (ival Interval) {
131132
ival.Year = int64(dtime.time.Year())
132133
ival.Month = int64(dtime.time.Month())
133134
ival.Day = int64(dtime.time.Day())
@@ -158,7 +159,7 @@ func daysInMonth(year int64, month int64) int64 {
158159

159160
// C implementation:
160161
// https://github.com/tarantool/c-dt/blob/cec6acebb54d9e73ea0b99c63898732abd7683a6/dt_arithmetic.c#L74-L98
161-
func addMonth(ival *Interval, delta int64, adjust Adjust) {
162+
func addMonth(ival Interval, delta int64, adjust Adjust) Interval {
162163
oldYear := ival.Year
163164
oldMonth := ival.Month
164165

@@ -172,16 +173,17 @@ func addMonth(ival *Interval, delta int64, adjust Adjust) {
172173
}
173174
}
174175
if adjust == ExcessAdjust || ival.Day < 28 {
175-
return
176+
return ival
176177
}
177178

178179
dim := daysInMonth(ival.Year, ival.Month)
179180
if ival.Day > dim || (adjust == LastAdjust && ival.Day == daysInMonth(oldYear, oldMonth)) {
180181
ival.Day = dim
181182
}
183+
return ival
182184
}
183185

184-
func (dtime *Datetime) add(ival Interval, positive bool) (*Datetime, error) {
186+
func (dtime Datetime) add(ival Interval, positive bool) (Datetime, error) {
185187
newVal := intervalFromDatetime(dtime)
186188

187189
var direction int64
@@ -191,7 +193,7 @@ func (dtime *Datetime) add(ival Interval, positive bool) (*Datetime, error) {
191193
direction = -1
192194
}
193195

194-
addMonth(&newVal, direction*ival.Year*12+direction*ival.Month, ival.Adjust)
196+
newVal = addMonth(newVal, direction*ival.Year*12+direction*ival.Month, ival.Adjust)
195197
newVal.Day += direction * 7 * ival.Week
196198
newVal.Day += direction * ival.Day
197199
newVal.Hour += direction * ival.Hour
@@ -203,23 +205,23 @@ func (dtime *Datetime) add(ival Interval, positive bool) (*Datetime, error) {
203205
int(newVal.Day), int(newVal.Hour), int(newVal.Min),
204206
int(newVal.Sec), int(newVal.Nsec), dtime.time.Location())
205207

206-
return NewDatetime(tm)
208+
return MakeDatetime(tm)
207209
}
208210

209211
// Add creates a new Datetime as addition of the Datetime and Interval. It may
210212
// return an error if a new Datetime is out of supported range.
211-
func (dtime *Datetime) Add(ival Interval) (*Datetime, error) {
213+
func (dtime Datetime) Add(ival Interval) (Datetime, error) {
212214
return dtime.add(ival, true)
213215
}
214216

215217
// Sub creates a new Datetime as subtraction of the Datetime and Interval. It
216218
// may return an error if a new Datetime is out of supported range.
217-
func (dtime *Datetime) Sub(ival Interval) (*Datetime, error) {
219+
func (dtime Datetime) Sub(ival Interval) (Datetime, error) {
218220
return dtime.add(ival, false)
219221
}
220222

221223
// Interval returns an Interval value to a next Datetime value.
222-
func (dtime *Datetime) Interval(next *Datetime) Interval {
224+
func (dtime Datetime) Interval(next Datetime) Interval {
223225
curIval := intervalFromDatetime(dtime)
224226
nextIval := intervalFromDatetime(next)
225227
_, curOffset := dtime.time.Zone()
@@ -236,11 +238,12 @@ func (dtime *Datetime) Interval(next *Datetime) Interval {
236238
// If a Datetime created via unmarshaling Tarantool's datetime then we try to
237239
// create a location with time.LoadLocation() first. In case of failure, we use
238240
// a location created with time.FixedZone().
239-
func (dtime *Datetime) ToTime() time.Time {
241+
func (dtime Datetime) ToTime() time.Time {
240242
return dtime.time
241243
}
242244

243-
func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
245+
func datetimeEncoder(e *msgpack.Encoder, v reflect.Value) ([]byte, error) {
246+
dtime := v.Interface().(Datetime)
244247
tm := dtime.ToTime()
245248

246249
var dt datetime
@@ -272,17 +275,25 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
272275
return buf, nil
273276
}
274277

275-
func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
276-
l := len(b)
277-
if l != maxSize && l != secondsSize {
278-
return fmt.Errorf("invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize)
278+
func datetimeDecoder(d *msgpack.Decoder, v reflect.Value, extLen int) error {
279+
if extLen != maxSize && extLen != secondsSize {
280+
return fmt.Errorf("invalid data length: got %d, wanted %d or %d", extLen, secondsSize, maxSize)
281+
}
282+
283+
b := make([]byte, extLen)
284+
n, err := d.Buffered().Read(b)
285+
if err != nil {
286+
return err
287+
}
288+
if n < extLen {
289+
return fmt.Errorf("msgpack: unexpected end of stream after %d datetime bytes", n)
279290
}
280291

281292
var dt datetime
282293
sec := binary.LittleEndian.Uint64(b)
283294
dt.seconds = int64(sec)
284295
dt.nsec = 0
285-
if l == maxSize {
296+
if extLen == maxSize {
286297
dt.nsec = int32(binary.LittleEndian.Uint32(b[secondsSize:]))
287298
dt.tzOffset = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize:]))
288299
dt.tzIndex = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize+tzOffsetSize:]))
@@ -315,13 +326,12 @@ func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
315326
}
316327
tt = tt.In(loc)
317328

318-
dtp, err := NewDatetime(tt)
319-
if dtp != nil {
320-
*tm = *dtp
321-
}
329+
ptr := v.Addr().Interface().(*Datetime)
330+
*ptr, err = MakeDatetime(tt)
322331
return err
323332
}
324333

325334
func init() {
326-
msgpack.RegisterExt(datetime_extId, (*Datetime)(nil))
335+
msgpack.RegisterExtDecoder(datetimeExtID, Datetime{}, datetimeDecoder)
336+
msgpack.RegisterExtEncoder(datetimeExtID, Datetime{}, datetimeEncoder)
327337
}

0 commit comments

Comments
 (0)