Repository: stern/stern
Pull request #282 (2023-10-21 23:11:24)

281: Support toTimestamp template function
This PR adds support for a new template function `toTimestamp`, mentioned in #281. The `toTimestamp` function takes in an object, a layout, and optionally a timezone. This allows for more custom time parsing, for instance, if a user doesn't care about seeing the date of the log and only the time (in their own timezone) they can use a template such as:
```
--template '{{ with $msg := .Message | tryParseJSON }}[{{ toTimestamp $msg.time "15:04:05" "Local" }}] {{ $msg.msg }}{{ end }}{{ "\n" }}'
```


Patch:
diff --git a/README.md b/README.md
index 3bba481..ca2ffba 100644
--- a/README.md
+++ b/README.md
@@ -160,6 +160,7 @@ functions](https://golang.org/pkg/text/template/#hdr-Functions)):
 | `extjson`       | `string`              | Parse the object as json and output colorized json                                |
 | `ppextjson`     | `string`              | Parse the object as json and output pretty-print colorized json                   |
 | `toRFC3339Nano` | `object`              | Parse timestamp (string, int, json.Number) and output it using RFC3339Nano format |
+| `toTimestamp`   | `object, string [, string]` | Parse timestamp (string, int, json.Number) and output it using the given layout in the timezone that is optionally given (defaults to UTC). |
 | `levelColor`    | `string`              | Print log level using appropriate color                                           |
 | `colorBlack`    | `string`              | Print text using black color                                                      |
 | `colorRed`      | `string`              | Print text using red color                                                        |
diff --git a/cmd/cmd.go b/cmd/cmd.go
index f2fd212..5703661 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -488,6 +488,24 @@ func (o *options) generateTemplate() (*template.Template, error) {
 		"toUTC": func(ts any) time.Time {
 			return cast.ToTime(ts).UTC()
 		},
+		"toTimestamp": func(ts any, layout string, optionalTZ ...string) (string, error) {
+			t, parseErr := cast.ToTimeE(ts)
+			if parseErr != nil {
+				return "", parseErr
+			}
+
+			var tz string
+			if len(optionalTZ) > 0 {
+				tz = optionalTZ[0]
+			}
+
+			loc, loadErr := time.LoadLocation(tz)
+			if loadErr != nil {
+				return "", loadErr
+			}
+
+			return t.In(loc).Format(layout), nil
+		},
 		"color": func(color color.Color, text string) string {
 			return color.SprintFunc()(text)
 		},


Test patch:
diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go
index e6c83e8..2b1af25 100644
--- a/cmd/cmd_test.go
+++ b/cmd/cmd_test.go
@@ -376,6 +376,28 @@ func TestOptionsGenerateTemplate(t *testing.T) {
 			"pod1 container1 [1970-01-01T00:02:03Z] INFO template message",
 			false,
 		},
+		{
+			"template-to-timestamp-with-timezone",
+			func() *options {
+				o := NewOptions(streams)
+				o.template = `{{ toTimestamp .Message "Jan 02 2006 15:04 MST" "US/Eastern" }}`
+				return o
+			}(),
+			`2024-01-01T05:00:00`,
+			`Jan 01 2024 00:00 EST`,
+			false,
+		},
+		{
+			"template-to-timestamp-without-timezone",
+			func() *options {
+				o := NewOptions(streams)
+				o.template = `{{ toTimestamp .Message "Jan 02 2006 15:04 MST" }}`
+				return o
+			}(),
+			`2024-01-01T05:00:00`,
+			`Jan 01 2024 05:00 UTC`,
+			false,
+		},
 	}
 
 	for _, tt := range tests {