192 lines
4.8 KiB
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
|
|
}
|
|
|