Skip to content

Commit

Permalink
feat: add time to int converters (#25147)
Browse files Browse the repository at this point in the history
Description: Allows conversion of time to nanoseconds, microseconds,
milliseconds or seconds

Link to tracking Issue:
Related to
#24686

Testing: Unit tests for each converter

Documentation:

---------

Co-authored-by: Alex Boten <[email protected]>
Co-authored-by: Tyler Helmuth <[email protected]>
Co-authored-by: Evan Bradley <[email protected]>
  • Loading branch information
4 people authored Aug 31, 2023
1 parent 4fabc48 commit 3a6f547
Show file tree
Hide file tree
Showing 12 changed files with 571 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .chloggen/feat_time-to-int.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use this changelog template to create an entry for release notes.
# If your change doesn't affect end users, such as a test fix or a tooling change,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: 'enhancement'

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: 'pkg/ottl'

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "Add converters to convert time to unix nanoseconds, unix microseconds, unix milliseconds or unix seconds"

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [24686]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
30 changes: 30 additions & 0 deletions pkg/ottl/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,36 @@ func (p *Parser[K]) newGetterFromConverter(c converter) (Getter[K], error) {
}, nil
}

// TimeGetter is a Getter that must return an time.Time.
type TimeGetter[K any] interface {
// Get retrieves an time.Time value.
Get(ctx context.Context, tCtx K) (time.Time, error)
}

// StandardTimeGetter is a basic implementation of IntGetter
type StandardTimeGetter[K any] struct {
Getter func(ctx context.Context, tCtx K) (interface{}, error)
}

// Get retrieves a time.Time value.
// If the value is not a time.Time, a new TypeError is returned.
// If there is an error getting the value it will be returned.
func (g StandardTimeGetter[K]) Get(ctx context.Context, tCtx K) (time.Time, error) {
val, err := g.Getter(ctx, tCtx)
if err != nil {
return time.Time{}, fmt.Errorf("error getting value in %T: %w", g, err)
}
if val == nil {
return time.Time{}, TypeError("expected time but got nil")
}
switch v := val.(type) {
case time.Time:
return v, nil
default:
return time.Time{}, TypeError(fmt.Sprintf("expected time but got %T", val))
}
}

// DurationGetter is a Getter that must return an time.Duration.
type DurationGetter[K any] interface {
// Get retrieves an int64 value.
Expand Down
89 changes: 89 additions & 0 deletions pkg/ottl/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1657,3 +1657,92 @@ func Test_StandardDurationGetter_WrappedError(t *testing.T) {
_, ok := err.(TypeError)
assert.False(t, ok)
}

func Test_StandardTimeGetter(t *testing.T) {
tests := []struct {
name string
getter StandardTimeGetter[interface{}]
want string
valid bool
expectedErrorMsg string
}{
{
name: "2023 time",
getter: StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(2023, 8, 17, 1, 1, 1, 1, time.UTC), nil
},
},
want: "2023-08-17T01:01:01.000000001Z",
valid: true,
},
{
name: "before 2000 time",
getter: StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(1999, 12, 1, 10, 59, 58, 57, time.UTC), nil
},
},
want: "1999-12-01T10:59:58.000000057Z",
valid: true,
},
{
name: "wrong type - duration",
getter: StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.ParseDuration("70ns")
},
},
valid: false,
expectedErrorMsg: "expected time but got time.Duration",
},
{
name: "wrong type - bool",
getter: StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return true, nil
},
},
valid: false,
expectedErrorMsg: "expected time but got bool",
},
{
name: "nil",
getter: StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return nil, nil
},
},
valid: false,
expectedErrorMsg: "expected time but got nil",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
val, err := tt.getter.Get(context.Background(), nil)
if tt.valid {
assert.NoError(t, err)
var want time.Time
want, err = time.Parse("2006-01-02T15:04:05.000000000Z", tt.want)
assert.NoError(t, err)
assert.Equal(t, want, val)
} else {
assert.ErrorContains(t, err, tt.expectedErrorMsg)
}
})
}
}

// nolint:errorlint
func Test_StandardTimeGetter_WrappedError(t *testing.T) {
getter := StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return nil, TypeError("")
},
}
_, err := getter.Get(context.Background(), nil)
assert.Error(t, err)
_, ok := err.(TypeError)
assert.False(t, ok)
}
38 changes: 38 additions & 0 deletions pkg/ottl/ottlfuncs/func_unix_micro.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"

import (
"context"
"fmt"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

type UnixMicroArguments[K any] struct {
Time ottl.TimeGetter[K] `ottlarg:"0"`
}

func NewUnixMicroFactory[K any]() ottl.Factory[K] {
return ottl.NewFactory("UnixMicro", &UnixMicroArguments[K]{}, createUnixMicroFunction[K])
}
func createUnixMicroFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
args, ok := oArgs.(*UnixMicroArguments[K])

if !ok {
return nil, fmt.Errorf("UnixMicroFactory args must be of type *UnixMicroArguments[K]")
}

return UnixMicro(args.Time)
}

func UnixMicro[K any](inputTime ottl.TimeGetter[K]) (ottl.ExprFunc[K], error) {
return func(ctx context.Context, tCtx K) (interface{}, error) {
t, err := inputTime.Get(ctx, tCtx)
if err != nil {
return nil, err
}
return t.UnixMicro(), nil
}, nil
}
69 changes: 69 additions & 0 deletions pkg/ottl/ottlfuncs/func_unix_micro_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

func Test_TimeUnixMicro(t *testing.T) {
tests := []struct {
name string
time ottl.TimeGetter[interface{}]
expected time.Time
}{
{
name: "January 1, 2023",
time: &ottl.StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(2023, 1, 1, 0, 0, 0, 0, time.Local), nil
},
},
expected: time.Date(2023, 1, 1, 0, 0, 0, 0, time.Local),
},
{
name: "April 30, 2001, 3pm",
time: &ottl.StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(2001, 4, 30, 15, 0, 0, 0, time.Local), nil
},
},
expected: time.Date(2001, 4, 30, 15, 0, 0, 0, time.Local),
},
{
name: "November 12, 1980, 4:35:01am",
time: &ottl.StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(1980, 11, 12, 4, 35, 1, 0, time.Local), nil
},
},
expected: time.Date(1980, 11, 12, 4, 35, 1, 0, time.Local),
},
{
name: "October 4, 2020, 5:05 5 microseconds 5 nanosecs",
time: &ottl.StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(2020, 10, 4, 5, 5, 5, 5, time.Local), nil
},
},
expected: time.Date(2020, 10, 4, 5, 5, 5, 5, time.Local),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exprFunc, err := UnixMicro(tt.time)
assert.NoError(t, err)
result, err := exprFunc(nil, nil)
assert.NoError(t, err)
want := tt.expected.UnixMicro()
assert.Equal(t, want, result)
})
}
}
38 changes: 38 additions & 0 deletions pkg/ottl/ottlfuncs/func_unix_milli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"

import (
"context"
"fmt"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

type UnixMilliArguments[K any] struct {
Time ottl.TimeGetter[K] `ottlarg:"0"`
}

func NewUnixMilliFactory[K any]() ottl.Factory[K] {
return ottl.NewFactory("UnixMilli", &UnixMilliArguments[K]{}, createUnixMilliFunction[K])
}
func createUnixMilliFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
args, ok := oArgs.(*UnixMilliArguments[K])

if !ok {
return nil, fmt.Errorf("UnixMilliFactory args must be of type *UnixMilliArguments[K]")
}

return UnixMilli(args.Time)
}

func UnixMilli[K any](inputTime ottl.TimeGetter[K]) (ottl.ExprFunc[K], error) {
return func(ctx context.Context, tCtx K) (interface{}, error) {
t, err := inputTime.Get(ctx, tCtx)
if err != nil {
return nil, err
}
return t.UnixMilli(), nil
}, nil
}
69 changes: 69 additions & 0 deletions pkg/ottl/ottlfuncs/func_unix_milli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

func Test_TimeUnixMilli(t *testing.T) {
tests := []struct {
name string
time ottl.TimeGetter[interface{}]
expected time.Time
}{
{
name: "January 1, 2022",
time: &ottl.StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local), nil
},
},
expected: time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local),
},
{
name: "May 30, 2002, 3pm",
time: &ottl.StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(2002, 5, 30, 15, 0, 0, 0, time.Local), nil
},
},
expected: time.Date(2002, 5, 30, 15, 0, 0, 0, time.Local),
},
{
name: "September 12, 1980, 4:35:01am",
time: &ottl.StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(1980, 9, 12, 4, 35, 1, 0, time.Local), nil
},
},
expected: time.Date(1980, 9, 12, 4, 35, 1, 0, time.Local),
},
{
name: "October 4, 2020, 5:05 5 microseconds 5 nanosecs",
time: &ottl.StandardTimeGetter[interface{}]{
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
return time.Date(2020, 10, 4, 5, 5, 5, 5, time.Local), nil
},
},
expected: time.Date(2020, 10, 4, 5, 5, 5, 5, time.Local),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exprFunc, err := UnixMilli(tt.time)
assert.NoError(t, err)
result, err := exprFunc(nil, nil)
assert.NoError(t, err)
want := tt.expected.UnixMilli()
assert.Equal(t, want, result)
})
}
}
Loading

0 comments on commit 3a6f547

Please sign in to comment.