Files
go-fa-api/time.go
2026-05-25 22:27:18 +02:00

65 lines
2.0 KiB
Go

package fa
import (
"fmt"
"strings"
"time"
)
// faDateLayouts lists every layout FA has been observed emitting in
// title= attributes of date elements. Tried in order.
var faDateLayouts = []string{
"January 2, 2006 03:04:05 PM", // "March 23, 2026 09:01:08 AM" current beta popup_date title
"January 2, 2006 3:04:05 PM",
"Jan 2, 2006 03:04:05 PM", // 3-letter month variant
"Jan 2, 2006 3:04:05 PM",
"Jan 2, 2006 03:04 PM", // legacy beta layout (no seconds)
"Jan 2, 2006 3:04 PM",
"2006-01-02T15:04:05Z07:00",
time.RFC3339,
}
// ParseFADate parses a FurAffinity-formatted date string. FA renders dates
// either as a "popup" with the full timestamp in a title attribute, or as a
// relative phrase ("5 hours ago") in the visible text. Callers should pass
// the title attribute when available.
//
// FA does not include timezone information in its displayed format; the site
// uses server-local time historically labelled as UTC-7. We treat parsed
// values as UTC because that is what the SDK consistently exposes callers
// who need a wall-clock display should convert.
func ParseFADate(s string) (time.Time, error) {
s = strings.TrimSpace(s)
if s == "" {
return time.Time{}, fmt.Errorf("parse fa date: empty string")
}
cleaned := stripOrdinals(s)
for _, layout := range faDateLayouts {
if t, err := time.ParseInLocation(layout, cleaned, time.UTC); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("parse fa date %q: no matching layout", s)
}
// stripOrdinals removes English ordinal suffixes (st, nd, rd, th) from a date
// string so it can be parsed by Go's reference layout. "Mar 17th, 2026" →
// "Mar 17, 2026".
func stripOrdinals(s string) string {
var b strings.Builder
b.Grow(len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if (c >= '0' && c <= '9') && i+2 < len(s) {
next2 := strings.ToLower(s[i+1 : i+3])
if next2 == "st" || next2 == "nd" || next2 == "rd" || next2 == "th" {
b.WriteByte(c)
i += 2
continue
}
}
b.WriteByte(c)
}
return b.String()
}