63 lines
1.7 KiB
Go
63 lines
1.7 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// timeLayouts are tried in order when parsing a persisted timestamp. The
|
|
// first two cover what this Go implementation itself writes (time zone
|
|
// aware); the rest cover what the original Python implementation wrote via
|
|
// datetime.now().isoformat() — no time zone, and microseconds included only
|
|
// when non-zero (Go's parser accepts an optional fractional-seconds suffix
|
|
// even when the layout doesn't specify one, so a single no-fraction layout
|
|
// handles both cases).
|
|
var timeLayouts = []string{
|
|
time.RFC3339Nano,
|
|
time.RFC3339,
|
|
"2006-01-02T15:04:05",
|
|
}
|
|
|
|
// Time wraps time.Time to support reading timestamps in both this Go
|
|
// implementation's own format and the original Python implementation's
|
|
// timezone-naive datetime.isoformat() format (e.g.
|
|
// "2026-03-19T18:21:47.740090"), which Go's default time.Time YAML/text
|
|
// decoding rejects outright since it strictly requires RFC3339 (with a zone
|
|
// offset).
|
|
type Time struct {
|
|
time.Time
|
|
}
|
|
|
|
// UnmarshalYAML tries each supported layout in turn.
|
|
func (t *Time) UnmarshalYAML(value *yaml.Node) error {
|
|
var s string
|
|
if err := value.Decode(&s); err != nil {
|
|
return err
|
|
}
|
|
if s == "" {
|
|
t.Time = time.Time{}
|
|
return nil
|
|
}
|
|
|
|
var lastErr error
|
|
for _, layout := range timeLayouts {
|
|
parsed, err := time.Parse(layout, s)
|
|
if err == nil {
|
|
t.Time = parsed
|
|
return nil
|
|
}
|
|
lastErr = err
|
|
}
|
|
return fmt.Errorf("cannot parse time %q: %w", s, lastErr)
|
|
}
|
|
|
|
// MarshalYAML writes the timestamp in RFC3339Nano (timezone-aware) format.
|
|
func (t Time) MarshalYAML() (interface{}, error) {
|
|
if t.Time.IsZero() {
|
|
return nil, nil
|
|
}
|
|
return t.Time.Format(time.RFC3339Nano), nil
|
|
}
|