mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 03:52:24 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			324 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			324 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"
 |