Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

111 lines
2.7 KiB
Go

package timeutil
import (
"fmt"
"os"
"strconv"
"time"
)
// Ptr formats a time.Time to an RFC3339Nano string pointer
// for DB storage. Returns nil for zero time.
func Ptr(t time.Time) *string {
if t.IsZero() {
return nil
}
s := t.UTC().Format(time.RFC3339Nano)
return &s
}
// Format formats a time.Time to an RFC3339Nano string for DB
// storage. Returns empty string for zero time.
func Format(t time.Time) string {
if t.IsZero() {
return ""
}
return t.UTC().Format(time.RFC3339Nano)
}
// IsValidDate reports whether s is a well-formed YYYY-MM-DD string.
func IsValidDate(s string) bool {
_, err := time.Parse("2006-01-02", s)
return err == nil
}
// IsValidTimestamp reports whether s is a well-formed RFC3339 or
// RFC3339Nano timestamp.
func IsValidTimestamp(s string) bool {
if _, err := time.Parse(time.RFC3339, s); err == nil {
return true
}
_, err := time.Parse(time.RFC3339Nano, s)
return err == nil
}
// ParseSince resolves a --since value against now: relative forms
// "Nh", "Nd", "Nw", "Nm" (months), "Ny", or an absolute YYYY-MM-DD
// (that date's midnight in now's location). N is a positive integer.
// Months/years use now.AddDate(-y, -m, 0) for calendar-aware arithmetic;
// hours/days/weeks use now.Add(-d).
func ParseSince(now time.Time, s string) (time.Time, error) {
if IsValidDate(s) {
return time.ParseInLocation("2006-01-02", s, now.Location())
}
if len(s) < 2 {
return time.Time{}, sinceFormatError(s)
}
unit := s[len(s)-1]
n, err := strconv.Atoi(s[:len(s)-1])
if err != nil || n <= 0 {
return time.Time{}, sinceFormatError(s)
}
switch unit {
case 'h':
return now.Add(-time.Duration(n) * time.Hour), nil
case 'd':
return now.Add(-time.Duration(n) * 24 * time.Hour), nil
case 'w':
return now.Add(-time.Duration(n) * 7 * 24 * time.Hour), nil
case 'm':
return now.AddDate(0, -n, 0), nil
case 'y':
return now.AddDate(-n, 0, 0), nil
default:
return time.Time{}, sinceFormatError(s)
}
}
// sinceFormatError names the accepted --since forms in the error message so
// callers can react without consulting docs.
func sinceFormatError(s string) error {
return fmt.Errorf(
"invalid --since %q: use Nh, Nd, Nw, Nm, Ny, or YYYY-MM-DD", s)
}
func BestEffortLocalTimezone() string {
return bestEffortLocalTimezone(
os.Getenv("TZ"),
time.Now().Location(),
)
}
func bestEffortLocalTimezone(envTZ string, loc *time.Location) string {
if name := validatedTimezoneName(envTZ); name != "" {
return name
}
if loc == nil {
return ""
}
return validatedTimezoneName(loc.String())
}
func validatedTimezoneName(name string) string {
if name == "" || name == "Local" {
return ""
}
if _, err := time.LoadLocation(name); err != nil {
return ""
}
return name
}