Skip to content

Commit

Permalink
chore: add HumanizeTimestamp; make ConvertToFloat exportable (#654)
Browse files Browse the repository at this point in the history
chore: add HumanizeTimestamp; make ConvertToFloat exportable

Signed-off-by: Sergey <[email protected]>
  • Loading branch information
freak12techno authored Jun 21, 2024
1 parent 04635d2 commit ab322ea
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 67 deletions.
38 changes: 36 additions & 2 deletions helpers/templates/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
package templates

import (
"errors"
"fmt"
"math"
"strconv"
"time"

"github.com/prometheus/common/model"
)

func convertToFloat(i interface{}) (float64, error) {
var errNaNOrInf = errors.New("value is NaN or Inf")

func ConvertToFloat(i interface{}) (float64, error) {
switch v := i.(type) {
case float64:
return v, nil
Expand All @@ -41,8 +46,20 @@ func convertToFloat(i interface{}) (float64, error) {
}
}

func FloatToTime(v float64) (*time.Time, error) {
if math.IsNaN(v) || math.IsInf(v, 0) {
return nil, errNaNOrInf
}
timestamp := v * 1e9
if timestamp > math.MaxInt64 || timestamp < math.MinInt64 {
return nil, fmt.Errorf("%v cannot be represented as a nanoseconds timestamp since it overflows int64", v)
}
t := model.TimeFromUnixNano(int64(timestamp)).Time().UTC()
return &t, nil
}

func HumanizeDuration(i interface{}) (string, error) {
v, err := convertToFloat(i)
v, err := ConvertToFloat(i)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -87,3 +104,20 @@ func HumanizeDuration(i interface{}) (string, error) {
}
return fmt.Sprintf("%.4g%ss", v, prefix), nil
}

func HumanizeTimestamp(i interface{}) (string, error) {
v, err := ConvertToFloat(i)
if err != nil {
return "", err
}

tm, err := FloatToTime(v)
switch {
case errors.Is(err, errNaNOrInf):
return fmt.Sprintf("%.4g", v), nil
case err != nil:
return "", err
}

return fmt.Sprint(tm), nil
}
112 changes: 47 additions & 65 deletions helpers/templates/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,100 +14,54 @@
package templates

import (
"math"
"testing"

"github.com/stretchr/testify/require"
)

func TestHumanizeDurationSecondsFloat64(t *testing.T) {
func TestHumanizeDuration(t *testing.T) {
tc := []struct {
name string
input float64
input interface{}
expected string
}{
// Integers
{name: "zero", input: 0, expected: "0s"},
{name: "one second", input: 1, expected: "1s"},
{name: "one minute", input: 60, expected: "1m 0s"},
{name: "one hour", input: 3600, expected: "1h 0m 0s"},
{name: "one day", input: 86400, expected: "1d 0h 0m 0s"},
{name: "one day and one hour", input: 86400 + 3600, expected: "1d 1h 0m 0s"},
{name: "negative duration", input: -(86400*2 + 3600*3 + 60*4 + 5), expected: "-2d 3h 4m 5s"},
// Float64 with fractions
{name: "using a float", input: 899.99, expected: "14m 59s"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

func TestHumanizeDurationSubsecondAndFractionalSecondsFloat64(t *testing.T) {
tc := []struct {
name string
input float64
expected string
}{
{name: "millseconds", input: .1, expected: "100ms"},
{name: "nanoseconds", input: .0001, expected: "100us"},
{name: "milliseconds + nanoseconds", input: .12345, expected: "123.5ms"},
{name: "minute + millisecond", input: 60.1, expected: "1m 0s"},
{name: "minute + milliseconds", input: 60.5, expected: "1m 0s"},
{name: "second + milliseconds", input: 1.2345, expected: "1.234s"},
{name: "second + milliseconds rounded", input: 12.345, expected: "12.35s"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

func TestHumanizeDurationErrorString(t *testing.T) {
_, err := HumanizeDuration("one")
require.Error(t, err)
}

func TestHumanizeDurationSecondsString(t *testing.T) {
tc := []struct {
name string
input string
expected string
}{
// String
{name: "zero", input: "0", expected: "0s"},
{name: "second", input: "1", expected: "1s"},
{name: "minute", input: "60", expected: "1m 0s"},
{name: "hour", input: "3600", expected: "1h 0m 0s"},
{name: "day", input: "86400", expected: "1d 0h 0m 0s"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

func TestHumanizeDurationSubsecondAndFractionalSecondsString(t *testing.T) {
tc := []struct {
name string
input string
expected string
}{
// String with fractions
{name: "millseconds", input: ".1", expected: "100ms"},
{name: "nanoseconds", input: ".0001", expected: "100us"},
{name: "milliseconds + nanoseconds", input: ".12345", expected: "123.5ms"},
{name: "minute + millisecond", input: "60.1", expected: "1m 0s"},
{name: "minute + milliseconds", input: "60.5", expected: "1m 0s"},
{name: "second + milliseconds", input: "1.2345", expected: "1.234s"},
{name: "second + milliseconds rounded", input: "12.345", expected: "12.35s"},
// Int
{name: "zero", input: 0, expected: "0s"},
{name: "negative", input: -1, expected: "-1s"},
{name: "second", input: 1, expected: "1s"},
{name: "days", input: 1234567, expected: "14d 6h 56m 7s"},
}

for _, tt := range tc {
Expand All @@ -119,23 +73,51 @@ func TestHumanizeDurationSubsecondAndFractionalSecondsString(t *testing.T) {
}
}

func TestHumanizeDurationSecondsInt(t *testing.T) {
func TestHumanizeDurationErrorString(t *testing.T) {
_, err := HumanizeDuration("one")
require.Error(t, err)
}

func TestHumanizeTimestamp(t *testing.T) {
tc := []struct {
name string
input int
input interface{}
expected string
}{
{name: "zero", input: 0, expected: "0s"},
{name: "negative", input: -1, expected: "-1s"},
{name: "second", input: 1, expected: "1s"},
{name: "days", input: 1234567, expected: "14d 6h 56m 7s"},
// Int
{name: "zero", input: 0, expected: "1970-01-01 00:00:00 +0000 UTC"},
{name: "negative", input: -1, expected: "1969-12-31 23:59:59 +0000 UTC"},
{name: "one", input: 1, expected: "1970-01-01 00:00:01 +0000 UTC"},
{name: "past", input: 1234567, expected: "1970-01-15 06:56:07 +0000 UTC"},
{name: "future", input: 9223372036, expected: "2262-04-11 23:47:16 +0000 UTC"},
// Uint
{name: "zero", input: uint64(0), expected: "1970-01-01 00:00:00 +0000 UTC"},
{name: "one", input: uint64(1), expected: "1970-01-01 00:00:01 +0000 UTC"},
{name: "past", input: uint64(1234567), expected: "1970-01-15 06:56:07 +0000 UTC"},
{name: "future", input: uint64(9223372036), expected: "2262-04-11 23:47:16 +0000 UTC"},
// NaN/Inf, strings
{name: "infinity", input: "+Inf", expected: "+Inf"},
{name: "minus infinity", input: "-Inf", expected: "-Inf"},
{name: "NaN", input: "NaN", expected: "NaN"},
// Nan/Inf, float64
{name: "infinity", input: math.Inf(1), expected: "+Inf"},
{name: "minus infinity", input: math.Inf(-1), expected: "-Inf"},
{name: "NaN", input: math.NaN(), expected: "NaN"},
// Sampled data
{name: "sample float64", input: 1435065584.128, expected: "2015-06-23 13:19:44.128 +0000 UTC"},
{name: "sample string", input: "1435065584.128", expected: "2015-06-23 13:19:44.128 +0000 UTC"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
result, err := HumanizeTimestamp(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

func TestHumanizeTimestampError(t *testing.T) {
_, err := HumanizeTimestamp(math.MaxInt64)
require.Error(t, err)
}

0 comments on commit ab322ea

Please sign in to comment.