Skip to content

Commit 85f6be7

Browse files
authored
Convert string dates to time (#19)
convert string dates to time
1 parent 7c8f8ea commit 85f6be7

File tree

3 files changed

+97
-2
lines changed

3 files changed

+97
-2
lines changed

duck/data/converters.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package data
33
import (
44
"time"
55

6+
"github.com/araddon/dateparse"
67
"github.com/grafana/grafana-plugin-sdk-go/data"
78
"github.com/grafana/grafana-plugin-sdk-go/data/framestruct"
89
)
@@ -37,7 +38,7 @@ var timeConverter = func(i interface{}) (interface{}, error) {
3738
if s, ok := i.(*string); ok {
3839
return parseDate(*s)
3940
}
40-
return nil, nil
41+
return i, nil
4142
}
4243

4344
const layout = "2006-01-02 15:04:05-07"
@@ -50,3 +51,63 @@ func parseDate(s string) (time.Time, error) {
5051
}
5152
return t.UTC(), nil
5253
}
54+
55+
// TODO: just define converters for the date fields we find
56+
// then we can avoid looping through all the results and fields here
57+
func ConvertDateFields(results []map[string]any) {
58+
dateFields := findDateFields(results)
59+
for row, result := range results {
60+
for key, value := range result {
61+
isDateField := dateFields[key]
62+
if isDateField != nil && *isDateField {
63+
if s, ok := value.(string); ok {
64+
dateValue := isDate(s)
65+
results[row][key] = dateValue
66+
}
67+
}
68+
}
69+
}
70+
}
71+
72+
func findDateFields(results []map[string]any) map[string]*bool {
73+
dateFields := make(map[string]*bool)
74+
for _, result := range results {
75+
if len(dateFields) == len(result) {
76+
break
77+
}
78+
for key, value := range result {
79+
if value == nil {
80+
continue
81+
}
82+
83+
isDateField := dateFields[key]
84+
if isDateField != nil {
85+
continue
86+
}
87+
88+
if s, ok := value.(string); ok {
89+
if s == "" {
90+
continue
91+
}
92+
93+
dateValue := isDate(s)
94+
flag := true
95+
flagPtr := &flag
96+
if dateValue != nil {
97+
dateFields[key] = flagPtr
98+
} else {
99+
*flagPtr = false
100+
}
101+
}
102+
}
103+
}
104+
return dateFields
105+
}
106+
107+
func isDate(s string) *time.Time {
108+
val, err := dateparse.ParseStrict(s)
109+
if err != nil {
110+
return nil
111+
}
112+
return &val
113+
}

duck/duckdb.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ func resultsToFrame(name string, res string, f *sdk.Frame, frames []*sdk.Frame)
180180
logger.Error("error unmarshalling results", "error", err)
181181
return err
182182
}
183+
184+
data.ConvertDateFields(results)
185+
183186
converters := data.Converters(frames)
184187
resultsFrame, err := framestruct.ToDataFrame(name, results, converters...)
185188

@@ -260,4 +263,4 @@ func resultsToFrame(name string, res string, f *sdk.Frame, frames []*sdk.Frame)
260263
// }
261264
// }
262265
// return nil
263-
// }
266+
//

duck/duckdb_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,37 @@ func TestLabelsMultiFrame(t *testing.T) {
401401
assert.Contains(t, txt, "B")
402402
}
403403

404+
func TestTimeSeriesAggregate(t *testing.T) {
405+
db := NewInMemoryDB()
406+
407+
tt := "2024-02-23 09:01:54"
408+
dd, err := dateparse.ParseAny(tt)
409+
assert.Nil(t, err)
410+
411+
var values = []time.Time{dd, dd, dd}
412+
timeField := data.NewField("time", nil, values)
413+
valueField := data.NewField("value", nil, []*float64{new(float64), new(float64), new(float64)})
414+
categoryField := data.NewField("category", nil, []string{"a", "a", "b"})
415+
416+
frame := data.NewFrame("foo", timeField, valueField, categoryField)
417+
frame.RefID = "foo"
418+
419+
frames := []*data.Frame{frame}
420+
421+
model := &data.Frame{}
422+
err = db.QueryFramesInto("foo", "select CURRENT_TIMESTAMP, min(time) as t, 1 as j from foo group by category", frames, model)
423+
assert.Nil(t, err)
424+
425+
assert.Equal(t, data.FrameTypeTimeSeriesWide, model.Meta.Type)
426+
427+
assert.Equal(t, 2, model.Rows())
428+
txt, err := model.StringTable(-1, -1)
429+
assert.Nil(t, err)
430+
431+
fmt.Printf("GOT: %s", txt)
432+
assert.Contains(t, txt, "Type: []*time.Time")
433+
}
434+
404435
// TODO - don't think this is valid to have a frame with duplicate fields
405436
// func TestWideFrameWithDuplicateFields(t *testing.T) {
406437
// db := NewInMemoryDB()

0 commit comments

Comments
 (0)