diff --git a/pkg/ottl/e2e/e2e_test.go b/pkg/ottl/e2e/e2e_test.go index 72077e66dd23..8fbcd880ff7c 100644 --- a/pkg/ottl/e2e/e2e_test.go +++ b/pkg/ottl/e2e/e2e_test.go @@ -856,6 +856,12 @@ func Test_e2e_converters(t *testing.T) { tCtx.GetLogRecord().SetTimestamp(pcommon.NewTimestampFromTime(TestLogTimestamp.AsTime().Truncate(time.Second))) }, }, + { + statement: `set(attributes["time"],Timestamp(time, "%Y-%m-%d"))`, + want: func(tCtx ottllog.TransformContext) { + tCtx.GetLogRecord().Attributes().PutStr("time", "2020-02-11") + }, + }, { statement: `set(attributes["test"], "pass") where UnixMicro(time) > 0`, want: func(tCtx ottllog.TransformContext) { diff --git a/pkg/ottl/ottlfuncs/README.md b/pkg/ottl/ottlfuncs/README.md index 1ffac1a17847..d734ed03db69 100644 --- a/pkg/ottl/ottlfuncs/README.md +++ b/pkg/ottl/ottlfuncs/README.md @@ -463,6 +463,7 @@ Available Converters: - [String](#string) - [Substring](#substring) - [Time](#time) +- [Timestamp](#timestamp) - [ToKeyValueString](#tokeyvaluestring) - [TraceID](#traceid) - [TruncateTime](#truncatetime) @@ -1933,7 +1934,59 @@ Examples: ### Timestamp -TODO +`Timestamp(time, format)` + +The `Timestamp` Converter takes a `time.Time` and converts it to a human readable string representations of the time according to the specidied format. + +`time` is `time.Time`. If `time` is another type an error is returned. `format` is a string. + +If `format` is nil, an error is returned. The parser used is the parser at [internal/coreinternal/parser](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/internal/coreinternal/timeutils). If `format` does not follow the parsing rules used by this parser, an error is returned. + +`format` denotes a textual and human readable representation of the resulting time value formatted according to ctime-like format string. It follows [standard Go Layout formatting](https://pkg.go.dev/time#pkg-constants) with few additional substitutes: +| substitution | description | examples | +|-----|-----|-----| +|`%Y` | Year as a zero-padded number | 0001, 0002, ..., 2019, 2020, ..., 9999 | +|`%y` | Year, last two digits as a zero-padded number | 01, ..., 99 | +|`%m` | Month as a zero-padded number | 01, 02, ..., 12 | +|`%o` | Month as a space-padded number | 1, 2, ..., 12 | +|`%q` | Month as an unpadded number | 1,2,...,12 | +|`%b`, `%h` | Abbreviated month name | Jan, Feb, ... | +|`%B` | Full month name | January, February, ... | +|`%d` | Day of the month as a zero-padded number | 01, 02, ..., 31 | +|`%e` | Day of the month as a space-padded number| 1, 2, ..., 31 | +|`%g` | Day of the month as a unpadded number | 1,2,...,31 | +|`%a` | Abbreviated weekday name | Sun, Mon, ... | +|`%A` | Full weekday name | Sunday, Monday, ... | +|`%H` | Hour (24-hour clock) as a zero-padded number | 00, ..., 24 | +|`%I` | Hour (12-hour clock) as a zero-padded number | 00, ..., 12 | +|`%l` | Hour 12-hour clock | 0, ..., 24 | +|`%p` | Locale’s equivalent of either AM or PM | AM, PM | +|`%P` | Locale’s equivalent of either am or pm | am, pm | +|`%M` | Minute as a zero-padded number | 00, 01, ..., 59 | +|`%S` | Second as a zero-padded number | 00, 01, ..., 59 | +|`%L` | Millisecond as a zero-padded number | 000, 001, ..., 999 | +|`%f` | Microsecond as a zero-padded number | 000000, ..., 999999 | +|`%s` | Nanosecond as a zero-padded number | 00000000, ..., 99999999 | +|`%z` | UTC offset in the form ±HHMM[SS[.ffffff]] or empty | +0000, -0400 | +|`%Z` | Timezone name or abbreviation or empty | UTC, EST, CST | +|`%i` | Timezone as +/-HH | -07 | +|`%j` | Timezone as +/-HH:MM | -07:00 | +|`%k` | Timezone as +/-HH:MM:SS | -07:00:00 | +|`%w` | Timezone as +/-HHMMSS | -070000 | +|`%D`, `%x` | Short MM/DD/YYYY date, equivalent to %m/%d/%y | 01/21/2031 | +|`%F` | Short YYYY-MM-DD date, equivalent to %Y-%m-%d | 2031-01-21 | +|`%T`,`%X` | ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S | 02:55:02 | +|`%r` | 12-hour clock time | 02:55:02 pm | +|`%R` | 24-hour HH:MM time, equivalent to %H:%M | 13:55 | +|`%n` | New-line character ('\n') | | +|`%t` | Horizontal-tab character ('\t') | | +|`%%` | A % sign | | +|`%c` | Date and time representation | Mon Jan 02 15:04:05 2006 | + +Examples: + +- `Timestamp("123456543534334", "%A %h %e %Y")` +- `Timestamp(5874359837485873843954835, "%B %d %A, %Y, %r")` ### ToKeyValueString diff --git a/pkg/ottl/ottlfuncs/func_timestamp.go b/pkg/ottl/ottlfuncs/func_timestamp.go index 904bee671023..425e9e165ece 100644 --- a/pkg/ottl/ottlfuncs/func_timestamp.go +++ b/pkg/ottl/ottlfuncs/func_timestamp.go @@ -6,14 +6,13 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" "fmt" - "time" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/timeutils" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) type TimestampArguments[K any] struct { - Time ottl.IntLikeGetter[K] + Time ottl.TimeGetter[K] Format string } @@ -31,7 +30,7 @@ func createTimestampFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments return Timestamp(args.Time, args.Format) } -func Timestamp[K any](timeValue ottl.IntLikeGetter[K], format string) (ottl.ExprFunc[K], error) { +func Timestamp[K any](timeValue ottl.TimeGetter[K], format string) (ottl.ExprFunc[K], error) { if format == "" { return nil, fmt.Errorf("format cannot be nil") } @@ -46,12 +45,7 @@ func Timestamp[K any](timeValue ottl.IntLikeGetter[K], format string) (ottl.Expr if err != nil { return nil, err } - if t == nil { - return nil, fmt.Errorf("time cannot be nil") - } - - unixTime := time.Unix(0, *t) - return unixTime.Format(gotimeFormat), nil + return t.Format(gotimeFormat), nil }, nil } diff --git a/pkg/ottl/ottlfuncs/func_timestamp_test.go b/pkg/ottl/ottlfuncs/func_timestamp_test.go index c79f5c5355b9..b4b64e5616f1 100644 --- a/pkg/ottl/ottlfuncs/func_timestamp_test.go +++ b/pkg/ottl/ottlfuncs/func_timestamp_test.go @@ -16,7 +16,7 @@ import ( func Test_Timestamp(t *testing.T) { tests := []struct { name string - time ottl.IntLikeGetter[any] + time ottl.TimeGetter[any] format string expected string errorMsg string @@ -24,25 +24,25 @@ func Test_Timestamp(t *testing.T) { }{ { name: "empty format", - time: &ottl.StandardIntLikeGetter[any]{}, + time: &ottl.StandardTimeGetter[any]{}, format: "", errorMsg: "format cannot be nil", }, { - name: "nil time", - time: &ottl.StandardIntLikeGetter[any]{ + name: "invalid time", + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { return "something", nil }, }, format: "%Y-%m-%d", - funcErrorMsg: "time cannot be nil", + funcErrorMsg: "expected time but got string", }, { name: "simple short form", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2023, 4, 12, 0, 0, 0, 0, time.Local).UnixNano(), nil + return time.Date(2023, 4, 12, 0, 0, 0, 0, time.Local), nil }, }, format: "%Y-%m-%d", @@ -50,9 +50,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "simple short form with short year and slashes", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2011, 11, 11, 0, 0, 0, 0, time.Local).UnixNano(), nil + return time.Date(2011, 11, 11, 0, 0, 0, 0, time.Local), nil }, }, format: "%d/%m/%y", @@ -60,9 +60,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "month day year", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2023, 2, 4, 0, 0, 0, 0, time.Local).UnixNano(), nil + return time.Date(2023, 2, 4, 0, 0, 0, 0, time.Local), nil }, }, format: "%m/%d/%Y", @@ -70,9 +70,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "simple long form", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(1993, 7, 31, 0, 0, 0, 0, time.Local).UnixNano(), nil + return time.Date(1993, 7, 31, 0, 0, 0, 0, time.Local), nil }, }, format: "%B %d, %Y", @@ -80,9 +80,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "date with timestamp", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2023, 3, 14, 17, 0o2, 59, 0, time.Local).UnixNano(), nil + return time.Date(2023, 3, 14, 17, 0o2, 59, 0, time.Local), nil }, }, format: "%b %d %Y %H:%M:%S", @@ -90,9 +90,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "day of the week long form", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2023, 5, 1, 0, 0, 0, 0, time.Local).UnixNano(), nil + return time.Date(2023, 5, 1, 0, 0, 0, 0, time.Local), nil }, }, format: "%A, %B %d, %Y", @@ -100,9 +100,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "short weekday, short month, long format", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2023, 5, 20, 0, 0, 0, 0, time.Local).UnixNano(), nil + return time.Date(2023, 5, 20, 0, 0, 0, 0, time.Local), nil }, }, format: "%a, %b %d, %Y", @@ -110,9 +110,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "short months", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2023, 2, 15, 0, 0, 0, 0, time.Local).UnixNano(), nil + return time.Date(2023, 2, 15, 0, 0, 0, 0, time.Local), nil }, }, format: "%b %d, %Y", @@ -120,9 +120,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "simple short form with time", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2023, 5, 26, 12, 34, 56, 0, time.Local).UnixNano(), nil + return time.Date(2023, 5, 26, 12, 34, 56, 0, time.Local), nil }, }, format: "%Y-%m-%d %H:%M:%S", @@ -130,9 +130,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "RFC 3339 in custom format", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(2012, 11, 0o1, 22, 8, 41, 0, time.Local).UnixNano(), nil + return time.Date(2012, 11, 0o1, 22, 8, 41, 0, time.Local), nil }, }, format: "%Y-%m-%dT%H:%M:%S", @@ -140,9 +140,9 @@ func Test_Timestamp(t *testing.T) { }, { name: "RFC 3339 in custom format before 2000", - time: &ottl.StandardIntLikeGetter[any]{ + time: &ottl.StandardTimeGetter[any]{ Getter: func(_ context.Context, _ any) (any, error) { - return time.Date(1986, 10, 0o1, 0o0, 17, 33, 0o0, time.Local).UnixNano(), nil + return time.Date(1986, 10, 0o1, 0o0, 17, 33, 0o0, time.Local), nil }, }, format: "%Y-%m-%dT%H:%M:%S",