Skip to content

Commit e52f705

Browse files
authored
Fix parse of unix timestamp with more than ns precision (influxdata#5826)
1 parent 3e0efda commit e52f705

File tree

3 files changed

+49
-24
lines changed

3 files changed

+49
-24
lines changed

internal/internal.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -348,25 +348,18 @@ func ParseTimestamp(timestamp interface{}, format string) (time.Time, error) {
348348
// format = "unix_ns": epoch is assumed to be in nanoseconds and can come as number or string. Cannot have a decimal part.
349349
func ParseTimestampWithLocation(timestamp interface{}, format string, location string) (time.Time, error) {
350350
timeInt, timeFractional := int64(0), int64(0)
351-
timeEpochStr, ok := timestamp.(string)
352-
var err error
353351

354-
if !ok {
355-
timeEpochFloat, ok := timestamp.(float64)
356-
if !ok {
357-
return time.Time{}, fmt.Errorf("time: %v could not be converted to string nor float64", timestamp)
358-
}
359-
intPart, frac := math.Modf(timeEpochFloat)
360-
timeInt, timeFractional = int64(intPart), int64(frac*1e9)
361-
} else {
362-
splitted := regexp.MustCompile("[.,]").Split(timeEpochStr, 2)
352+
switch ts := timestamp.(type) {
353+
case string:
354+
var err error
355+
splitted := regexp.MustCompile("[.,]").Split(ts, 2)
363356
timeInt, err = strconv.ParseInt(splitted[0], 10, 64)
364357
if err != nil {
365358
loc, err := time.LoadLocation(location)
366359
if err != nil {
367360
return time.Time{}, fmt.Errorf("location: %s could not be loaded as a location", location)
368361
}
369-
return time.ParseInLocation(format, timeEpochStr, loc)
362+
return time.ParseInLocation(format, ts, loc)
370363
}
371364

372365
if len(splitted) == 2 {
@@ -380,7 +373,15 @@ func ParseTimestampWithLocation(timestamp interface{}, format string, location s
380373
return time.Time{}, err
381374
}
382375
}
376+
case int64:
377+
timeInt = ts
378+
case float64:
379+
intPart, frac := math.Modf(ts)
380+
timeInt, timeFractional = int64(intPart), int64(frac*1e9)
381+
default:
382+
return time.Time{}, fmt.Errorf("time: %v could not be converted to string nor float64", timestamp)
383383
}
384+
384385
if strings.EqualFold(format, "unix") {
385386
return time.Unix(timeInt, timeFractional).UTC(), nil
386387
} else if strings.EqualFold(format, "unix_ms") {

plugins/parsers/csv/parser.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -225,29 +225,25 @@ outer:
225225
// to the format.
226226
func parseTimestamp(timeFunc func() time.Time, recordFields map[string]interface{},
227227
timestampColumn, timestampFormat string,
228-
) (metricTime time.Time, err error) {
229-
metricTime = timeFunc()
230-
228+
) (time.Time, error) {
231229
if timestampColumn != "" {
232230
if recordFields[timestampColumn] == nil {
233-
err = fmt.Errorf("timestamp column: %v could not be found", timestampColumn)
234-
return
231+
return time.Time{}, fmt.Errorf("timestamp column: %v could not be found", timestampColumn)
235232
}
236233

237-
tStr := fmt.Sprintf("%v", recordFields[timestampColumn])
238-
239234
switch timestampFormat {
240235
case "":
241-
err = fmt.Errorf("timestamp format must be specified")
242-
return
236+
return time.Time{}, fmt.Errorf("timestamp format must be specified")
243237
default:
244-
metricTime, err = internal.ParseTimestamp(tStr, timestampFormat)
238+
metricTime, err := internal.ParseTimestamp(recordFields[timestampColumn], timestampFormat)
245239
if err != nil {
246-
return
240+
return time.Time{}, err
247241
}
242+
return metricTime, err
248243
}
249244
}
250-
return
245+
246+
return timeFunc(), nil
251247
}
252248

253249
// SetDefaultTags set the DefaultTags

plugins/parsers/csv/parser_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66
"time"
77

8+
"github.com/influxdata/telegraf"
89
"github.com/influxdata/telegraf/metric"
910
"github.com/influxdata/telegraf/testutil"
1011
"github.com/stretchr/testify/require"
@@ -322,3 +323,30 @@ func TestParseStream(t *testing.T) {
322323
DefaultTime(),
323324
), metric)
324325
}
326+
327+
func TestTimestampUnixFloatPrecision(t *testing.T) {
328+
p := Parser{
329+
MetricName: "csv",
330+
ColumnNames: []string{"time", "value"},
331+
TimestampColumn: "time",
332+
TimestampFormat: "unix",
333+
TimeFunc: DefaultTime,
334+
}
335+
data := `1551129661.95456123352050781250,42`
336+
337+
expected := []telegraf.Metric{
338+
testutil.MustMetric(
339+
"csv",
340+
map[string]string{},
341+
map[string]interface{}{
342+
"value": 42,
343+
"time": 1551129661.954561233,
344+
},
345+
time.Unix(1551129661, 954561233),
346+
),
347+
}
348+
349+
metrics, err := p.Parse([]byte(data))
350+
require.NoError(t, err)
351+
testutil.RequireMetricsEqual(t, expected, metrics)
352+
}

0 commit comments

Comments
 (0)