Use strftime module.
This commit is contained in:
parent
ebb84b8489
commit
a8d9413ae3
9 changed files with 6 additions and 783 deletions
|
@ -18,8 +18,8 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-strftime"
|
||||||
"github.com/ncruces/zenity"
|
"github.com/ncruces/zenity"
|
||||||
"github.com/ncruces/zenity/internal/strftime"
|
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package zenity
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity/internal/strftime"
|
"github.com/ncruces/go-strftime"
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncruces/zenity/internal/strftime"
|
"github.com/ncruces/go-strftime"
|
||||||
"github.com/ncruces/zenity/internal/zenutil"
|
"github.com/ncruces/zenity/internal/zenutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.17
|
||||||
require (
|
require (
|
||||||
github.com/dchest/jsmin v0.0.0-20160823214000-faeced883947
|
github.com/dchest/jsmin v0.0.0-20160823214000-faeced883947
|
||||||
github.com/josephspurrier/goversioninfo v1.3.0
|
github.com/josephspurrier/goversioninfo v1.3.0
|
||||||
|
github.com/ncruces/go-strftime v0.1.0
|
||||||
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844
|
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844
|
||||||
go.uber.org/goleak v1.1.11 // test
|
go.uber.org/goleak v1.1.11 // test
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -9,6 +9,8 @@ github.com/josephspurrier/goversioninfo v1.3.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/ncruces/go-strftime v0.1.0 h1:RmHX+0N9AZvUnBk3UGapiv+I2nSyzjCM9HDCHp5H7sM=
|
||||||
|
github.com/ncruces/go-strftime v0.1.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc=
|
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc=
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
package strftime
|
|
||||||
|
|
||||||
type parser struct {
|
|
||||||
fmt string
|
|
||||||
specs map[byte]string
|
|
||||||
unpadded map[byte]string
|
|
||||||
writeLit func(byte) error
|
|
||||||
writeFmt func(string) error
|
|
||||||
fallback func(spec byte, pad bool) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parse() error {
|
|
||||||
const (
|
|
||||||
initial = iota
|
|
||||||
specifier
|
|
||||||
nopadding
|
|
||||||
)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
state := initial
|
|
||||||
for _, b := range []byte(p.fmt) {
|
|
||||||
switch state {
|
|
||||||
default:
|
|
||||||
if b == '%' {
|
|
||||||
state = specifier
|
|
||||||
} else {
|
|
||||||
err = p.writeLit(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
case specifier:
|
|
||||||
if b == '-' {
|
|
||||||
state = nopadding
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if fmt, ok := p.specs[b]; ok {
|
|
||||||
err = p.writeFmt(fmt)
|
|
||||||
} else {
|
|
||||||
err = p.fallback(b, true)
|
|
||||||
}
|
|
||||||
state = initial
|
|
||||||
|
|
||||||
case nopadding:
|
|
||||||
if fmt, ok := p.unpadded[b]; ok {
|
|
||||||
err = p.writeFmt(fmt)
|
|
||||||
} else if fmt, ok := p.specs[b]; ok {
|
|
||||||
err = p.writeFmt(fmt)
|
|
||||||
} else {
|
|
||||||
err = p.fallback(b, false)
|
|
||||||
}
|
|
||||||
state = initial
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch state {
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
case specifier:
|
|
||||||
return p.writeLit('%')
|
|
||||||
case nopadding:
|
|
||||||
err := p.writeLit('%')
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return p.writeLit('-')
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
package strftime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *parser) goSpecifiers() {
|
|
||||||
// https://strftime.org/
|
|
||||||
p.specs = map[byte]string{
|
|
||||||
'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",
|
|
||||||
'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",
|
|
||||||
'v': "_2-Jan-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': "\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
p.unpadded = map[byte]string{
|
|
||||||
'm': "1",
|
|
||||||
'd': "2",
|
|
||||||
'I': "3",
|
|
||||||
'M': "4",
|
|
||||||
'S': "5",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) uts35Specifiers() {
|
|
||||||
// https://nsdateformatter.com/
|
|
||||||
p.specs = map[byte]string{
|
|
||||||
'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",
|
|
||||||
'v': "d-MMM-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': "\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
p.unpadded = map[byte]string{
|
|
||||||
'm': "M",
|
|
||||||
'd': "d",
|
|
||||||
'j': "D",
|
|
||||||
'H': "H",
|
|
||||||
'I': "h",
|
|
||||||
'M': "m",
|
|
||||||
'S': "s",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func weekNumber(t time.Time, pad, monday bool) string {
|
|
||||||
day := t.YearDay()
|
|
||||||
offset := int(t.Weekday())
|
|
||||||
if monday {
|
|
||||||
if offset == 0 {
|
|
||||||
offset = 6
|
|
||||||
} else {
|
|
||||||
offset--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if day < offset {
|
|
||||||
if pad {
|
|
||||||
return "00"
|
|
||||||
} else {
|
|
||||||
return "0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n := (day-offset)/7 + 1
|
|
||||||
if n < 10 && pad {
|
|
||||||
return "0" + strconv.Itoa(n)
|
|
||||||
}
|
|
||||||
return strconv.Itoa(n)
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
package strftime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Format(fmt string, t time.Time) string {
|
|
||||||
var res strings.Builder
|
|
||||||
|
|
||||||
var parser parser
|
|
||||||
parser.fmt = fmt
|
|
||||||
parser.goSpecifiers()
|
|
||||||
|
|
||||||
parser.writeLit = res.WriteByte
|
|
||||||
|
|
||||||
parser.writeFmt = func(fmt string) error {
|
|
||||||
switch fmt {
|
|
||||||
case "000", "000000", "000000000":
|
|
||||||
res.WriteString(t.Format("." + fmt)[1:])
|
|
||||||
default:
|
|
||||||
res.WriteString(t.Format(fmt))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.fallback = func(spec byte, pad bool) error {
|
|
||||||
switch spec {
|
|
||||||
default:
|
|
||||||
res.WriteByte('%')
|
|
||||||
if !pad {
|
|
||||||
res.WriteByte('-')
|
|
||||||
}
|
|
||||||
res.WriteByte(spec)
|
|
||||||
case 'C':
|
|
||||||
s := t.Format("2006")
|
|
||||||
res.WriteString(s[:len(s)-2])
|
|
||||||
case 'g':
|
|
||||||
y, _ := t.ISOWeek()
|
|
||||||
res.WriteString(time.Date(y, 1, 1, 0, 0, 0, 0, time.UTC).Format("06"))
|
|
||||||
case 'G':
|
|
||||||
y, _ := t.ISOWeek()
|
|
||||||
res.WriteString(time.Date(y, 1, 1, 0, 0, 0, 0, time.UTC).Format("2006"))
|
|
||||||
case 'V':
|
|
||||||
_, w := t.ISOWeek()
|
|
||||||
if w < 10 && pad {
|
|
||||||
res.WriteByte('0')
|
|
||||||
}
|
|
||||||
res.WriteString(strconv.Itoa(w))
|
|
||||||
case 'W':
|
|
||||||
res.WriteString(weekNumber(t, pad, true))
|
|
||||||
case 'U':
|
|
||||||
res.WriteString(weekNumber(t, pad, false))
|
|
||||||
case 'w':
|
|
||||||
w := int(t.Weekday())
|
|
||||||
res.WriteString(strconv.Itoa(w))
|
|
||||||
case 'u':
|
|
||||||
if w := int(t.Weekday()); w == 0 {
|
|
||||||
res.WriteByte('7')
|
|
||||||
} else {
|
|
||||||
res.WriteString(strconv.Itoa(w))
|
|
||||||
}
|
|
||||||
case 'k':
|
|
||||||
h := t.Hour()
|
|
||||||
if h < 10 {
|
|
||||||
res.WriteByte(' ')
|
|
||||||
}
|
|
||||||
res.WriteString(strconv.Itoa(h))
|
|
||||||
case 'l':
|
|
||||||
h := t.Hour()
|
|
||||||
if h == 0 {
|
|
||||||
h = 12
|
|
||||||
} else if h > 12 {
|
|
||||||
h -= 12
|
|
||||||
}
|
|
||||||
if h < 10 {
|
|
||||||
res.WriteByte(' ')
|
|
||||||
}
|
|
||||||
res.WriteString(strconv.Itoa(h))
|
|
||||||
case 's':
|
|
||||||
res.WriteString(strconv.FormatInt(t.Unix(), 10))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.parse()
|
|
||||||
return res.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(fmt, value string) (time.Time, error) {
|
|
||||||
layout, err := Layout(fmt)
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}, err
|
|
||||||
}
|
|
||||||
return time.Parse(layout, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Layout(fmt string) (string, error) {
|
|
||||||
var res strings.Builder
|
|
||||||
|
|
||||||
var parser parser
|
|
||||||
parser.fmt = fmt
|
|
||||||
parser.goSpecifiers()
|
|
||||||
|
|
||||||
parser.writeLit = func(b byte) error {
|
|
||||||
if '0' <= b && b <= '9' {
|
|
||||||
return errors.New("strftime: unsupported literal digit: '" + string(b) + "'")
|
|
||||||
}
|
|
||||||
res.WriteByte(b)
|
|
||||||
if b == 'M' || b == 'T' || b == 'm' || b == 'n' {
|
|
||||||
cur := res.String()
|
|
||||||
switch {
|
|
||||||
case strings.HasSuffix(cur, "Jan"):
|
|
||||||
return errors.New("strftime: unsupported literal: 'Jan'")
|
|
||||||
case strings.HasSuffix(cur, "Mon"):
|
|
||||||
return errors.New("strftime: unsupported literal: 'Mon'")
|
|
||||||
case strings.HasSuffix(cur, "MST"):
|
|
||||||
return errors.New("strftime: unsupported literal: 'MST'")
|
|
||||||
case strings.HasSuffix(cur, "PM"):
|
|
||||||
return errors.New("strftime: unsupported literal: 'PM'")
|
|
||||||
case strings.HasSuffix(cur, "pm"):
|
|
||||||
return errors.New("strftime: unsupported literal: 'pm'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.writeFmt = func(fmt string) error {
|
|
||||||
switch fmt {
|
|
||||||
case "000", "000000", "000000000":
|
|
||||||
if cur := res.String(); !(strings.HasSuffix(cur, ".") || strings.HasSuffix(cur, ",")) {
|
|
||||||
return errors.New("strftime: unsupported specifier: fractional seconds must follow '.' or ','")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.WriteString(fmt)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.fallback = func(spec byte, pad bool) error {
|
|
||||||
return errors.New("strftime: unsupported specifier: %" + string(spec))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parser.parse(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.writeFmt("")
|
|
||||||
return res.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UTS35(fmt string) (string, error) {
|
|
||||||
var parser parser
|
|
||||||
parser.fmt = fmt
|
|
||||||
parser.uts35Specifiers()
|
|
||||||
|
|
||||||
const quote = '\''
|
|
||||||
var literal bool
|
|
||||||
var res strings.Builder
|
|
||||||
|
|
||||||
parser.writeLit = func(b byte) error {
|
|
||||||
if b == quote {
|
|
||||||
res.WriteByte(quote)
|
|
||||||
res.WriteByte(quote)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !literal && ('a' <= b && b <= 'z' || 'A' <= b && b <= 'Z') {
|
|
||||||
literal = true
|
|
||||||
res.WriteByte(quote)
|
|
||||||
}
|
|
||||||
res.WriteByte(b)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.writeFmt = func(fmt string) error {
|
|
||||||
if literal {
|
|
||||||
literal = false
|
|
||||||
res.WriteByte(quote)
|
|
||||||
}
|
|
||||||
res.WriteString(fmt)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.fallback = func(spec byte, pad bool) error {
|
|
||||||
return errors.New("strftime: unsupported specifier: %" + string(spec))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parser.parse(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.writeFmt("")
|
|
||||||
return res.String(), nil
|
|
||||||
}
|
|
|
@ -1,378 +0,0 @@
|
||||||
package strftime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var reference = time.Date(2009, 8, 7, 6, 5, 4, 300000000, time.UTC)
|
|
||||||
|
|
||||||
var timeTests = []struct {
|
|
||||||
format string
|
|
||||||
layout string
|
|
||||||
uts35 string
|
|
||||||
time string
|
|
||||||
}{
|
|
||||||
// Date and time formats
|
|
||||||
{"%c", time.ANSIC, "E MMM d HH:mm:ss yyyy", "Fri Aug 7 06:05:04 2009"},
|
|
||||||
{"%+", time.UnixDate, "E MMM d HH:mm:ss zzz yyyy", "Fri Aug 7 06:05:04 UTC 2009"},
|
|
||||||
{"%FT%TZ", time.RFC3339[:20], "yyyy-MM-dd'T'HH:mm:ss'Z'", "2009-08-07T06:05:04Z"},
|
|
||||||
{"%a %b %e %T %Y", time.ANSIC, "", "Fri Aug 7 06:05:04 2009"},
|
|
||||||
{"%a %b %e %T %Z %Y", time.UnixDate, "", "Fri Aug 7 06:05:04 UTC 2009"},
|
|
||||||
{"%a %b %d %T %z %Y", time.RubyDate, "E MMM dd HH:mm:ss Z yyyy", "Fri Aug 07 06:05:04 +0000 2009"},
|
|
||||||
{"%a, %d %b %Y %T %Z", time.RFC1123, "E, dd MMM yyyy HH:mm:ss zzz", "Fri, 07 Aug 2009 06:05:04 UTC"},
|
|
||||||
{"%a, %d %b %Y %T GMT", http.TimeFormat, "E, dd MMM yyyy HH:mm:ss 'GMT'", "Fri, 07 Aug 2009 06:05:04 GMT"},
|
|
||||||
{"%Y-%m-%dT%H:%M:%SZ", time.RFC3339[:20], "yyyy-MM-dd'T'HH:mm:ss'Z'", "2009-08-07T06:05:04Z"},
|
|
||||||
// Date formats
|
|
||||||
{"%v", "_2-Jan-2006", "d-MMM-yyyy", " 7-Aug-2009"},
|
|
||||||
{"%F", "2006-01-02", "yyyy-MM-dd", "2009-08-07"},
|
|
||||||
{"%D", "01/02/06", "MM/dd/yy", "08/07/09"},
|
|
||||||
{"%x", "01/02/06", "MM/dd/yy", "08/07/09"},
|
|
||||||
{"%e-%b-%Y", "_2-Jan-2006", "", " 7-Aug-2009"},
|
|
||||||
{"%Y-%m-%d", "2006-01-02", "yyyy-MM-dd", "2009-08-07"},
|
|
||||||
{"%m/%d/%y", "01/02/06", "MM/dd/yy", "08/07/09"},
|
|
||||||
// Time formats
|
|
||||||
{"%R", "15:04", "HH:mm", "06:05"},
|
|
||||||
{"%T", "15:04:05", "HH:mm:ss", "06:05:04"},
|
|
||||||
{"%X", "15:04:05", "HH:mm:ss", "06:05:04"},
|
|
||||||
{"%r", "03:04:05 PM", "hh:mm:ss a", "06:05:04 AM"},
|
|
||||||
{"%H:%M", "15:04", "HH:mm", "06:05"},
|
|
||||||
{"%H:%M:%S", "15:04:05", "HH:mm:ss", "06:05:04"},
|
|
||||||
{"%I:%M:%S %p", "03:04:05 PM", "hh:mm:ss a", "06:05:04 AM"},
|
|
||||||
// Misc
|
|
||||||
{"%g", "", "YY", "09"},
|
|
||||||
{"%V/%G", "", "ww/YYYY", "32/2009"},
|
|
||||||
{"%Cth Century Fox", "", "", "20th Century Fox"},
|
|
||||||
{"%-I o'clock", "3 o'clock", "h 'o''clock'", "6 o'clock"},
|
|
||||||
{"%-A, the %uth day of the week", "", "", "Friday, the 5th day of the week"},
|
|
||||||
{"%Nns since %T", "", "SSSSSSSSS'ns since 'HH:mm:ss", "300000000ns since 06:05:04"},
|
|
||||||
{"%-S.%Ls since %R", "5.000s since 15:04", "s.SSS's since 'HH:mm", "4.300s since 06:05"},
|
|
||||||
// Parsing
|
|
||||||
{"", "", "", ""},
|
|
||||||
{"%", "%", "%", "%"},
|
|
||||||
{"%-", "%-", "%-", "%-"},
|
|
||||||
{"%n", "\n", "\n", "\n"},
|
|
||||||
{"%t", "\t", "\t", "\t"},
|
|
||||||
{"%q", "", "", "%q"},
|
|
||||||
{"%-q", "", "", "%-q"},
|
|
||||||
{"'", "'", "''", "'"},
|
|
||||||
{"100%", "", "100%", "100%"},
|
|
||||||
{"Monday", "", "'Monday'", "Monday"},
|
|
||||||
{"January", "", "'January'", "January"},
|
|
||||||
{"MST", "", "'MST'", "MST"},
|
|
||||||
{"AM", "AM", "'AM'", "AM"},
|
|
||||||
{"am", "am", "'am'", "am"},
|
|
||||||
{"PM", "", "'PM'", "PM"},
|
|
||||||
{"pm", "", "'pm'", "pm"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormat(t *testing.T) {
|
|
||||||
for _, test := range timeTests {
|
|
||||||
if got := Format(test.format, reference); got != test.time {
|
|
||||||
t.Errorf("Format(%q) = %q, want %q", test.format, got, test.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLayout(t *testing.T) {
|
|
||||||
for _, test := range timeTests {
|
|
||||||
if got, err := Layout(test.format); err != nil && test.layout != "" {
|
|
||||||
t.Errorf("Layout(%q) = %v", test.format, err)
|
|
||||||
} else if got != test.layout {
|
|
||||||
t.Errorf("Layout(%q) = %q, want %q", test.format, got, test.layout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUTS35(t *testing.T) {
|
|
||||||
for _, test := range timeTests {
|
|
||||||
if got, err := UTS35(test.format); err != nil && test.uts35 != "" {
|
|
||||||
t.Errorf("UTS35(%q) = %v", test.format, err)
|
|
||||||
} else if got != test.uts35 {
|
|
||||||
t.Errorf("UTS35(%q) = %q, want %q", test.format, got, test.uts35)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormat_ruby(t *testing.T) {
|
|
||||||
// https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html#method-i-strftime
|
|
||||||
reference := time.Date(2007, 11, 19, 8, 37, 48, 0, time.FixedZone("", -6*3600))
|
|
||||||
tests := []struct {
|
|
||||||
format string
|
|
||||||
time string
|
|
||||||
}{
|
|
||||||
{"Printed on %m/%d/%Y", "Printed on 11/19/2007"},
|
|
||||||
{"at %I:%M%p", "at 08:37AM"},
|
|
||||||
// Various ISO 8601 formats:
|
|
||||||
{"%Y%m%d", "20071119"}, // Calendar date (basic)
|
|
||||||
{"%F", "2007-11-19"}, // Calendar date (extended)
|
|
||||||
{"%Y-%m", "2007-11"}, // Calendar date, reduced accuracy, specific month
|
|
||||||
{"%Y", "2007"}, // Calendar date, reduced accuracy, specific year
|
|
||||||
{"%C", "20"}, // Calendar date, reduced accuracy, specific century
|
|
||||||
{"%Y%j", "2007323"}, // Ordinal date (basic)
|
|
||||||
{"%Y-%j", "2007-323"}, // Ordinal date (extended)
|
|
||||||
{"%GW%V%u", "2007W471"}, // Week date (basic)
|
|
||||||
{"%G-W%V-%u", "2007-W47-1"}, // Week date (extended)
|
|
||||||
{"%GW%V", "2007W47"}, // Week date, reduced accuracy, specific week (basic)
|
|
||||||
{"%G-W%V", "2007-W47"}, // Week date, reduced accuracy, specific week (extended)
|
|
||||||
{"%H%M%S", "083748"}, // Local time (basic)
|
|
||||||
{"%T", "08:37:48"}, // Local time (extended)
|
|
||||||
{"%H%M", "0837"}, // Local time, reduced accuracy, specific minute (basic)
|
|
||||||
{"%H:%M", "08:37"}, // Local time, reduced accuracy, specific minute (extended)
|
|
||||||
{"%H", "08"}, // Local time, reduced accuracy, specific hour
|
|
||||||
{"%H%M%S,%L", "083748,000"}, // Local time with decimal fraction, comma as decimal sign (basic)
|
|
||||||
{"%T,%L", "08:37:48,000"}, // Local time with decimal fraction, comma as decimal sign (extended)
|
|
||||||
{"%H%M%S.%L", "083748.000"}, // Local time with decimal fraction, full stop as decimal sign (basic)
|
|
||||||
{"%T.%L", "08:37:48.000"}, // Local time with decimal fraction, full stop as decimal sign (extended)
|
|
||||||
{"%H%M%S%z", "083748-0600"}, // Local time and the difference from UTC (basic)
|
|
||||||
{"%Y%m%dT%H%M%S%z", "20071119T083748-0600"}, // Date and time of day for calendar date (basic)
|
|
||||||
{"%Y%jT%H%M%S%z", "2007323T083748-0600"}, // Date and time of day for ordinal date (basic)
|
|
||||||
{"%GW%V%uT%H%M%S%z", "2007W471T083748-0600"}, // Date and time of day for week date (basic)
|
|
||||||
{"%Y%m%dT%H%M", "20071119T0837"}, // Calendar date and local time (basic)
|
|
||||||
{"%FT%R", "2007-11-19T08:37"}, // Calendar date and local time (extended)
|
|
||||||
{"%Y%jT%H%MZ", "2007323T0837Z"}, // Ordinal date and UTC of day (basic)
|
|
||||||
{"%Y-%jT%RZ", "2007-323T08:37Z"}, // Ordinal date and UTC of day (extended)
|
|
||||||
{"%GW%V%uT%H%M%z", "2007W471T0837-0600"}, // Week date and local time and difference from UTC (basic)
|
|
||||||
// {"%T%:z", "08:37:48-06:00"}, // Local time and the difference from UTC (extended)
|
|
||||||
// {"%FT%T%:z", "2007-11-19T08:37:48-06:00"}, // Date and time of day for calendar date (extended)
|
|
||||||
// {"%Y-%jT%T%:z", "2007-323T08:37:48-06:00"}, // Date and time of day for ordinal date (extended)
|
|
||||||
// {"%G-W%V-%uT%T%:z", "2007-W47-1T08:37:48-06:00"}, // Date and time of day for week date (extended)
|
|
||||||
// {"%G-W%V-%uT%R%:z", "2007-W47-1T08:37-06:00"}, // Week date and local time and difference from
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
if got := Format(test.format, reference); got != test.time {
|
|
||||||
t.Errorf("Format(%q) = %q, want %q", test.format, got, test.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormat_tebeka(t *testing.T) {
|
|
||||||
// github.com/tebeka/strftime
|
|
||||||
// github.com/hhkbp2/go-strftime
|
|
||||||
reference := time.Date(2009, time.November, 10, 23, 1, 2, 3, time.UTC)
|
|
||||||
tests := []struct {
|
|
||||||
format string
|
|
||||||
time string
|
|
||||||
}{
|
|
||||||
{"%a", "Tue"},
|
|
||||||
{"%A", "Tuesday"},
|
|
||||||
{"%b", "Nov"},
|
|
||||||
{"%B", "November"},
|
|
||||||
{"%c", "Tue Nov 10 23:01:02 2009"}, // we use a different format
|
|
||||||
{"%d", "10"},
|
|
||||||
{"%H", "23"},
|
|
||||||
{"%I", "11"},
|
|
||||||
{"%j", "314"},
|
|
||||||
{"%m", "11"},
|
|
||||||
{"%M", "01"},
|
|
||||||
{"%p", "PM"},
|
|
||||||
{"%S", "02"},
|
|
||||||
{"%U", "45"},
|
|
||||||
{"%w", "2"},
|
|
||||||
{"%W", "45"},
|
|
||||||
{"%x", "11/10/09"},
|
|
||||||
{"%X", "23:01:02"},
|
|
||||||
{"%y", "09"},
|
|
||||||
{"%Y", "2009"},
|
|
||||||
{"%Z", "UTC"},
|
|
||||||
{"%L", "000"}, // we use a different specifier
|
|
||||||
{"%f", "000000"}, // we use a different specifier
|
|
||||||
{"%N", "000000003"}, // we use a different specifier
|
|
||||||
|
|
||||||
// Escape
|
|
||||||
{"%%%Y", "%2009"},
|
|
||||||
{"%3%%", "%3%"},
|
|
||||||
{"%3%L", "%3000"}, // we use a different specifier
|
|
||||||
{"%3xy%L", "%3xy000"}, // we use a different specifier
|
|
||||||
|
|
||||||
// Embedded
|
|
||||||
{"/path/%Y/%m/report", "/path/2009/11/report"},
|
|
||||||
|
|
||||||
// Empty
|
|
||||||
{"", ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
if got := Format(test.format, reference); got != test.time {
|
|
||||||
t.Errorf("Format(%q) = %q, want %q", test.format, got, test.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormat_fastly(t *testing.T) {
|
|
||||||
// github.com/fastly/go-utils/strftime
|
|
||||||
timezone, err := time.LoadLocation("MST")
|
|
||||||
if err != nil {
|
|
||||||
t.Skip("could not load timezone:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reference := time.Unix(1136239445, 0).In(timezone)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
format string
|
|
||||||
time string
|
|
||||||
}{
|
|
||||||
{"", ``},
|
|
||||||
|
|
||||||
// invalid formats
|
|
||||||
{"%", `%`},
|
|
||||||
{"%^", `%^`},
|
|
||||||
{"%^ ", `%^ `},
|
|
||||||
{"%^ x", `%^ x`},
|
|
||||||
{"x%^ x", `x%^ x`},
|
|
||||||
|
|
||||||
// supported locale-invariant formats
|
|
||||||
{"%a", `Mon`},
|
|
||||||
{"%A", `Monday`},
|
|
||||||
{"%b", `Jan`},
|
|
||||||
{"%B", `January`},
|
|
||||||
{"%C", `20`},
|
|
||||||
{"%d", `02`},
|
|
||||||
{"%D", `01/02/06`},
|
|
||||||
{"%e", ` 2`},
|
|
||||||
{"%F", `2006-01-02`},
|
|
||||||
{"%G", `2006`},
|
|
||||||
{"%g", `06`},
|
|
||||||
{"%h", `Jan`},
|
|
||||||
{"%H", `15`},
|
|
||||||
{"%I", `03`},
|
|
||||||
{"%j", `002`},
|
|
||||||
{"%k", `15`},
|
|
||||||
{"%l", ` 3`},
|
|
||||||
{"%m", `01`},
|
|
||||||
{"%M", `04`},
|
|
||||||
{"%n", "\n"},
|
|
||||||
{"%p", `PM`},
|
|
||||||
{"%r", `03:04:05 PM`},
|
|
||||||
{"%R", `15:04`},
|
|
||||||
{"%s", `1136239445`},
|
|
||||||
{"%S", `05`},
|
|
||||||
{"%t", "\t"},
|
|
||||||
{"%T", `15:04:05`},
|
|
||||||
{"%u", `1`},
|
|
||||||
{"%U", `01`},
|
|
||||||
{"%V", `01`},
|
|
||||||
{"%w", `1`},
|
|
||||||
{"%W", `01`},
|
|
||||||
{"%x", `01/02/06`},
|
|
||||||
{"%X", `15:04:05`},
|
|
||||||
{"%y", `06`},
|
|
||||||
{"%Y", `2006`},
|
|
||||||
{"%z", `-0700`},
|
|
||||||
{"%Z", `MST`},
|
|
||||||
{"%%", `%`},
|
|
||||||
|
|
||||||
// supported locale-varying formats
|
|
||||||
{"%c", `Mon Jan 2 15:04:05 2006`},
|
|
||||||
{"%E", `%E`},
|
|
||||||
{"%EF", `%EF`},
|
|
||||||
{"%O", `%O`},
|
|
||||||
{"%OF", `%OF`},
|
|
||||||
{"%P", `pm`},
|
|
||||||
{"%+", `Mon Jan 2 15:04:05 MST 2006`},
|
|
||||||
{
|
|
||||||
"%a|%A|%b|%B|%c|%C|%d|%D|%e|%E|%EF|%F|%G|%g|%h|%H|%I|%j|%k|%l|%m|%M|%O|%OF|%p|%P|%r|%R|%s|%S|%t|%T|%u|%U|%V|%w|%W|%x|%X|%y|%Y|%z|%Z|%%",
|
|
||||||
`Mon|Monday|Jan|January|Mon Jan 2 15:04:05 2006|20|02|01/02/06| 2|%E|%EF|2006-01-02|2006|06|Jan|15|03|002|15| 3|01|04|%O|%OF|PM|pm|03:04:05 PM|15:04|1136239445|05| |15:04:05|1|01|01|1|01|01/02/06|15:04:05|06|2006|-0700|MST|%`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
if got := Format(test.format, reference); got != test.time {
|
|
||||||
t.Errorf("Format(%q) = %q, want %q", test.format, got, test.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormat_jehiah(t *testing.T) {
|
|
||||||
// github.com/jehiah/go-strftime
|
|
||||||
reference := time.Unix(1340244776, 0).UTC()
|
|
||||||
tests := []struct {
|
|
||||||
format string
|
|
||||||
time string
|
|
||||||
}{
|
|
||||||
{"%Y-%m-%d %H:%M:%S", "2012-06-21 02:12:56"},
|
|
||||||
{"aaabbb0123456789%Y", "aaabbb01234567892012"},
|
|
||||||
{"%H:%M:%S.%L", "02:12:56.000"}, // jehiah disagrees with Ruby on this one
|
|
||||||
{"%0%1%%%2", "%0%1%%2"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
if got := Format(test.format, reference); got != test.time {
|
|
||||||
t.Errorf("Format(%q) = %q, want %q", test.format, got, test.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormat_lestrrat(t *testing.T) {
|
|
||||||
// github.com/lestrrat-go/strftime
|
|
||||||
reference := time.Unix(1136239445, 123456789).UTC()
|
|
||||||
tests := []struct {
|
|
||||||
format string
|
|
||||||
time string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
`%A %a %B %b %C %c %D %d %e %F %H %h %I %j %k %l %M %m %n %p %R %r %S %T %t %U %u %V %v %W %w %X %x %Y %y %Z %z`,
|
|
||||||
"Monday Mon January Jan 20 Mon Jan 2 22:04:05 2006 01/02/06 02 2 2006-01-02 22 Jan 10 002 22 10 04 01 \n PM 22:04 10:04:05 PM 05 22:04:05 \t 01 1 01 2-Jan-2006 01 1 22:04:05 01/02/06 2006 06 UTC +0000",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
if got := Format(test.format, reference); got != test.time {
|
|
||||||
t.Errorf("Format(%q) = %q, want %q", test.format, got, test.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuzzFormat(f *testing.F) {
|
|
||||||
for _, test := range timeTests {
|
|
||||||
f.Add(test.format)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, fmt string) {
|
|
||||||
s := Format(fmt, reference)
|
|
||||||
if s == "" && fmt != "" {
|
|
||||||
t.Errorf("Format(%q) = %q", fmt, s)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuzzParse(f *testing.F) {
|
|
||||||
for _, test := range timeTests {
|
|
||||||
f.Add(test.format, Format(test.format, reference))
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, format, value string) {
|
|
||||||
tm, err := Parse(format, value)
|
|
||||||
if tm.IsZero() && err == nil {
|
|
||||||
t.Errorf("Parse(%q, %q) = (%v, %v)", format, value, tm, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuzzLayout(f *testing.F) {
|
|
||||||
for _, test := range timeTests {
|
|
||||||
f.Add(test.format)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, format string) {
|
|
||||||
layout, err := Layout(format)
|
|
||||||
if format != "" && layout == "" && err == nil {
|
|
||||||
t.Errorf("Layout(%q) = (%q, %v)", format, layout, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuzzUTS35(f *testing.F) {
|
|
||||||
for _, test := range timeTests {
|
|
||||||
f.Add(test.format)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, format string) {
|
|
||||||
pattern, err := UTS35(format)
|
|
||||||
if format != "" && pattern == "" && err == nil {
|
|
||||||
t.Errorf("UTS35(%q) = (%q, %v)", format, pattern, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in a new issue