Skip to content

Commit 59ad9bf

Browse files
msgpack_ext: added String() function for interval
Added function for converting interval type to string, added tests for this function. Added test for decimal conversion. Closes #322 Co-authored-by: Oleg Jukovec <oleg.jukovec@gmail.com>
1 parent fae6dc7 commit 59ad9bf

4 files changed

Lines changed: 338 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
2222
to free used data directly by calling (#493).
2323
* Resources allocated for a `Future` object created by the `Connection` type
2424
could be released with the `Future.Release()` call.
25+
* Added function String() for type interval (#322).
2526

2627
### Changed
2728

datetime/example_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ func ExampleDatetime_Interval() {
161161
ival := dtFirst.Interval(dtSecond)
162162
fmt.Printf("%v", ival)
163163
// Output:
164-
// {2 2 0 -11 0 -1 0 0 0}
164+
// +2 years, 2 months, -11 days, -1 minute
165165
}
166166

167167
// ExampleDatetime_Add demonstrates how to add an Interval to a Datetime value.
@@ -278,7 +278,7 @@ func ExampleInterval_Add() {
278278

279279
fmt.Printf("%v", ival)
280280
// Output:
281-
// {11 2 3 0 0 30 10 0 1}
281+
// +11 years, 2 months, 3 weeks, 30 minutes, 10 seconds
282282
}
283283

284284
// ExampleInterval_Sub demonstrates how to subtract two intervals.
@@ -298,5 +298,5 @@ func ExampleInterval_Sub() {
298298

299299
fmt.Printf("%v", ival)
300300
// Output:
301-
// {-9 2 3 0 0 -30 10 0 1}
301+
// -9 years, 2 months, 3 weeks, -30 minutes, 10 seconds
302302
}

datetime/interval.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package datetime
22

33
import (
44
"bytes"
5+
"fmt"
56
"reflect"
7+
"strings"
68

79
"github.com/vmihailenco/msgpack/v5"
810
)
@@ -216,6 +218,73 @@ func decodeInterval(d *msgpack.Decoder, v reflect.Value) (err error) {
216218
return nil
217219
}
218220

221+
// Returns a human-readable string representation of the interval.
222+
var intervalFields = [8]struct {
223+
sing string
224+
plur string
225+
}{
226+
{"year", "years"},
227+
{"month", "months"},
228+
{"week", "weeks"},
229+
{"day", "days"},
230+
{"hour", "hours"},
231+
{"minute", "minutes"},
232+
{"second", "seconds"},
233+
{"nanosecond", "nanoseconds"},
234+
}
235+
236+
func (ival Interval) String() string {
237+
values := [8]int64{
238+
ival.Year,
239+
ival.Month,
240+
ival.Week,
241+
ival.Day,
242+
ival.Hour,
243+
ival.Min,
244+
ival.Sec,
245+
ival.Nsec,
246+
}
247+
248+
parts := make([]string, 0, 8)
249+
first := true
250+
hasNonZero := false
251+
252+
for i, field := range intervalFields {
253+
value := values[i]
254+
if value == 0 {
255+
continue
256+
}
257+
258+
hasNonZero = true
259+
var sign string
260+
var absValue int64
261+
262+
if value < 0 {
263+
sign = "-"
264+
absValue = -value
265+
} else {
266+
if first {
267+
sign = "+"
268+
}
269+
absValue = value
270+
}
271+
272+
unit := field.plur
273+
if absValue == 1 {
274+
unit = field.sing
275+
}
276+
277+
parts = append(parts, fmt.Sprintf("%s%d %s", sign, absValue, unit))
278+
first = false
279+
}
280+
281+
if !hasNonZero {
282+
return "0 seconds"
283+
}
284+
285+
return strings.Join(parts, ", ")
286+
}
287+
219288
func init() {
220289
msgpack.RegisterExtEncoder(interval_extId, Interval{},
221290
func(e *msgpack.Encoder, v reflect.Value) (ret []byte, err error) {

datetime/interval_test.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/tarantool/go-tarantool/v3/test_helpers"
1111
)
1212

13+
var _ fmt.Stringer = Interval{}
14+
1315
func TestIntervalAdd(t *testing.T) {
1416
orig := Interval{
1517
Year: 1,
@@ -133,3 +135,266 @@ func TestIntervalTarantoolEncoding(t *testing.T) {
133135
})
134136
}
135137
}
138+
139+
func TestIntervalString(t *testing.T) {
140+
tests := []struct {
141+
name string
142+
interval Interval
143+
expected string
144+
}{
145+
{
146+
name: "empty interval",
147+
interval: Interval{},
148+
expected: "0 seconds",
149+
},
150+
{
151+
name: "single positive year",
152+
interval: Interval{
153+
Year: 1,
154+
},
155+
expected: "+1 year",
156+
},
157+
{
158+
name: "single negative year",
159+
interval: Interval{
160+
Year: -1,
161+
},
162+
expected: "-1 year",
163+
},
164+
{
165+
name: "multiple positive years",
166+
interval: Interval{
167+
Year: 5,
168+
},
169+
expected: "+5 years",
170+
},
171+
{
172+
name: "multiple positive components",
173+
interval: Interval{
174+
Year: 1,
175+
Month: 2,
176+
Day: 3,
177+
},
178+
expected: "+1 year, 2 months, 3 days",
179+
},
180+
{
181+
name: "positive components without sign after first",
182+
interval: Interval{
183+
Hour: 1,
184+
Min: 30,
185+
Sec: 45,
186+
},
187+
expected: "+1 hour, 30 minutes, 45 seconds",
188+
},
189+
{
190+
name: "mixed signs",
191+
interval: Interval{
192+
Year: -1,
193+
Month: 2,
194+
Week: -3,
195+
Day: 4,
196+
Hour: -5,
197+
Min: 6,
198+
Sec: -7,
199+
Nsec: 9,
200+
},
201+
expected: "-1 year, 2 months, -3 weeks, 4 days, -5 hours, 6 minutes," +
202+
" -7 seconds, 9 nanoseconds",
203+
},
204+
{
205+
name: "positive seconds with nanoseconds",
206+
interval: Interval{
207+
Sec: 5,
208+
Nsec: 123456789,
209+
},
210+
expected: "+5 seconds, 123456789 nanoseconds",
211+
},
212+
{
213+
name: "negative seconds with nanoseconds",
214+
interval: Interval{
215+
Sec: -5,
216+
Nsec: -123456789,
217+
},
218+
expected: "-5 seconds, -123456789 nanoseconds",
219+
},
220+
{
221+
name: "only positive nanoseconds",
222+
interval: Interval{
223+
Nsec: 500000000,
224+
},
225+
expected: "+500000000 nanoseconds",
226+
},
227+
{
228+
name: "only negative nanoseconds",
229+
interval: Interval{
230+
Nsec: -500000000,
231+
},
232+
expected: "-500000000 nanoseconds",
233+
},
234+
{
235+
name: "complex interval",
236+
interval: Interval{
237+
Year: 1,
238+
Month: 6,
239+
Week: 2,
240+
Day: 3,
241+
Hour: 12,
242+
Min: 30,
243+
Sec: 45,
244+
Nsec: 123456789,
245+
},
246+
expected: "+1 year, 6 months, 2 weeks, 3 days, 12 hours, 30 minutes," +
247+
" 45 seconds, 123456789 nanoseconds",
248+
},
249+
{
250+
name: "all negative components",
251+
interval: Interval{
252+
Year: -1,
253+
Day: -2,
254+
Hour: -3,
255+
},
256+
expected: "-1 year, -2 days, -3 hours",
257+
},
258+
}
259+
260+
for _, tt := range tests {
261+
t.Run(tt.name, func(t *testing.T) {
262+
result := tt.interval.String()
263+
if result != tt.expected {
264+
t.Errorf("Interval.String() = %v, want %v", result, tt.expected)
265+
}
266+
})
267+
}
268+
}
269+
func TestIntervalString_WroksWithFmt(t *testing.T) {
270+
ival := Interval{Hour: 2, Min: 30}
271+
result := ival.String()
272+
expected := "+2 hours, 30 minutes"
273+
if result != expected {
274+
t.Errorf("fmt.Sprintf('%%s') = %v, want %v", result, expected)
275+
}
276+
277+
result = fmt.Sprintf("%v", ival)
278+
if result != expected {
279+
t.Errorf("fmt.Sprintf('%%v') = %v, want %v", result, expected)
280+
}
281+
}
282+
283+
func TestIntervalString_FromTarantool(t *testing.T) {
284+
skipIfDatetimeUnsupported(t)
285+
286+
conn := test_helpers.ConnectWithValidation(t, dialer, opts)
287+
defer conn.Close()
288+
289+
testCases := []struct {
290+
luaExpr string
291+
expected string
292+
}{
293+
{
294+
"return require('datetime').interval.new({})",
295+
"0 seconds",
296+
},
297+
{
298+
"return require('datetime').interval.new({year = 1})",
299+
"+1 year",
300+
},
301+
{
302+
"return require('datetime').interval.new({year = -1})",
303+
"-1 year",
304+
},
305+
{
306+
"return require('datetime').interval.new({year = 5})",
307+
"+5 years",
308+
},
309+
{
310+
"return require('datetime').interval.new({year = 1, month = 2, day = 3})",
311+
"+1 year, 2 months, 3 days",
312+
},
313+
{
314+
"return require('datetime').interval.new({hour = 1, min = 30, sec = 45})",
315+
"+1 hour, 30 minutes, 45 seconds",
316+
},
317+
{
318+
"return require('datetime').interval.new({year = -1, month = 2, week = -3, day = 4," +
319+
" hour = -5, min = 6, sec = -7, nsec = 9})",
320+
"-1 year, 2 months, -3 weeks, 4 days, -5 hours, 6 minutes, -7 seconds, 9 nanoseconds",
321+
},
322+
{
323+
"return require('datetime').interval.new({sec = 5, nsec = 123456789})",
324+
"+5 seconds, 123456789 nanoseconds",
325+
},
326+
{
327+
"return require('datetime').interval.new({sec = -5, nsec = -123456789})",
328+
"-5 seconds, -123456789 nanoseconds",
329+
},
330+
{
331+
"return require('datetime').interval.new({nsec = 500000000})",
332+
"+500000000 nanoseconds",
333+
},
334+
{
335+
"return require('datetime').interval.new({nsec = -500000000})",
336+
"-500000000 nanoseconds",
337+
},
338+
{
339+
"return require('datetime').interval.new({year = 1, month = 6, week = 2, day = 3," +
340+
" hour = 12, min = 30, sec = 45, nsec = 123456789})",
341+
"+1 year, 6 months, 2 weeks, 3 days, 12 hours, 30 minutes, 45 seconds," +
342+
" 123456789 nanoseconds",
343+
},
344+
{
345+
"return require('datetime').interval.new({year = -1, day = -2, hour = -3})",
346+
"-1 year, -2 days, -3 hours",
347+
},
348+
}
349+
350+
for _, tc := range testCases {
351+
t.Run(tc.expected, func(t *testing.T) {
352+
data, err := conn.Do(tarantool.NewEvalRequest(tc.luaExpr)).Get()
353+
if err != nil {
354+
t.Fatalf("Eval failed: %s", err)
355+
}
356+
if len(data) != 1 {
357+
t.Fatalf("Expected 1 result, got %d", len(data))
358+
}
359+
ival, ok := data[0].(Interval)
360+
if !ok {
361+
t.Fatalf("Result is not Interval: %T", data[0])
362+
}
363+
if got := ival.String(); got != tc.expected {
364+
t.Errorf("String() = %q, want %q", got, tc.expected)
365+
}
366+
})
367+
}
368+
}
369+
370+
func TestIntervalString_EdgeCases(t *testing.T) {
371+
tests := []struct {
372+
name string
373+
interval Interval
374+
}{
375+
{
376+
name: "max values",
377+
interval: Interval{Year: 1<<63 - 1, Month: 1<<63 - 1},
378+
},
379+
{
380+
name: "min values",
381+
interval: Interval{Year: -1 << 63, Month: -1 << 63},
382+
},
383+
{
384+
name: "mixed signs complex",
385+
interval: Interval{Year: 1, Month: -1, Day: 1, Hour: -1},
386+
},
387+
}
388+
389+
for _, tt := range tests {
390+
t.Run(tt.name, func(t *testing.T) {
391+
result := tt.interval.String()
392+
if result == "" {
393+
t.Error("Interval.String() returned empty string")
394+
}
395+
if len(result) > 1000 {
396+
t.Error("Interval.String() returned unexpectedly long string")
397+
}
398+
})
399+
}
400+
}

0 commit comments

Comments
 (0)