65 lines
2.0 KiB
Go
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()
|
|
}
|