Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cli/command/config/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/inspect"
"github.com/docker/go-units"
clduration "github.com/docker/cli/internal/duration"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
)
Expand Down Expand Up @@ -90,11 +90,11 @@ func (c *configContext) Name() string {
}

func (c *configContext) CreatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(c.c.Meta.CreatedAt)) + " ago"
return clduration.HumanDuration(time.Now().UTC().Sub(c.c.Meta.CreatedAt)) + " ago"
}

func (c *configContext) UpdatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(c.c.Meta.UpdatedAt)) + " ago"
return clduration.HumanDuration(time.Now().UTC().Sub(c.c.Meta.UpdatedAt)) + " ago"
}

func (c *configContext) Labels() string {
Expand Down
5 changes: 3 additions & 2 deletions cli/command/formatter/buildcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"time"

clduration "github.com/docker/cli/internal/duration"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/build"
)
Expand Down Expand Up @@ -150,7 +151,7 @@ func (c *buildCacheContext) CreatedAt() string {
}

func (c *buildCacheContext) CreatedSince() string {
return units.HumanDuration(time.Now().UTC().Sub(c.v.CreatedAt)) + " ago"
return clduration.HumanDuration(time.Now().UTC().Sub(c.v.CreatedAt)) + " ago"
}

func (c *buildCacheContext) LastUsedAt() string {
Expand All @@ -164,7 +165,7 @@ func (c *buildCacheContext) LastUsedSince() string {
if c.v.LastUsedAt == nil {
return ""
}
return units.HumanDuration(time.Now().UTC().Sub(*c.v.LastUsedAt)) + " ago"
return clduration.HumanDuration(time.Now().UTC().Sub(*c.v.LastUsedAt)) + " ago"
}

func (c *buildCacheContext) UsageCount() string {
Expand Down
3 changes: 2 additions & 1 deletion cli/command/formatter/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/containerd/platforms"
"github.com/distribution/reference"
clduration "github.com/docker/cli/internal/duration"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/container"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -236,7 +237,7 @@ func (c *ContainerContext) CreatedAt() string {
// by clock skew between the client and the daemon.
func (c *ContainerContext) RunningFor() string {
createdAt := time.Unix(c.c.Created, 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
return clduration.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}

// Platform returns a human-readable representation of the container's
Expand Down
3 changes: 2 additions & 1 deletion cli/command/formatter/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/distribution/reference"
clduration "github.com/docker/cli/internal/duration"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/image"
)
Expand Down Expand Up @@ -238,7 +239,7 @@ func (c *imageContext) CreatedSince() string {
return ""
}

return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
return clduration.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}

func (c *imageContext) CreatedAt() string {
Expand Down
3 changes: 2 additions & 1 deletion cli/command/image/formatter_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/docker/cli/cli/command/formatter"
clduration "github.com/docker/cli/internal/duration"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/client"
Expand Down Expand Up @@ -96,7 +97,7 @@ func (c *historyContext) CreatedSince() string {
if c.h.Created <= epoch {
return "N/A"
}
created := units.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0)))
created := clduration.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0)))
return created + " ago"
}

Expand Down
6 changes: 3 additions & 3 deletions cli/command/secret/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/inspect"
"github.com/docker/go-units"
clduration "github.com/docker/cli/internal/duration"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
)
Expand Down Expand Up @@ -90,7 +90,7 @@ func (c *secretContext) Name() string {
}

func (c *secretContext) CreatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.CreatedAt)) + " ago"
return clduration.HumanDuration(time.Now().UTC().Sub(c.s.Meta.CreatedAt)) + " ago"
}

func (c *secretContext) Driver() string {
Expand All @@ -101,7 +101,7 @@ func (c *secretContext) Driver() string {
}

func (c *secretContext) UpdatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.UpdatedAt)) + " ago"
return clduration.HumanDuration(time.Now().UTC().Sub(c.s.Meta.UpdatedAt)) + " ago"
}

func (c *secretContext) Labels() string {
Expand Down
4 changes: 2 additions & 2 deletions cli/command/task/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"github.com/distribution/reference"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/go-units"
clduration "github.com/docker/cli/internal/duration"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
)
Expand Down Expand Up @@ -121,7 +121,7 @@ func (c *taskContext) DesiredState() string {
func (c *taskContext) CurrentState() string {
return fmt.Sprintf("%s %s ago",
formatter.PrettyPrint(c.task.Status.State),
strings.ToLower(units.HumanDuration(time.Since(c.task.Status.Timestamp))),
strings.ToLower(clduration.HumanDuration(time.Since(c.task.Status.Timestamp))),
)
}

Expand Down
50 changes: 50 additions & 0 deletions internal/duration/duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Package duration provides human-readable duration formatting for the CLI.
package duration

import (
"fmt"
"time"
)

const (
day = 24 * time.Hour
week = 7 * day
month = 30 * day
year = 365 * day
)

// HumanDuration returns a human-readable approximation of a duration
// (e.g. "About a minute", "4 hours ago" style inputs without the "ago").
// Day/week/month/year boundaries use round-half-up so CLI CREATED columns
// stay closer to Docker Desktop.
func HumanDuration(d time.Duration) string {
if d < 0 {
d = -d
}
if seconds := int(d.Seconds()); seconds < 1 {
return "Less than a second"
} else if seconds == 1 {
return "1 second"
} else if seconds < 60 {
return fmt.Sprintf("%d seconds", seconds)
} else if minutes := int(d.Minutes()); minutes == 1 {
return "About a minute"
} else if minutes < 60 {
return fmt.Sprintf("%d minutes", minutes)
} else if hours := roundDuration(d, time.Hour); hours == 1 {
return "About an hour"
} else if d < 48*time.Hour {
return fmt.Sprintf("%d hours", hours)
} else if d < 2*week {
return fmt.Sprintf("%d days", roundDuration(d, day))
} else if d < 2*month {
return fmt.Sprintf("%d weeks", roundDuration(d, week))
} else if d < 2*year {
return fmt.Sprintf("%d months", roundDuration(d, month))
}
return fmt.Sprintf("%d years", roundDuration(d, year))
}

func roundDuration(d, unit time.Duration) int {
return int((d + unit/2) / unit)
}
21 changes: 21 additions & 0 deletions internal/duration/duration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package duration

import (
"testing"
"time"
)

func TestHumanDurationRounding(t *testing.T) {
// 3.958 days should round to 4 days (Desktop-style), not floor to 3.
d := time.Duration(3.958333 * float64(24*time.Hour))
got := HumanDuration(d)
if got != "4 days" {
t.Fatalf("got %q, want 4 days", got)
}
// 2 weeks + 4 days (~18 days) closer to 3 weeks than 2.
d = 2*week + 4*day
got = HumanDuration(d)
if got != "3 weeks" {
t.Fatalf("got %q, want 3 weeks", got)
}
}