daemon/internal/timestamp: replace GetTimestamp for Parse

All code in the daemon using `GetTimestamp` called `ParseTimestamp`
afterwards to convert the string value to a time.Time.

Replace it with a `Parse` function that immediately returns a
time.Time (in UTC) so that we don't have to "fuzzy" parse twice.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn committed 2026-03-03 21:51:41 +01:00
1 parent fc3cb00398
commit 553420b8f3
10 files changed
+48 -106

No files matched your search

+3 -8
View File
@@ -601,20 +601,15 @@ func (i *ImageService) setupFilters(ctx context.Context, imageFilters filters.Ar
return nil, err
}
now := time.Now()
err = imageFilters.WalkValues("until", func(value string) error {
ts, err := timestamp.GetTimestamp(value, time.Now())
until, err := timestamp.Parse(value, now)
if err != nil {
return errdefs.InvalidParameter(fmt.Errorf("invalid value for 'until' filter: %w", err))
}
seconds, nanoseconds, err := timestamp.ParseTimestamps(ts, 0)
if err != nil {
return errdefs.InvalidParameter(fmt.Errorf("invalid value for 'until' filter: %w", err))
}
until := time.Unix(seconds, nanoseconds)
fltrs = append(fltrs, func(image c8dimages.Image) bool {
created := image.CreatedAt
return created.Before(until)
return image.CreatedAt.Before(until)
})
return nil
})
+4 -16
View File
@@ -126,10 +126,7 @@ func TestLogEvents(t *testing.T) {
// 2016-03-07T17:28:03.129014751+02:00 container destroy 0b863f2a26c18557fc6cdadda007c459f9ec81b874780808138aea78a3595079 (image=ubuntu, name=small_hoover)
func TestLoadBufferedEvents(t *testing.T) {
now := time.Now()
f, err := timestamp.GetTimestamp("2016-03-07T17:28:03.100000000+02:00", now)
assert.NilError(t, err)
s, sNano, err := timestamp.ParseTimestamps(f, -1)
since, err := timestamp.Parse("2016-03-07T17:28:03.100000000+02:00", now)
assert.NilError(t, err)
m1, err := eventstestutils.Scan("2016-03-07T17:28:03.022433271+02:00 container die 0b863f2a26c18557fc6cdadda007c459f9ec81b874780808138aea78a3595079 (image=ubuntu, name=small_hoover)")
@@ -145,7 +142,6 @@ func TestLoadBufferedEvents(t *testing.T) {
events: []events.Message{*m1, *m2, *m3},
}
since := time.Unix(s, sNano)
until := time.Time{}
messages := evts.loadBufferedEvents(since, until, nil)
@@ -154,16 +150,11 @@ func TestLoadBufferedEvents(t *testing.T) {
func TestLoadBufferedEventsOnlyFromPast(t *testing.T) {
now := time.Now()
f, err := timestamp.GetTimestamp("2016-03-07T17:28:03.090000000+02:00", now)
since, err := timestamp.Parse("2016-03-07T17:28:03.090000000+02:00", now)
assert.NilError(t, err)
s, sNano, err := timestamp.ParseTimestamps(f, 0)
assert.NilError(t, err)
f, err = timestamp.GetTimestamp("2016-03-07T17:28:03.100000000+02:00", now)
assert.NilError(t, err)
u, uNano, err := timestamp.ParseTimestamps(f, 0)
until, err := timestamp.Parse("2016-03-07T17:28:03.100000000+02:00", now)
assert.NilError(t, err)
m1, err := eventstestutils.Scan("2016-03-07T17:28:03.022433271+02:00 container die 0b863f2a26c18557fc6cdadda007c459f9ec81b874780808138aea78a3595079 (image=ubuntu, name=small_hoover)")
@@ -179,9 +170,6 @@ func TestLoadBufferedEventsOnlyFromPast(t *testing.T) {
events: []events.Message{*m1, *m2, *m3},
}
since := time.Unix(s, sNano)
until := time.Unix(u, uNano)
messages := evts.loadBufferedEvents(since, until, nil)
assert.Assert(t, is.Len(messages, 1))
assert.Check(t, is.Equal(messages[0].Type, events.NetworkEventType))
+3 -8
View File
@@ -45,12 +45,7 @@ func Scan(text string) (*events.Message, error) {
return nil, fmt.Errorf("text is not an event: %s", text)
}
f, err := timestamp.GetTimestamp(md["timestamp"], time.Now())
if err != nil {
return nil, err
}
t, tn, err := timestamp.ParseTimestamps(f, -1)
created, err := timestamp.Parse(md["timestamp"], time.Now())
if err != nil {
return nil, err
}
@@ -62,8 +57,8 @@ func Scan(text string) (*events.Message, error) {
}
return &events.Message{
Time: t,
TimeNano: time.Unix(t, tn).UnixNano(),
Time: created.Unix(),
TimeNano: created.UnixNano(),
Type: events.Type(md["eventType"]),
Action: events.Action(md["action"]),
Actor: events.Actor{
+4 -7
View File
@@ -62,17 +62,14 @@ func (i *ImageService) Images(ctx context.Context, opts imagebackend.ListOptions
return nil, err
}
now := time.Now()
err = opts.Filters.WalkValues("until", func(value string) error {
ts, err := timestamp.GetTimestamp(value, time.Now())
ts, err := timestamp.Parse(value, now)
if err != nil {
return errdefs.InvalidParameter(fmt.Errorf("invalid value for 'until' filter: %w", err))
}
seconds, nanoseconds, err := timestamp.ParseTimestamps(ts, 0)
if err != nil {
return errdefs.InvalidParameter(fmt.Errorf("invalid value for 'until' filter: %w", err))
}
if tsUnix := time.Unix(seconds, nanoseconds); beforeFilter.IsZero() || beforeFilter.After(tsUnix) {
beforeFilter = tsUnix
if beforeFilter.IsZero() || beforeFilter.After(ts) {
beforeFilter = ts
}
return nil
})
+2 -6
View File
@@ -193,13 +193,9 @@ func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
if len(untilFilters) > 1 {
return time.Time{}, errdefs.InvalidParameter(errors.New("more than one until filter specified"))
}
ts, err := timestamp.GetTimestamp(untilFilters[0], time.Now())
t, err := timestamp.Parse(untilFilters[0], time.Now())
if err != nil {
return time.Time{}, errdefs.InvalidParameter(fmt.Errorf("invalid value for 'until' filter: %w", err))
}
seconds, nanoseconds, err := timestamp.ParseTimestamps(ts, 0)
if err != nil {
return time.Time{}, errdefs.InvalidParameter(fmt.Errorf("invalid value for 'until' filter: %w", err))
}
return time.Unix(seconds, nanoseconds), nil
return t, nil
}
+2 -9
View File
@@ -674,20 +674,13 @@ func toBuildkitPruneInfo(opts buildbackend.CachePruneOptions) (client.PruneInfo,
case 0:
// nothing to do
case 1:
ts, err := timestamp.GetTimestamp(untilValues[0], time.Now())
parsed, err := timestamp.Parse(untilValues[0], time.Now())
if err != nil {
return client.PruneInfo{}, errInvalidFilterValue{
errors.Wrapf(err, "%q filter expects a duration (e.g., '24h') or a timestamp", filterKey),
}
}
seconds, nanoseconds, err := timestamp.ParseTimestamps(ts, 0)
if err != nil {
return client.PruneInfo{}, errInvalidFilterValue{
errors.Wrapf(err, "failed to parse timestamp %q", ts),
}
}
until = time.Since(time.Unix(seconds, nanoseconds))
until = time.Since(parsed)
default:
return client.PruneInfo{}, errMultipleFilterValues{}
}
+16 -20
View File
@@ -17,19 +17,17 @@ const (
dateLocal = "2006-01-02" // RFC3339 with local timezone and time at 00:00:00
)
// GetTimestamp tries to parse given string as golang duration,
// then RFC3339 time and finally as a Unix timestamp. If
// any of these were successful, it returns a Unix timestamp
// as string otherwise returns the given value back.
// In case of duration input, the returned timestamp is computed
// as the given reference time minus the amount of the duration.
func GetTimestamp(value string, reference time.Time) (string, error) {
// Parse tries to parse given string as golang duration, then RFC3339 time and
// finally as a Unix timestamp. The returned time is normalized to UTC.
//
// In case of duration input, the returned timestamp is computed as the given
// reference time minus the amount of the duration.
func Parse(value string, reference time.Time) (time.Time, error) {
if strings.TrimSpace(value) == "" {
return time.Time{}, errors.New("failed to parse value as time or duration: value is empty")
}
if d, err := time.ParseDuration(value); value != "0" && err == nil {
t := reference.Add(-d)
if t.Nanosecond() == 0 {
return strconv.FormatInt(t.Unix(), 10), nil
}
return fmt.Sprintf("%d.%09d", t.Unix(), t.Nanosecond()), nil
return reference.Add(-d).UTC(), nil
}
var format string
@@ -88,18 +86,16 @@ func GetTimestamp(value string, reference time.Time) (string, error) {
if err != nil {
// if there is a `-` then it's an RFC3339 like timestamp
if strings.Contains(value, "-") {
return "", err // was probably an RFC3339 like timestamp but the parser failed with an error
return time.Time{}, err // was probably an RFC3339 like timestamp but the parser failed with an error
}
if _, _, err := parseTimestamp(value); err != nil {
return "", fmt.Errorf("failed to parse value as time or duration: %q", value)
sec, nsec, err := parseTimestamp(value)
if err != nil {
return time.Time{}, fmt.Errorf("failed to parse value as time or duration: %q", value)
}
return value, nil // unix timestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server)
return time.Unix(sec, nsec), nil
}
if t.Nanosecond() == 0 {
return strconv.FormatInt(t.Unix(), 10), nil
}
return fmt.Sprintf("%d.%09d", t.Unix(), t.Nanosecond()), nil
return t.UTC(), nil
}
// ParseTimestamps returns seconds and nanoseconds from a timestamp that has
+6 -14
View File
@@ -1,8 +1,6 @@
package timestamp_test
import (
"fmt"
"strconv"
"testing"
"time"
@@ -11,7 +9,7 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestGetTimestamp(t *testing.T) {
func TestParse(t *testing.T) {
now := time.Date(2020, 1, 2, 3, 4, 5, 123456789, time.UTC)
tests := []struct {
in string
@@ -73,26 +71,20 @@ func TestGetTimestamp(t *testing.T) {
name = "<empty>"
}
t.Run(name, func(t *testing.T) {
out, err := timestamp.GetTimestamp(tc.in, now)
out, err := timestamp.Parse(tc.in, now)
if tc.expectedErr {
assert.Assert(t, err != nil, "expected error for %q, got none", tc.in)
return
}
assert.NilError(t, err)
exp, err := time.Parse(time.RFC3339Nano, tc.expected)
want, err := time.Parse(time.RFC3339Nano, tc.expected)
assert.NilError(t, err, "invalid expected value")
var want string
if exp.Nanosecond() == 0 {
want = strconv.FormatInt(exp.Unix(), 10)
} else {
want = fmt.Sprintf("%d.%09d", exp.Unix(), exp.Nanosecond())
}
assert.Assert(t, out == want,
assert.Assert(t, out.Equal(want),
"expected: %s\ngot: %s",
want,
out,
want.Format(time.RFC3339Nano),
out.Format(time.RFC3339Nano),
)
})
}
+3 -7
View File
@@ -96,20 +96,16 @@ func newFilter(args filters.Args) (Filter, error) {
if err := args.WalkValues("type", validateNetworkTypeFilter); err != nil {
return Filter{}, err
}
until := time.Time{}
var until time.Time
if untilFilters := args.Get("until"); len(untilFilters) > 0 {
if len(untilFilters) > 1 {
return Filter{}, errdefs.InvalidParameter(errors.New("more than one until filter specified"))
}
ts, err := timestamp.GetTimestamp(untilFilters[0], time.Now())
var err error
until, err = timestamp.Parse(untilFilters[0], time.Now())
if err != nil {
return Filter{}, errdefs.InvalidParameter(err)
}
seconds, nanoseconds, err := timestamp.ParseTimestamps(ts, 0)
if err != nil {
return Filter{}, errdefs.InvalidParameter(err)
}
until = time.Unix(seconds, nanoseconds)
}
return Filter{
args: args,
+5 -11
View File
@@ -205,24 +205,18 @@ func (daemon *Daemon) NetworkPrune(ctx context.Context, filterArgs filters.Args)
}
func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
until := time.Time{}
if !pruneFilters.Contains("until") {
return until, nil
return time.Time{}, nil
}
untilFilters := pruneFilters.Get("until")
if len(untilFilters) > 1 {
return until, errdefs.InvalidParameter(errors.New("more than one until filter specified"))
return time.Time{}, errdefs.InvalidParameter(errors.New("more than one until filter specified"))
}
ts, err := timestamp.GetTimestamp(untilFilters[0], time.Now())
t, err := timestamp.Parse(untilFilters[0], time.Now())
if err != nil {
return until, errdefs.InvalidParameter(err)
return time.Time{}, errdefs.InvalidParameter(err)
}
seconds, nanoseconds, err := timestamp.ParseTimestamps(ts, 0)
if err != nil {
return until, errdefs.InvalidParameter(err)
}
until = time.Unix(seconds, nanoseconds)
return until, nil
return t, nil
}
func matchLabels(pruneFilters filters.Args, labels map[string]string) bool {