From 75920481b1128a9715975d4ab0c631eeab37d6c4 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Thu, 2 Feb 2023 12:42:36 +1100 Subject: [PATCH] Added from_unix operator #1535 --- pkg/yqlib/doc/operators/datetime.md | 12 +++++++++ pkg/yqlib/lexer_participle.go | 1 + pkg/yqlib/lib.go | 1 + pkg/yqlib/operator_datetime.go | 41 +++++++++++++++++++++++++++++ pkg/yqlib/operator_datetime_test.go | 8 ++++++ 5 files changed, 63 insertions(+) diff --git a/pkg/yqlib/doc/operators/datetime.md b/pkg/yqlib/doc/operators/datetime.md index 0dc3ed5e..3a2fb72d 100644 --- a/pkg/yqlib/doc/operators/datetime.md +++ b/pkg/yqlib/doc/operators/datetime.md @@ -86,6 +86,18 @@ a: cool updated: 2021-05-19T01:02:03Z ``` +## From Unix +Converts from unix time + +Running +```bash +yq --null-input '1675301929 | from_unix' +``` +will output +```yaml +2023-02-02T12:38:49+11:00 +``` + ## Timezone: from standard RFC3339 format Returns a new datetime in the specified timezone. Specify standard IANA Time Zone format or 'utc', 'local'. When given a single parameter, this assumes the datetime is in RFC3339 format. diff --git a/pkg/yqlib/lexer_participle.go b/pkg/yqlib/lexer_participle.go index 09e9dd75..cedbd110 100644 --- a/pkg/yqlib/lexer_participle.go +++ b/pkg/yqlib/lexer_participle.go @@ -45,6 +45,7 @@ var participleYqRules = []*participleYqRule{ simpleOp("format_datetime", formatDateTimeOpType), simpleOp("now", nowOpType), simpleOp("tz", tzOpType), + simpleOp("from_?unix", fromUnixOpType), simpleOp("with_dtf", withDtFormatOpType), simpleOp("error", errorOpType), simpleOp("sortKeys", sortKeysOpType), diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 1bb7f405..12b299fd 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -93,6 +93,7 @@ var formatDateTimeOpType = &operationType{Type: "FORMAT_DATE_TIME", NumArgs: 1, var withDtFormatOpType = &operationType{Type: "WITH_DATE_TIME_FORMAT", NumArgs: 1, Precedence: 50, Handler: withDateTimeFormat} var nowOpType = &operationType{Type: "NOW", NumArgs: 0, Precedence: 50, Handler: nowOp} var tzOpType = &operationType{Type: "TIMEZONE", NumArgs: 1, Precedence: 50, Handler: tzOp} +var fromUnixOpType = &operationType{Type: "FROM_UNIX", NumArgs: 0, Precedence: 50, Handler: fromUnixOp} var encodeOpType = &operationType{Type: "ENCODE", NumArgs: 0, Precedence: 50, Handler: encodeOperator} var decodeOpType = &operationType{Type: "DECODE", NumArgs: 0, Precedence: 50, Handler: decodeOperator} diff --git a/pkg/yqlib/operator_datetime.go b/pkg/yqlib/operator_datetime.go index c3ec05f6..f639c540 100644 --- a/pkg/yqlib/operator_datetime.go +++ b/pkg/yqlib/operator_datetime.go @@ -4,6 +4,7 @@ import ( "container/list" "errors" "fmt" + "strconv" "time" "gopkg.in/yaml.v3" @@ -129,3 +130,43 @@ func tzOp(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) return context.ChildContext(results), nil } + +func parseUnixTime(unixTime string) (time.Time, error) { + seconds, err := strconv.ParseFloat(unixTime, 64) + + if err != nil { + return time.Now(), err + } + + return time.UnixMilli(int64(seconds * 1000)), nil +} + +func fromUnixOp(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + + var results = list.New() + + for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + + actualTag := guessTagFromCustomType(candidate.Node) + + if actualTag != "!!int" && guessTagFromCustomType(candidate.Node) != "!!float" { + return Context{}, fmt.Errorf("from_unix only works on numbers, found %v instead", candidate.Node.Tag) + } + + parsedTime, err := parseUnixTime(candidate.Node.Value) + if err != nil { + return Context{}, err + } + + node := &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: "!!timestamp", + Value: parsedTime.Format(time.RFC3339), + } + + results.PushBack(candidate.CreateReplacement(node)) + } + + return context.ChildContext(results), nil +} diff --git a/pkg/yqlib/operator_datetime_test.go b/pkg/yqlib/operator_datetime_test.go index a1d2692f..74b81390 100644 --- a/pkg/yqlib/operator_datetime_test.go +++ b/pkg/yqlib/operator_datetime_test.go @@ -39,6 +39,14 @@ var dateTimeOperatorScenarios = []expressionScenario{ "D0, P[], (doc)::a: cool\nupdated: 2021-05-19T01:02:03Z\n", }, }, + { + description: "From Unix", + subdescription: "Converts from unix time", + expression: `1675301929 | from_unix`, + expected: []string{ + "D0, P[], (!!timestamp)::2023-02-02T12:38:49+11:00\n", + }, + }, { description: "Timezone: from standard RFC3339 format", subdescription: "Returns a new datetime in the specified timezone. Specify standard IANA Time Zone format or 'utc', 'local'. When given a single parameter, this assumes the datetime is in RFC3339 format.",