@@ -12,6 +12,7 @@ package datetime
1212import (
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
325334func init () {
326- msgpack .RegisterExt (datetime_extId , (* Datetime )(nil ))
335+ msgpack .RegisterExtDecoder (datetimeExtID , Datetime {}, datetimeDecoder )
336+ msgpack .RegisterExtEncoder (datetimeExtID , Datetime {}, datetimeEncoder )
327337}
0 commit comments