text/expander-time-funcs.go

192 lines
4.8 KiB
Go

// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expander-time-funcs.go
package text
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
)
const func_time_fmt = "__time-fmt"
/*
If format is empty, the default template will be used.
*/
func TimeFmt(when time.Time, format string) (result string, err error) {
var sb strings.Builder
var sep byte = 0
var filler byte
var field int
if len(format) == 0 {
format = "%Y-%m-%d %H:%M:%S"
}
perc := false
i := 0
for i < len(format) && err == nil {
ch := format[i]
if ch == '%' {
if perc {
sb.WriteByte(ch)
perc = false
} else {
perc = true
}
} else if perc {
perc = false
switch ch {
case '.', '-', '/', '_', ':':
if field > 0 {
err = fmt.Errorf("invalid time specifier format at %d", i)
} else {
sep = ch
j := i + 1
if j < len(format) && (format[j] == '0' || format[j] == ' ') {
filler = format[j]
j++
start := j
for j < len(format) && format[j] >= '0' && format[j] <= '9' {
j++
}
field, err = strconv.Atoi(format[start:j])
i = j - 1
}
perc = true
}
case '0', ' ':
filler = ch
j := i + 1
for j < len(format) && format[j] >= '0' && format[j] <= '9' {
j++
}
field, err = strconv.Atoi(format[i+1 : j])
i = j - 1
perc = true
default:
switch ch {
case 'D':
if filler != 0 {
templ := fmt.Sprintf("%%d%c%%%c%dd%c%%%c%dd", sep, filler, field, sep, filler, field)
_, err = sb.WriteString(fmt.Sprintf(templ, when.Year(), when.Month(), when.Day()))
} else {
_, err = sb.WriteString(fmt.Sprintf("%d%c%*d%c%*d", when.Year(), sep, field, when.Month(), sep, field, when.Day()))
}
case 'T':
if filler != 0 {
templ := fmt.Sprintf("%%%c%dd%c%%%c%dd%c%%%c%dd", filler, field, sep, filler, field, sep, filler, field)
_, err = sb.WriteString(fmt.Sprintf(templ, when.Hour(), when.Minute(), when.Second()))
} else {
_, err = sb.WriteString(fmt.Sprintf("%d%c%*d%c%*d", when.Hour(), sep, field, when.Minute(), sep, field, when.Second()))
}
case 'Y':
_, err = sb.WriteString(fmt.Sprintf("%d", when.Year()))
case 'm':
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Month()))
case 'd':
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Day()))
case 'H':
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Hour()))
case 'M':
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Minute()))
case 'S':
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Second()))
default:
err = fmt.Errorf("unsupported '%c' time specifier at %q/%d", ch, format, i+1)
}
filler = 0
field = 0
sep = 0
}
} else {
err = sb.WriteByte(ch)
}
i++
}
if err == nil {
result = sb.String()
}
return
}
func parseDelta(source string) (delta time.Duration) {
// TODO implementare
var err error
if delta, err = time.ParseDuration(strings.TrimSpace(source)); err != nil {
delta = 0
}
return
}
// Format a time value using a template (see TimeFmt()).
// __time-fmt <time> <template>
// <time> can be:
//
// now
// yesterday
// @<timestamp>, e.g. @1707889716
// @., where '.' will be replaced by hte pipeline value
func ExFuncTimeFmt(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
var format string
var whenAsString string
var when time.Time
if len(args) == 0 {
whenAsString = "now"
} else {
whenAsString = args[0]
if len(args) > 1 {
format = args[1]
}
}
if len(whenAsString) == 0 {
err = errors.New("time value is empty")
return
}
switch {
case strings.HasPrefix(whenAsString, "now"):
delta := parseDelta(whenAsString[3:])
when = time.Now().Add(delta)
case strings.HasPrefix(whenAsString, "yesterday"):
delta := parseDelta(whenAsString[9:])
when = time.Now().Add(-24 * time.Hour).Add(delta)
case strings.HasPrefix(whenAsString, "@."):
var ts int64
if ts, err = strconv.ParseInt(varValue, 10, 64); err != nil {
return
}
delta := parseDelta(whenAsString[2:])
when = time.UnixMilli(ts * 1000).Add(delta)
case strings.HasPrefix(whenAsString, "@"):
var ts int64
var i int
for i = 1; i < len(whenAsString) && whenAsString[i] >= '0' && whenAsString[i] <= '9'; i++ {
}
if ts, err = strconv.ParseInt(whenAsString[1:i], 10, 64); err != nil {
return
}
delta := parseDelta(whenAsString[i:])
when = time.UnixMilli(ts * 1000).Add(delta)
default:
err = fmt.Errorf("time specifier '%s' not supported", whenAsString)
return
}
result, err = TimeFmt(when, format)
return
}
func AddTimeFuncs(ctx ExpanderContext) ExpanderContext {
ctx.AddFunc(func_time_fmt, ExFuncTimeFmt, 1, 2, "Formats the specified time value (arg1) according with the format specifier (arg2 or default).")
return ctx
}