2022-03-29 07:53:20 -04:00
|
|
|
package zenutil
|
|
|
|
|
2022-03-29 20:28:19 -04:00
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
2022-03-29 07:53:20 -04:00
|
|
|
|
2022-03-29 09:09:01 -04:00
|
|
|
// Strftime is internal.
|
2022-03-29 20:28:19 -04:00
|
|
|
func Strftime(fmt string, t time.Time) string {
|
|
|
|
var res strings.Builder
|
|
|
|
writeLit := res.WriteByte
|
|
|
|
writeFmt := func(fmt string) (int, error) {
|
|
|
|
return res.WriteString(t.Format(fmt))
|
|
|
|
}
|
|
|
|
strftimeGo(fmt, writeLit, writeFmt)
|
|
|
|
return res.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// StrftimeLayout is internal.
|
|
|
|
func StrftimeLayout(fmt string) string {
|
|
|
|
var res strings.Builder
|
|
|
|
strftimeGo(fmt, res.WriteByte, res.WriteString)
|
|
|
|
return res.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func strftimeGo(fmt string, writeLit func(byte) error, writeFmt func(string) (int, error)) {
|
2022-03-29 09:09:01 -04:00
|
|
|
// https://strftime.org/
|
2022-03-29 20:28:19 -04:00
|
|
|
fmts := map[byte]string{
|
2022-03-29 09:09:01 -04:00
|
|
|
'B': "January",
|
|
|
|
'b': "Jan",
|
|
|
|
'h': "Jan",
|
|
|
|
'm': "01",
|
|
|
|
'A': "Monday",
|
|
|
|
'a': "Mon",
|
|
|
|
'e': "_2",
|
|
|
|
'd': "02",
|
|
|
|
'j': "002",
|
|
|
|
'H': "15",
|
|
|
|
'I': "03",
|
|
|
|
'M': "04",
|
|
|
|
'S': "05",
|
|
|
|
'Y': "2006",
|
|
|
|
'y': "06",
|
|
|
|
'p': "PM",
|
|
|
|
'Z': "MST",
|
|
|
|
'z': "-0700",
|
|
|
|
'L': "000",
|
|
|
|
'f': "000000",
|
|
|
|
'N': "000000000",
|
|
|
|
|
|
|
|
'+': "Mon Jan _2 15:04:05 MST 2006",
|
|
|
|
'c': "Mon Jan _2 15:04:05 2006",
|
|
|
|
'F': "2006-01-02",
|
|
|
|
'D': "01/02/06",
|
|
|
|
'x': "01/02/06",
|
|
|
|
'r': "03:04:05 PM",
|
|
|
|
'T': "15:04:05",
|
|
|
|
'X': "15:04:05",
|
|
|
|
'R': "15:04",
|
|
|
|
|
|
|
|
'%': "%",
|
|
|
|
't': "\t",
|
|
|
|
'n': LineBreak,
|
2022-03-29 20:28:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
unpaded := map[byte]string{
|
2022-03-29 09:09:01 -04:00
|
|
|
'm': "1",
|
|
|
|
'd': "2",
|
|
|
|
'I': "3",
|
|
|
|
'M': "4",
|
|
|
|
'S': "5",
|
2022-03-29 20:28:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
parser(fmt, fmts, unpaded, writeLit, writeFmt)
|
2022-03-29 09:09:01 -04:00
|
|
|
}
|
2022-03-29 07:53:20 -04:00
|
|
|
|
2022-03-29 09:09:01 -04:00
|
|
|
// StrftimeUTS35 is internal.
|
|
|
|
func StrftimeUTS35(fmt string) string {
|
|
|
|
// https://nsdateformatter.com/
|
2022-03-29 20:28:19 -04:00
|
|
|
fmts := map[byte]string{
|
2022-03-29 09:09:01 -04:00
|
|
|
'B': "MMMM",
|
|
|
|
'b': "MMM",
|
|
|
|
'h': "MMM",
|
|
|
|
'm': "MM",
|
|
|
|
'A': "EEEE",
|
|
|
|
'a': "E",
|
|
|
|
'd': "dd",
|
|
|
|
'j': "DDD",
|
|
|
|
'H': "HH",
|
|
|
|
'I': "hh",
|
|
|
|
'M': "mm",
|
|
|
|
'S': "ss",
|
|
|
|
'Y': "yyyy",
|
|
|
|
'y': "yy",
|
|
|
|
'G': "YYYY",
|
|
|
|
'g': "YY",
|
|
|
|
'V': "ww",
|
|
|
|
'p': "a",
|
|
|
|
'Z': "zzz",
|
|
|
|
'z': "Z",
|
|
|
|
'L': "SSS",
|
|
|
|
'f': "SSSSSS",
|
|
|
|
'N': "SSSSSSSSS",
|
|
|
|
|
|
|
|
'+': "E MMM d HH:mm:ss zzz yyyy",
|
|
|
|
'c': "E MMM d HH:mm:ss yyyy",
|
|
|
|
'F': "yyyy-MM-dd",
|
|
|
|
'D': "MM/dd/yy",
|
|
|
|
'x': "MM/dd/yy",
|
|
|
|
'r': "hh:mm:ss a",
|
|
|
|
'T': "HH:mm:ss",
|
|
|
|
'X': "HH:mm:ss",
|
|
|
|
'R': "HH:mm",
|
|
|
|
|
|
|
|
'%': "%",
|
|
|
|
't': "\t",
|
|
|
|
'n': LineBreak,
|
2022-03-29 20:28:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
unpaded := map[byte]string{
|
2022-03-29 09:09:01 -04:00
|
|
|
'm': "M",
|
|
|
|
'd': "d",
|
|
|
|
'j': "D",
|
|
|
|
'H': "H",
|
|
|
|
'I': "h",
|
|
|
|
'M': "m",
|
|
|
|
'S': "s",
|
2022-03-29 20:28:19 -04:00
|
|
|
}
|
2022-03-29 07:53:20 -04:00
|
|
|
|
2022-03-29 20:28:19 -04:00
|
|
|
const quote = '\''
|
|
|
|
var literal bool
|
2022-03-29 07:53:20 -04:00
|
|
|
var res strings.Builder
|
2022-03-29 20:28:19 -04:00
|
|
|
|
|
|
|
writeLit := func(b byte) error {
|
|
|
|
if b == quote {
|
|
|
|
res.WriteByte(quote)
|
|
|
|
return res.WriteByte(quote)
|
|
|
|
}
|
|
|
|
if !literal && ('a' <= b && b <= 'z' || 'A' <= b && b <= 'Z') {
|
|
|
|
literal = true
|
|
|
|
res.WriteByte(quote)
|
|
|
|
}
|
|
|
|
return res.WriteByte(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
writeFmt := func(s string) (int, error) {
|
|
|
|
if literal {
|
|
|
|
literal = false
|
|
|
|
res.WriteByte(quote)
|
|
|
|
}
|
|
|
|
return res.WriteString(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
parser(fmt, fmts, unpaded, writeLit, writeFmt)
|
|
|
|
writeFmt("")
|
|
|
|
|
|
|
|
return res.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func parser(
|
|
|
|
fmt string,
|
|
|
|
formats, unpadded map[byte]string,
|
|
|
|
writeLit func(byte) error, writeFmt func(string) (int, error)) {
|
2022-03-29 07:53:20 -04:00
|
|
|
|
|
|
|
const (
|
|
|
|
initial = iota
|
|
|
|
special
|
2022-03-29 09:09:01 -04:00
|
|
|
padding
|
2022-03-29 07:53:20 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
state := initial
|
|
|
|
for _, b := range []byte(fmt) {
|
|
|
|
switch state {
|
|
|
|
case initial:
|
|
|
|
if b == '%' {
|
|
|
|
state = special
|
|
|
|
} else {
|
2022-03-29 20:28:19 -04:00
|
|
|
writeLit(b)
|
2022-03-29 07:53:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
case special:
|
2022-03-29 09:09:01 -04:00
|
|
|
if b == '-' {
|
|
|
|
state = padding
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if s, ok := formats[b]; ok {
|
2022-03-29 20:28:19 -04:00
|
|
|
writeFmt(s)
|
2022-03-29 07:53:20 -04:00
|
|
|
} else {
|
2022-03-29 20:28:19 -04:00
|
|
|
writeLit('%')
|
|
|
|
writeLit(b)
|
2022-03-29 07:53:20 -04:00
|
|
|
}
|
|
|
|
state = initial
|
|
|
|
|
2022-03-29 09:09:01 -04:00
|
|
|
case padding:
|
|
|
|
if s, ok := unpadded[b]; ok {
|
2022-03-29 20:28:19 -04:00
|
|
|
writeFmt(s)
|
2022-03-29 09:09:01 -04:00
|
|
|
} else if s, ok := formats[b]; ok {
|
2022-03-29 20:28:19 -04:00
|
|
|
writeFmt(s)
|
2022-03-29 07:53:20 -04:00
|
|
|
} else {
|
2022-03-29 20:28:19 -04:00
|
|
|
writeLit('%')
|
|
|
|
writeLit('-')
|
|
|
|
writeLit(b)
|
2022-03-29 07:53:20 -04:00
|
|
|
}
|
|
|
|
state = initial
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 09:09:01 -04:00
|
|
|
switch state {
|
|
|
|
case padding:
|
2022-03-29 20:28:19 -04:00
|
|
|
writeLit('%')
|
|
|
|
fallthrough
|
2022-03-29 09:09:01 -04:00
|
|
|
case special:
|
2022-03-29 20:28:19 -04:00
|
|
|
writeLit('-')
|
2022-03-29 09:09:01 -04:00
|
|
|
}
|
2022-03-29 07:53:20 -04:00
|
|
|
}
|