mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-23 10:42:12 +00:00
325 lines
7 KiB
Go
325 lines
7 KiB
Go
|
package strftime
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"strconv"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Format returns a textual representation of the time value
|
||
|
// formatted according to the strftime format specification.
|
||
|
func Format(fmt string, t time.Time) string {
|
||
|
buf := buffer(fmt)
|
||
|
return string(AppendFormat(buf, fmt, t))
|
||
|
}
|
||
|
|
||
|
// AppendFormat is like Format, but appends the textual representation
|
||
|
// to dst and returns the extended buffer.
|
||
|
func AppendFormat(dst []byte, fmt string, t time.Time) []byte {
|
||
|
var parser parser
|
||
|
|
||
|
parser.literal = func(b byte) error {
|
||
|
dst = append(dst, b)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
parser.format = func(spec, flag byte) error {
|
||
|
switch spec {
|
||
|
case 'A':
|
||
|
dst = append(dst, t.Weekday().String()...)
|
||
|
return nil
|
||
|
case 'a':
|
||
|
dst = append(dst, t.Weekday().String()[:3]...)
|
||
|
return nil
|
||
|
case 'B':
|
||
|
dst = append(dst, t.Month().String()...)
|
||
|
return nil
|
||
|
case 'b', 'h':
|
||
|
dst = append(dst, t.Month().String()[:3]...)
|
||
|
return nil
|
||
|
case 'm':
|
||
|
dst = appendInt2(dst, int(t.Month()), flag)
|
||
|
return nil
|
||
|
case 'd':
|
||
|
dst = appendInt2(dst, int(t.Day()), flag)
|
||
|
return nil
|
||
|
case 'e':
|
||
|
dst = appendInt2(dst, int(t.Day()), ' ')
|
||
|
return nil
|
||
|
case 'I':
|
||
|
dst = append12Hour(dst, t, flag)
|
||
|
return nil
|
||
|
case 'l':
|
||
|
dst = append12Hour(dst, t, ' ')
|
||
|
return nil
|
||
|
case 'H':
|
||
|
dst = appendInt2(dst, t.Hour(), flag)
|
||
|
return nil
|
||
|
case 'k':
|
||
|
dst = appendInt2(dst, t.Hour(), ' ')
|
||
|
return nil
|
||
|
case 'M':
|
||
|
dst = appendInt2(dst, t.Minute(), flag)
|
||
|
return nil
|
||
|
case 'S':
|
||
|
dst = appendInt2(dst, t.Second(), flag)
|
||
|
return nil
|
||
|
case 'L':
|
||
|
dst = append(dst, t.Format(".000")[1:]...)
|
||
|
return nil
|
||
|
case 'f':
|
||
|
dst = append(dst, t.Format(".000000")[1:]...)
|
||
|
return nil
|
||
|
case 'N':
|
||
|
dst = append(dst, t.Format(".000000000")[1:]...)
|
||
|
return nil
|
||
|
case 'y':
|
||
|
dst = t.AppendFormat(dst, "06")
|
||
|
return nil
|
||
|
case 'Y':
|
||
|
dst = t.AppendFormat(dst, "2006")
|
||
|
return nil
|
||
|
case 'C':
|
||
|
dst = t.AppendFormat(dst, "2006")
|
||
|
dst = dst[:len(dst)-2]
|
||
|
return nil
|
||
|
case 'U':
|
||
|
dst = appendWeekNumber(dst, t, flag, true)
|
||
|
return nil
|
||
|
case 'W':
|
||
|
dst = appendWeekNumber(dst, t, flag, false)
|
||
|
return nil
|
||
|
case 'V':
|
||
|
_, w := t.ISOWeek()
|
||
|
dst = appendInt2(dst, w, flag)
|
||
|
return nil
|
||
|
case 'g':
|
||
|
y, _ := t.ISOWeek()
|
||
|
dst = year(y).AppendFormat(dst, "06")
|
||
|
return nil
|
||
|
case 'G':
|
||
|
y, _ := t.ISOWeek()
|
||
|
dst = year(y).AppendFormat(dst, "2006")
|
||
|
return nil
|
||
|
case 's':
|
||
|
dst = strconv.AppendInt(dst, t.Unix(), 10)
|
||
|
return nil
|
||
|
case 'Q':
|
||
|
dst = strconv.AppendInt(dst, t.UnixMilli(), 10)
|
||
|
return nil
|
||
|
case 'w':
|
||
|
w := t.Weekday()
|
||
|
dst = appendInt1(dst, int(w))
|
||
|
return nil
|
||
|
case 'u':
|
||
|
if w := t.Weekday(); w == 0 {
|
||
|
dst = append(dst, '7')
|
||
|
} else {
|
||
|
dst = appendInt1(dst, int(w))
|
||
|
}
|
||
|
return nil
|
||
|
case 'j':
|
||
|
if flag == '-' {
|
||
|
dst = strconv.AppendInt(dst, int64(t.YearDay()), 10)
|
||
|
} else {
|
||
|
dst = t.AppendFormat(dst, "002")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if layout := goLayout(spec, flag, false); layout != "" {
|
||
|
dst = t.AppendFormat(dst, layout)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
dst = append(dst, '%')
|
||
|
if flag != 0 {
|
||
|
dst = append(dst, flag)
|
||
|
}
|
||
|
dst = append(dst, spec)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
parser.parse(fmt)
|
||
|
return dst
|
||
|
}
|
||
|
|
||
|
// Parse converts a textual representation of time to the time value it represents
|
||
|
// according to the strptime format specification.
|
||
|
func Parse(fmt, value string) (time.Time, error) {
|
||
|
pattern, err := layout(fmt, true)
|
||
|
if err != nil {
|
||
|
return time.Time{}, err
|
||
|
}
|
||
|
return time.Parse(pattern, value)
|
||
|
}
|
||
|
|
||
|
// Layout converts a strftime format specification
|
||
|
// to a Go time pattern specification.
|
||
|
func Layout(fmt string) (string, error) {
|
||
|
return layout(fmt, false)
|
||
|
}
|
||
|
|
||
|
func layout(fmt string, parsing bool) (string, error) {
|
||
|
dst := buffer(fmt)
|
||
|
var parser parser
|
||
|
|
||
|
parser.literal = func(b byte) error {
|
||
|
if '0' <= b && b <= '9' {
|
||
|
return literalErr(b)
|
||
|
}
|
||
|
dst = append(dst, b)
|
||
|
if b == 'M' || b == 'T' || b == 'm' || b == 'n' {
|
||
|
switch {
|
||
|
case bytes.HasSuffix(dst, []byte("Jan")):
|
||
|
return literalErr("Jan")
|
||
|
case bytes.HasSuffix(dst, []byte("Mon")):
|
||
|
return literalErr("Mon")
|
||
|
case bytes.HasSuffix(dst, []byte("MST")):
|
||
|
return literalErr("MST")
|
||
|
case bytes.HasSuffix(dst, []byte("PM")):
|
||
|
return literalErr("PM")
|
||
|
case bytes.HasSuffix(dst, []byte("pm")):
|
||
|
return literalErr("pm")
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
parser.format = func(spec, flag byte) error {
|
||
|
if layout := goLayout(spec, flag, parsing); layout != "" {
|
||
|
dst = append(dst, layout...)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
switch spec {
|
||
|
default:
|
||
|
return formatError{}
|
||
|
|
||
|
case 'L', 'f', 'N':
|
||
|
if bytes.HasSuffix(dst, []byte(".")) || bytes.HasSuffix(dst, []byte(",")) {
|
||
|
switch spec {
|
||
|
default:
|
||
|
dst = append(dst, "000"...)
|
||
|
case 'f':
|
||
|
dst = append(dst, "000000"...)
|
||
|
case 'N':
|
||
|
dst = append(dst, "000000000"...)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
return formatError{message: "must follow '.' or ','"}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := parser.parse(fmt); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return string(dst), nil
|
||
|
}
|
||
|
|
||
|
// UTS35 converts a strftime format specification
|
||
|
// to a Unicode Technical Standard #35 Date Format Pattern.
|
||
|
func UTS35(fmt string) (string, error) {
|
||
|
const quote = '\''
|
||
|
var quoted bool
|
||
|
dst := buffer(fmt)
|
||
|
|
||
|
var parser parser
|
||
|
|
||
|
parser.literal = func(b byte) error {
|
||
|
if b == quote {
|
||
|
dst = append(dst, quote, quote)
|
||
|
return nil
|
||
|
}
|
||
|
if !quoted && ('a' <= b && b <= 'z' || 'A' <= b && b <= 'Z') {
|
||
|
dst = append(dst, quote)
|
||
|
quoted = true
|
||
|
}
|
||
|
dst = append(dst, b)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
parser.format = func(spec, flag byte) error {
|
||
|
if quoted {
|
||
|
dst = append(dst, quote)
|
||
|
quoted = false
|
||
|
}
|
||
|
if pattern := uts35Pattern(spec, flag); pattern != "" {
|
||
|
dst = append(dst, pattern...)
|
||
|
return nil
|
||
|
}
|
||
|
return formatError{}
|
||
|
}
|
||
|
|
||
|
if err := parser.parse(fmt); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
if quoted {
|
||
|
dst = append(dst, quote)
|
||
|
}
|
||
|
return string(dst), nil
|
||
|
}
|
||
|
|
||
|
func buffer(format string) (buf []byte) {
|
||
|
const bufSize = 64
|
||
|
max := len(format) + 10
|
||
|
if max < bufSize {
|
||
|
var b [bufSize]byte
|
||
|
buf = b[:0]
|
||
|
} else {
|
||
|
buf = make([]byte, 0, max)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func year(y int) time.Time {
|
||
|
return time.Date(y, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||
|
}
|
||
|
|
||
|
func appendWeekNumber(dst []byte, t time.Time, flag byte, sunday bool) []byte {
|
||
|
offset := int(t.Weekday())
|
||
|
if sunday {
|
||
|
offset = 6 - offset
|
||
|
} else if offset != 0 {
|
||
|
offset = 7 - offset
|
||
|
}
|
||
|
return appendInt2(dst, (t.YearDay()+offset)/7, flag)
|
||
|
}
|
||
|
|
||
|
func append12Hour(dst []byte, t time.Time, flag byte) []byte {
|
||
|
h := t.Hour()
|
||
|
if h == 0 {
|
||
|
h = 12
|
||
|
} else if h > 12 {
|
||
|
h -= 12
|
||
|
}
|
||
|
return appendInt2(dst, h, flag)
|
||
|
}
|
||
|
|
||
|
func appendInt1(dst []byte, i int) []byte {
|
||
|
return append(dst, byte('0'+i))
|
||
|
}
|
||
|
|
||
|
func appendInt2(dst []byte, i int, flag byte) []byte {
|
||
|
if flag == 0 || i >= 10 {
|
||
|
return append(dst, smallsString[i*2:i*2+2]...)
|
||
|
}
|
||
|
if flag == ' ' {
|
||
|
dst = append(dst, flag)
|
||
|
}
|
||
|
return appendInt1(dst, i)
|
||
|
}
|
||
|
|
||
|
const smallsString = "" +
|
||
|
"00010203040506070809" +
|
||
|
"10111213141516171819" +
|
||
|
"20212223242526272829" +
|
||
|
"30313233343536373839" +
|
||
|
"40414243444546474849" +
|
||
|
"50515253545556575859" +
|
||
|
"60616263646566676869" +
|
||
|
"70717273747576777879" +
|
||
|
"80818283848586878889" +
|
||
|
"90919293949596979899"
|