Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add strftime function #5197

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions docs/language/functions/strftime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
### Function

  **strftime** — format time values

### Synopsis
```
strftime(format: string, t: time) -> string
```

### Description
The _strftime_ function returns a string represenation of time `t`
as specified by the provided string `format`. `format` is a string
containing format directives that dictate how the time string is
formatted.

These directives are supported:

| Directive | Explanation | Example |
|-----------|-------------|---------|
| %A | Weekday as full name | Sunday, Monday, ..., Saturday |
| %a | Weekday as abbreviated name | Sun, Mon, ..., Sat |
| %B | Month as full name | January, February, ..., December |
| %b | Month as abbreviated name | Jan, Feb, ..., Dec |
| %C | Century number (year / 100) as a 2-digit integer | 20 |
| %c | Locale's appropriate date and time representation | Tue Jul 30 14:30:15 2024 |
| %D | Equivalent to `%m/%d/%y` | 7/30/24 |
| %d | Day of the month as a zero-padded decimal number | 01, 02, ..., 31 |
| %e | Day of the month as a decimal number (1-31); single digits are preceded by a blank | 1, 2, ..., 31 |
| %F | Equivalent to `%Y-%m-%d` | 2024-07-30 |
| %H | Hour (24-hour clock) as a zero-padded decimal number | 00, 01, ..., 23 |
| %I | Hour (12-hour clock) as a zero-padded decimal number | 00, 01, ..., 12 |
| %j | Day of the year as a zero-padded decimal number | 001, 002, ..., 366 |
| %k | Hour (24-hour clock) as a decimal number; single digits are preceded by a blank | 0, 1, ..., 23 |
| %l | Hour (12-hour clock) as a decimal number; single digits are preceded by a blank | 0, 1, ..., 12 |
| %M | Minute as a zero-padded decimal number | 00, 01, ..., 59 |
| %m | Month as a zero-padded decimal number | 01, 02, ..., 12 |
| %n | Newline character | \n |
| %p | "ante meridiem" (a.m.) or "post meridiem" (p.m.) | AM, PM |
| %R | Equivalent to `%H:%M` | 18:49 |
| %r | Equivalent to `%I:%M:%S %p` | 06:50:58 PM |
| %S | Second as a zero-padded decimal number | 00, 01, ..., 59 |
| %T | Equivalent to `%H:%M:%S` | 18:50:58 |
| %t | Tab character | \t |
| %U | Week number of the year (Sunday as the first day of the week) | 00, 01, ..., 53 |
| %u | Weekday as a decimal number, range 1 to 7, with Monday being 1 | 1, 2, ..., 7 |
| %V | Week number of the year (Monday as the first day of the week) as a decimal number (01-53) | 01, 02, ..., 53 |
| %v | Equivalent to `%e-%b-%Y` | 31-Jul-2024 |
| %W | Week number of the year (Monday as the first day of the week) | 00, 01, ..., 53 |
| %w | Weekday as a decimal number, range 0 to 6, with Sunday being 0 | 0, 1, ..., 6 |
| %X | Locale's appropriate time representation | 14:30:15 |
| %x | Locale's appropriate date representation | 07/30/24 |
| %Y | Year with century as a decimal number | 2024 |
| %y | Year without century as a decimal number | 24, 23 |
| %Z | Timezone name | UTC |
| %z | +hhmm or -hhmm numeric timezone (that is, the hour and minute offset from UTC) | +0000 |
| %% | A literal '%' character | % |

### Examples

Print the year number as a string
```mdtest-command
echo 2024-07-30T20:05:15.118252Z | zq -z 'strftime("%Y", this)' -
```
=>
```mdtest-output
"2024"
```
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/gosuri/uilive v0.0.4
github.com/hashicorp/golang-lru/v2 v2.0.1
github.com/kr/text v0.2.0
github.com/lestrrat-go/strftime v1.0.6
github.com/paulbellamy/ratecounter v0.2.0
github.com/pbnjay/memory v0.0.0-20190104145345-974d429e7ae4
github.com/peterh/liner v1.1.0
Expand Down Expand Up @@ -61,6 +62,7 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rivo/uniseg v0.1.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
Expand Down
3 changes: 3 additions & 0 deletions runtime/sam/expr/function/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ func New(zctx *zed.Context, name string, narg int) (expr.Function, field.Path, e
case "regexp_replace":
argmin, argmax = 3, 3
f = &RegexpReplace{zctx: zctx}
case "strftime":
argmin, argmax = 2, 2
f = &Strftime{zctx: zctx}
case "under":
f = &Under{zctx: zctx}
case "unflatten":
Expand Down
26 changes: 26 additions & 0 deletions runtime/sam/expr/function/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/brimdata/zed/pkg/nano"
"github.com/brimdata/zed/runtime/sam/expr"
"github.com/brimdata/zed/runtime/sam/expr/coerce"
"github.com/lestrrat-go/strftime"
)

// https://github.com/brimdata/zed/blob/main/docs/language/functions.md#now
Expand Down Expand Up @@ -46,3 +47,28 @@ func (b *Bucket) Call(ectx expr.Context, args []zed.Value) zed.Value {
}
return zed.NewTime(nano.Ts(v).Trunc(bin))
}

// https://github.com/brimdata/zed/blob/main/docs/language/functions.md#strftime
type Strftime struct {
zctx *zed.Context
formatter *strftime.Strftime
}

func (s *Strftime) Call(ectx expr.Context, args []zed.Value) zed.Value {
formatArg, timeArg := args[0], args[1]
if !formatArg.IsString() {
return s.zctx.WrapError(ectx.Arena(), "strftime: string value required for format arg", formatArg)
}
if zed.TypeUnder(timeArg.Type()) != zed.TypeTime {
return s.zctx.WrapError(ectx.Arena(), "strftime: time value required for time arg", args[1])
}
format := formatArg.AsString()
if s.formatter == nil || s.formatter.Pattern() != format {
var err error
if s.formatter, err = strftime.New(format); err != nil {
return s.zctx.WrapError(ectx.Arena(), "strftime: "+err.Error(), formatArg)
}
}
out := s.formatter.FormatString(timeArg.AsTime().Time())
return ectx.Arena().NewString(out)
}
13 changes: 13 additions & 0 deletions runtime/sam/expr/function/ztests/strftime.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
zed: 'strftime(f, v)'

input: |
{f:"%Y-%a",v:2024-07-30T06:15:01.062681Z}
{f:1,v:2024-07-30T06:15:01.062681Z}
mattnibs marked this conversation as resolved.
Show resolved Hide resolved
{f:"%H",v:"foo"}
{f:"%1",v:2024-07-30T06:15:01.062681Z}

output: |
"2024-Tue"
error({message:"strftime: string value required for format arg",on:1})
error({message:"strftime: time value required for time arg",on:"foo"})
error({message:"strftime: failed to compile format: pattern compilation failed: lookup failed: '%1' was not found in specification set",on:"%1"})