Repository: cli/cli
Pull request #8222 (2023-10-20 09:31:04)

Remove uses of FuzzyAgoAbbr in tables
Follow up to #8157

This PR brings consistency to all time fields used in tables by removing the uses of `FuzzyAgoAbbr`. One downside is that the width of these tables will now increase a bit, but I think the upside of consistency across our output is worth it. The other downside is that `run list` and `pr list` output format changed in non-TTY mode. I don't think this is a breaking change for `run list` as previously it was not parsable by a script since it was using `FuzzyAgoAbbr`, I view this as an improvement as it will now be in the `time.RFC3339` format. For `pr list` that is another story. It was previously in a machine parsable format and we are changing it in `time.RFC3339`. I personally think this is okay, but I might be being too cavalier. I can always revert the non-tty part for `pr list` to what is was previously. 


Patch:
diff --git a/internal/tableprinter/table_printer.go b/internal/tableprinter/table_printer.go
index ed734cbad..c7056d478 100644
--- a/internal/tableprinter/table_printer.go
+++ b/internal/tableprinter/table_printer.go
@@ -30,12 +30,13 @@ func (tp *TablePrinter) AddTimeField(now, t time.Time, c func(string) string) {
 	} else {
 		tf = t.Format(time.RFC3339)
 	}
-	tp.AddField(tf, tableprinter.WithColor(c))
+	tp.AddField(tf, WithColor(c))
 }
 
 var (
-	WithTruncate = tableprinter.WithTruncate
 	WithColor    = tableprinter.WithColor
+	WithPadding  = tableprinter.WithPadding
+	WithTruncate = tableprinter.WithTruncate
 )
 
 type headerOption struct {
@@ -77,8 +78,8 @@ func NewWithWriter(w io.Writer, isTTY bool, maxWidth int, cs *iostreams.ColorSch
 
 		tp.AddHeader(
 			headers.columns,
-			tableprinter.WithPadding(paddingFunc),
-			tableprinter.WithColor(cs.LightGrayUnderline),
+			WithPadding(paddingFunc),
+			WithColor(cs.LightGrayUnderline),
 		)
 	}
 
diff --git a/pkg/cmd/codespace/list.go b/pkg/cmd/codespace/list.go
index 086334e29..c1acccabb 100644
--- a/pkg/cmd/codespace/list.go
+++ b/pkg/cmd/codespace/list.go
@@ -8,7 +8,6 @@ import (
 	"github.com/MakeNowJust/heredoc"
 	"github.com/cli/cli/v2/internal/codespaces/api"
 	"github.com/cli/cli/v2/internal/tableprinter"
-	"github.com/cli/cli/v2/internal/text"
 	"github.com/cli/cli/v2/pkg/cmdutil"
 	"github.com/spf13/cobra"
 )
@@ -169,15 +168,11 @@ func (a *App) List(ctx context.Context, opts *listOptions, exporter cmdutil.Expo
 			tp.AddField(c.State, tableprinter.WithColor(stateColor))
 		}
 
-		if tp.IsTTY() {
-			ct, err := time.Parse(time.RFC3339, c.CreatedAt)
-			if err != nil {
-				return fmt.Errorf("error parsing date %q: %w", c.CreatedAt, err)
-			}
-			tp.AddField(text.FuzzyAgoAbbr(time.Now(), ct), tableprinter.WithColor(cs.Gray))
-		} else {
-			tp.AddField(c.CreatedAt)
+		ct, err := time.Parse(time.RFC3339, c.CreatedAt)
+		if err != nil {
+			return fmt.Errorf("error parsing date %q: %w", c.CreatedAt, err)
 		}
+		tp.AddTimeField(time.Now(), ct, cs.Gray)
 
 		if hasNonProdVSCSTarget {
 			tp.AddField(c.VSCSTarget)
diff --git a/pkg/cmd/gpg-key/list/list.go b/pkg/cmd/gpg-key/list/list.go
index 1ecc88b85..4c594c584 100644
--- a/pkg/cmd/gpg-key/list/list.go
+++ b/pkg/cmd/gpg-key/list/list.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/cli/cli/v2/internal/config"
 	"github.com/cli/cli/v2/internal/tableprinter"
-	"github.com/cli/cli/v2/internal/text"
 	"github.com/cli/cli/v2/pkg/cmdutil"
 	"github.com/cli/cli/v2/pkg/iostreams"
 	"github.com/spf13/cobra"
@@ -79,13 +78,7 @@ func listRun(opts *ListOptions) error {
 		t.AddField(gpgKey.Emails.String())
 		t.AddField(gpgKey.KeyID)
 		t.AddField(gpgKey.PublicKey, tableprinter.WithTruncate(truncateMiddle))
-
-		createdAt := gpgKey.CreatedAt.Format(time.RFC3339)
-		if t.IsTTY() {
-			createdAt = text.FuzzyAgoAbbr(now, gpgKey.CreatedAt)
-		}
-		t.AddField(createdAt, tableprinter.WithColor(cs.Gray))
-
+		t.AddTimeField(now, gpgKey.CreatedAt, cs.Gray)
 		expiresAt := gpgKey.ExpiresAt.Format(time.RFC3339)
 		if t.IsTTY() {
 			if gpgKey.ExpiresAt.IsZero() {
@@ -95,7 +88,6 @@ func listRun(opts *ListOptions) error {
 			}
 		}
 		t.AddField(expiresAt, tableprinter.WithColor(cs.Gray))
-
 		t.EndRow()
 	}
 
diff --git a/pkg/cmd/pr/list/list.go b/pkg/cmd/pr/list/list.go
index e900a5d7b..7721ecc1b 100644
--- a/pkg/cmd/pr/list/list.go
+++ b/pkg/cmd/pr/list/list.go
@@ -224,11 +224,7 @@ func listRun(opts *ListOptions) error {
 		if !isTTY {
 			table.AddField(prStateWithDraft(&pr))
 		}
-		if isTTY {
-			table.AddField(text.FuzzyAgo(opts.Now(), pr.CreatedAt), tableprinter.WithColor(cs.Gray))
-		} else {
-			table.AddField(pr.CreatedAt.String())
-		}
+		table.AddTimeField(opts.Now(), pr.CreatedAt, cs.Gray)
 		table.EndRow()
 	}
 	err = table.Render()
diff --git a/pkg/cmd/project/field-list/field_list.go b/pkg/cmd/project/field-list/field_list.go
index 3e36e2a86..f15369c94 100644
--- a/pkg/cmd/project/field-list/field_list.go
+++ b/pkg/cmd/project/field-list/field_list.go
@@ -23,7 +23,6 @@ type listOpts struct {
 
 type listConfig struct {
 	io     *iostreams.IOStreams
-	tp     *tableprinter.TablePrinter
 	client *queries.Client
 	opts   listOpts
 }
@@ -52,10 +51,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C
 				opts.number = int32(num)
 			}
 
-			t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Name", "Data type", "ID"))
 			config := listConfig{
 				io:     f.IOStreams,
-				tp:     t,
 				client: client,
 				opts:   opts,
 			}
@@ -109,14 +106,16 @@ func printResults(config listConfig, fields []queries.ProjectField, login string
 		return cmdutil.NewNoResultsError(fmt.Sprintf("Project %d for owner %s has no fields", config.opts.number, login))
 	}
 
+	tp := tableprinter.New(config.io, tableprinter.WithHeader("Name", "Data type", "ID"))
+
 	for _, f := range fields {
-		config.tp.AddField(f.Name())
-		config.tp.AddField(f.Type())
-		config.tp.AddField(f.ID(), tableprinter.WithTruncate(nil))
-		config.tp.EndRow()
+		tp.AddField(f.Name())
+		tp.AddField(f.Type())
+		tp.AddField(f.ID(), tableprinter.WithTruncate(nil))
+		tp.EndRow()
 	}
 
-	return config.tp.Render()
+	return tp.Render()
 }
 
 func printJSON(config listConfig, project *queries.Project) error {
diff --git a/pkg/cmd/project/item-list/item_list.go b/pkg/cmd/project/item-list/item_list.go
index f2e9bf8a6..45ca0c77f 100644
--- a/pkg/cmd/project/item-list/item_list.go
+++ b/pkg/cmd/project/item-list/item_list.go
@@ -23,7 +23,6 @@ type listOpts struct {
 
 type listConfig struct {
 	io     *iostreams.IOStreams
-	tp     *tableprinter.TablePrinter
 	client *queries.Client
 	opts   listOpts
 }
@@ -52,10 +51,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C
 				opts.number = int32(num)
 			}
 
-			t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Type", "Title", "Number", "Repository", "ID"))
 			config := listConfig{
 				io:     f.IOStreams,
-				tp:     t,
 				client: client,
 				opts:   opts,
 			}
@@ -108,20 +105,22 @@ func printResults(config listConfig, items []queries.ProjectItem, login string)
 		return cmdutil.NewNoResultsError(fmt.Sprintf("Project %d for owner %s has no items", config.opts.number, login))
 	}
 
+	tp := tableprinter.New(config.io, tableprinter.WithHeader("Type", "Title", "Number", "Repository", "ID"))
+
 	for _, i := range items {
-		config.tp.AddField(i.Type())
-		config.tp.AddField(i.Title())
+		tp.AddField(i.Type())
+		tp.AddField(i.Title())
 		if i.Number() == 0 {
-			config.tp.AddField("")
+			tp.AddField("")
 		} else {
-			config.tp.AddField(strconv.Itoa(i.Number()))
+			tp.AddField(strconv.Itoa(i.Number()))
 		}
-		config.tp.AddField(i.Repo())
-		config.tp.AddField(i.ID(), tableprinter.WithTruncate(nil))
-		config.tp.EndRow()
+		tp.AddField(i.Repo())
+		tp.AddField(i.ID(), tableprinter.WithTruncate(nil))
+		tp.EndRow()
 	}
 
-	return config.tp.Render()
+	return tp.Render()
 }
 
 func printJSON(config listConfig, project *queries.Project) error {
diff --git a/pkg/cmd/project/list/list.go b/pkg/cmd/project/list/list.go
index d3168d7eb..88f419b90 100644
--- a/pkg/cmd/project/list/list.go
+++ b/pkg/cmd/project/list/list.go
@@ -23,7 +23,6 @@ type listOpts struct {
 }
 
 type listConfig struct {
-	tp        *tableprinter.TablePrinter
 	client    *queries.Client
 	opts      listOpts
 	URLOpener func(string) error
@@ -52,9 +51,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C
 			URLOpener := func(url string) error {
 				return f.Browser.Browse(url)
 			}
-			t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Number", "Title", "State", "ID"))
+
 			config := listConfig{
-				tp:        t,
 				client:    client,
 				opts:      opts,
 				URLOpener: URLOpener,
@@ -68,7 +66,6 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C
 			return runList(config)
 		},
 	}
-
 	listCmd.Flags().StringVar(&opts.owner, "owner", "", "Login of the owner")
 	listCmd.Flags().BoolVarP(&opts.closed, "closed", "", false, "Include closed projects")
 	listCmd.Flags().BoolVarP(&opts.web, "web", "w", false, "Open projects list in the browser")
@@ -157,21 +154,23 @@ func printResults(config listConfig, projects []queries.Project, owner string) e
 		return cmdutil.NewNoResultsError(fmt.Sprintf("No projects found for %s", owner))
 	}
 
+	tp := tableprinter.New(config.io, tableprinter.WithHeader("Number", "Title", "State", "ID"))
+
 	for _, p := range projects {
-		config.tp.AddField(strconv.Itoa(int(p.Number)), tableprinter.WithTruncate(nil))
-		config.tp.AddField(p.Title)
+		tp.AddField(strconv.Itoa(int(p.Number)), tableprinter.WithTruncate(nil))
+		tp.AddField(p.Title)
 		var state string
 		if p.Closed {
 			state = "closed"
 		} else {
 			state = "open"
 		}
-		config.tp.AddField(state)
-		config.tp.AddField(p.ID, tableprinter.WithTruncate(nil))
-		config.tp.EndRow()
+		tp.AddField(state)
+		tp.AddField(p.ID, tableprinter.WithTruncate(nil))
+		tp.EndRow()
 	}
 
-	return config.tp.Render()
+	return tp.Render()
 }
 
 func printJSON(config listConfig, projects []queries.Project, totalCount int) error {
diff --git a/pkg/cmd/repo/deploy-key/list/list.go b/pkg/cmd/repo/deploy-key/list/list.go
index 3df78212c..0cf2861c1 100644
--- a/pkg/cmd/repo/deploy-key/list/list.go
+++ b/pkg/cmd/repo/deploy-key/list/list.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/cli/cli/v2/internal/ghrepo"
 	"github.com/cli/cli/v2/internal/tableprinter"
-	"github.com/cli/cli/v2/internal/text"
 	"github.com/cli/cli/v2/pkg/cmdutil"
 	"github.com/cli/cli/v2/pkg/iostreams"
 	"github.com/spf13/cobra"
@@ -85,20 +84,13 @@ func listRun(opts *ListOptions) error {
 		sshID := strconv.Itoa(deployKey.ID)
 		t.AddField(sshID)
 		t.AddField(deployKey.Title)
-
 		sshType := "read-only"
 		if !deployKey.ReadOnly {
 			sshType = "read-write"
 		}
 		t.AddField(sshType)
 		t.AddField(deployKey.Key, tableprinter.WithTruncate(truncateMiddle))
-
-		// TODO: Modify AddTimeField, add AddAbbrTimeField, or something else.
-		createdAt := deployKey.CreatedAt.Format(time.RFC3339)
-		if t.IsTTY() {
-			createdAt = text.FuzzyAgoAbbr(now, deployKey.CreatedAt)
-		}
-		t.AddField(createdAt, tableprinter.WithColor(cs.Gray))
+		t.AddTimeField(now, deployKey.CreatedAt, cs.Gray)
 		t.EndRow()
 	}
 
diff --git a/pkg/cmd/repo/list/list.go b/pkg/cmd/repo/list/list.go
index f91b847a7..e48142723 100644
--- a/pkg/cmd/repo/list/list.go
+++ b/pkg/cmd/repo/list/list.go
@@ -195,11 +195,7 @@ func listRun(opts *ListOptions) error {
 		tp.AddField(repo.NameWithOwner, tableprinter.WithColor(cs.Bold))
 		tp.AddField(text.RemoveExcessiveWhitespace(repo.Description))
 		tp.AddField(info, tableprinter.WithColor(infoColor))
-		if tp.IsTTY() {
-			tp.AddField(text.FuzzyAgoAbbr(opts.Now(), *t), tableprinter.WithColor(cs.Gray))
-		} else {
-			tp.AddField(t.Format(time.RFC3339))
-		}
+		tp.AddTimeField(opts.Now(), *t, cs.Gray)
 		tp.EndRow()
 	}
 
diff --git a/pkg/cmd/run/list/list.go b/pkg/cmd/run/list/list.go
index 39e14442a..7776b3521 100644
--- a/pkg/cmd/run/list/list.go
+++ b/pkg/cmd/run/list/list.go
@@ -8,7 +8,6 @@ import (
 	"github.com/cli/cli/v2/api"
 	"github.com/cli/cli/v2/internal/ghrepo"
 	"github.com/cli/cli/v2/internal/tableprinter"
-	"github.com/cli/cli/v2/internal/text"
 	"github.com/cli/cli/v2/pkg/cmd/run/shared"
 	workflowShared "github.com/cli/cli/v2/pkg/cmd/workflow/shared"
 	"github.com/cli/cli/v2/pkg/cmdutil"
@@ -150,21 +149,17 @@ func listRun(opts *ListOptions) error {
 			tp.AddField(string(run.Status))
 			tp.AddField(string(run.Conclusion))
 		}
-
 		tp.AddField(run.Title(), tableprinter.WithColor(cs.Bold))
-
 		tp.AddField(run.WorkflowName())
 		tp.AddField(run.HeadBranch, tableprinter.WithColor(cs.Bold))
 		tp.AddField(string(run.Event))
 		tp.AddField(fmt.Sprintf("%d", run.ID), tableprinter.WithColor(cs.Cyan))
-
 		tp.AddField(run.Duration(opts.now).String())
-		tp.AddField(text.FuzzyAgoAbbr(time.Now(), run.StartedTime()))
+		tp.AddTimeField(time.Now(), run.StartedTime(), cs.Gray)
 		tp.EndRow()
 	}
 
-	err = tp.Render()
-	if err != nil {
+	if err := tp.Render(); err != nil {
 		return err
 	}
 
diff --git a/pkg/cmd/ssh-key/list/list.go b/pkg/cmd/ssh-key/list/list.go
index 828fc0b31..f00075944 100644
--- a/pkg/cmd/ssh-key/list/list.go
+++ b/pkg/cmd/ssh-key/list/list.go
@@ -11,7 +11,6 @@ import (
 	"github.com/cli/cli/v2/api"
 	"github.com/cli/cli/v2/internal/config"
 	"github.com/cli/cli/v2/internal/tableprinter"
-	"github.com/cli/cli/v2/internal/text"
 	"github.com/cli/cli/v2/pkg/cmd/ssh-key/shared"
 	"github.com/cli/cli/v2/pkg/cmdutil"
 	"github.com/cli/cli/v2/pkg/iostreams"
@@ -85,22 +84,19 @@ func listRun(opts *ListOptions) error {
 
 	for _, sshKey := range sshKeys {
 		id := strconv.Itoa(sshKey.ID)
-		createdAt := sshKey.CreatedAt.Format(time.RFC3339)
-
 		if t.IsTTY() {
 			t.AddField(sshKey.Title)
 			t.AddField(id)
 			t.AddField(sshKey.Key, tableprinter.WithTruncate(truncateMiddle))
 			t.AddField(sshKey.Type)
-			t.AddField(text.FuzzyAgoAbbr(now, sshKey.CreatedAt), tableprinter.WithColor(cs.Gray))
+			t.AddTimeField(now, sshKey.CreatedAt, cs.Gray)
 		} else {
 			t.AddField(sshKey.Title)
 			t.AddField(sshKey.Key)
-			t.AddField(createdAt)
+			t.AddTimeField(now, sshKey.CreatedAt, cs.Gray)
 			t.AddField(id)
 			t.AddField(sshKey.Type)
 		}
-
 		t.EndRow()
 	}
 


Test patch:
diff --git a/pkg/cmd/codespace/list_test.go b/pkg/cmd/codespace/list_test.go
index 35f30581b..49bb0b4d2 100644
--- a/pkg/cmd/codespace/list_test.go
+++ b/pkg/cmd/codespace/list_test.go
@@ -85,6 +85,7 @@ func TestApp_List(t *testing.T) {
 						return []*api.Codespace{
 							{
 								DisplayName: "CS1",
+								CreatedAt:   "2023-01-01T00:00:00Z",
 							},
 						}, nil
 					},
@@ -106,6 +107,7 @@ func TestApp_List(t *testing.T) {
 						return []*api.Codespace{
 							{
 								DisplayName: "CS1",
+								CreatedAt:   "2023-01-01T00:00:00Z",
 							},
 						}, nil
 					},
@@ -129,6 +131,7 @@ func TestApp_List(t *testing.T) {
 						return []*api.Codespace{
 							{
 								DisplayName: "CS1",
+								CreatedAt:   "2023-01-01T00:00:00Z",
 							},
 						}, nil
 					},
@@ -159,6 +162,7 @@ func TestApp_List(t *testing.T) {
 						return []*api.Codespace{
 							{
 								DisplayName: "CS1",
+								CreatedAt:   "2023-01-01T00:00:00Z",
 							},
 						}, nil
 					},
diff --git a/pkg/cmd/gpg-key/list/list_test.go b/pkg/cmd/gpg-key/list/list_test.go
index a596275d1..de702498c 100644
--- a/pkg/cmd/gpg-key/list/list_test.go
+++ b/pkg/cmd/gpg-key/list/list_test.go
@@ -56,9 +56,9 @@ func Test_listRun(t *testing.T) {
 			}},
 			isTTY: true,
 			wantStdout: heredoc.Doc(`
-				EMAIL                KEY ID            PUBLIC KEY      ADDED  EXPIRES
-				johnny@test.com      ABCDEF1234567890  xJMEWfoofoofoo  1d     2099-01-01
-				monalisa@github.com  1234567890ABCDEF  xJMEWbarbarbar  1d     Never
+				EMAIL               KEY ID           PUBLIC KEY      ADDED            EXPIRES
+				johnny@test.com     ABCDEF123456...  xJMEWfoofoofoo  about 1 day ago  2099-01-01
+				monalisa@github...  1234567890AB...  xJMEWbarbarbar  about 1 day ago  Never
 			`),
 			wantStderr: "",
 		},
diff --git a/pkg/cmd/pr/list/list_test.go b/pkg/cmd/pr/list/list_test.go
index d4a658a81..61a8e3698 100644
--- a/pkg/cmd/pr/list/list_test.go
+++ b/pkg/cmd/pr/list/list_test.go
@@ -105,9 +105,9 @@ func TestPRList_nontty(t *testing.T) {
 
 	assert.Equal(t, "", output.Stderr())
 
-	assert.Equal(t, `32	New feature	feature	DRAFT	2022-08-24 20:01:12 +0000 UTC
-29	Fixed bad bug	hubot:bug-fix	OPEN	2022-07-20 19:01:12 +0000 UTC
-28	Improve documentation	docs	MERGED	2020-01-26 19:01:12 +0000 UTC
+	assert.Equal(t, `32	New feature	feature	DRAFT	2022-08-24T20:01:12Z
+29	Fixed bad bug	hubot:bug-fix	OPEN	2022-07-20T19:01:12Z
+28	Improve documentation	docs	MERGED	2020-01-26T19:01:12Z
 `, output.String())
 }
 
diff --git a/pkg/cmd/project/field-list/field_list_test.go b/pkg/cmd/project/field-list/field_list_test.go
index 85e67451f..ca01599fb 100644
--- a/pkg/cmd/project/field-list/field_list_test.go
+++ b/pkg/cmd/project/field-list/field_list_test.go
@@ -3,7 +3,7 @@ package fieldlist
 import (
 	"testing"
 
-	"github.com/cli/cli/v2/internal/tableprinter"
+	"github.com/MakeNowJust/heredoc"
 	"github.com/cli/cli/v2/pkg/cmd/project/shared/queries"
 	"github.com/cli/cli/v2/pkg/cmdutil"
 	"github.com/cli/cli/v2/pkg/iostreams"
@@ -87,6 +87,101 @@ func TestNewCmdList(t *testing.T) {
 	}
 }
 
+func TestRunList_User_tty(t *testing.T) {
+	defer gock.Off()
+	// gock.Observe(gock.DumpRequest)
+
+	// get user ID
+	gock.New("https://api.github.com").
+		Post("/graphql").
+		MatchType("json").
+		JSON(map[string]interface{}{
+			"query": "query UserOrgOwner.*",
+			"variables": map[string]interface{}{
+				"login": "monalisa",
+			},
+		}).
+		Reply(200).
+		JSON(map[string]interface{}{
+			"data": map[string]interface{}{
+				"user": map[string]interface{}{
+					"id": "an ID",
+				},
+			},
+			"errors": []interface{}{
+				map[string]interface{}{
+					"type": "NOT_FOUND",
+					"path": []string{"organization"},
+				},
+			},
+		})
+
+	// list project fields
+	gock.New("https://api.github.com").
+		Post("/graphql").
+		JSON(map[string]interface{}{
+			"query": "query UserProject.*",
+			"variables": map[string]interface{}{
+				"login":       "monalisa",
+				"number":      1,
+				"firstItems":  queries.LimitMax,
+				"afterItems":  nil,
+				"firstFields": queries.LimitDefault,
+				"afterFields": nil,
+			},
+		}).
+		Reply(200).
+		JSON(map[string]interface{}{
+			"data": map[string]interface{}{
+				"user": map[string]interface{}{
+					"projectV2": map[string]interface{}{
+						"fields": map[string]interface{}{
+							"nodes": []map[string]interface{}{
+								{
+									"__typename": "ProjectV2Field",
+									"name":       "FieldTitle",
+									"id":         "field ID",
+								},
+								{
+									"__typename": "ProjectV2SingleSelectField",
+									"name":       "Status",
+									"id":         "status ID",
+								},
+								{
+									"__typename": "ProjectV2IterationField",
+									"name":       "Iterations",
+									"id":         "iteration ID",
+								},
+							},
+						},
+					},
+				},
+			},
+		})
+
+	client := queries.NewTestClient()
+
+	ios, _, stdout, _ := iostreams.Test()
+	ios.SetStdoutTTY(true)
+	config := listConfig{
+		opts: listOpts{
+			number: 1,
+			owner:  "monalisa",
+		},
+		client: client,
+		io:     ios,
+	}
+
+	err := runList(config)
+	assert.NoError(t, err)
+	assert.Equal(t, heredoc.Doc(`
+		NAME        DATA TYPE                   ID
+		FieldTitle  ProjectV2Field              field ID
+		Status      ProjectV2SingleSelectField  status ID
+		Iterations  ProjectV2IterationField     iteration ID
+  `), stdout.String())
+}
+
 func TestRunList_User(t *testing.T) {
 	defer gock.Off()
 	// gock.Observe(gock.DumpRequest)
@@ -163,8 +258,6 @@ func TestRunList_User(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			number: 1,
 			owner:  "monalisa",
@@ -257,8 +350,6 @@ func TestRunList_Org(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			number: 1,
 			owner:  "github",
@@ -341,8 +432,6 @@ func TestRunList_Me(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			number: 1,
 			owner:  "@me",
@@ -409,8 +498,6 @@ func TestRunList_Empty(t *testing.T) {
 
 	ios, _, _, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			number: 1,
 			owner:  "@me",
diff --git a/pkg/cmd/project/item-list/item_list_test.go b/pkg/cmd/project/item-list/item_list_test.go
index c36dfc589..f637ea99b 100644
--- a/pkg/cmd/project/item-list/item_list_test.go
+++ b/pkg/cmd/project/item-list/item_list_test.go
@@ -3,12 +3,12 @@ package itemlist
 import (
 	"testing"
 
+	"github.com/MakeNowJust/heredoc"
 	"github.com/cli/cli/v2/pkg/cmd/project/shared/queries"
 	"github.com/cli/cli/v2/pkg/cmdutil"
 	"github.com/cli/cli/v2/pkg/iostreams"
 	"github.com/google/shlex"
 
-	"github.com/cli/cli/v2/internal/tableprinter"
 	"github.com/stretchr/testify/assert"
 	"gopkg.in/h2non/gock.v1"
 )
@@ -88,6 +88,115 @@ func TestNewCmdList(t *testing.T) {
 	}
 }
 
+func TestRunList_User_tty(t *testing.T) {
+	defer gock.Off()
+	// gock.Observe(gock.DumpRequest)
+
+	// get user ID
+	gock.New("https://api.github.com").
+		Post("/graphql").
+		MatchType("json").
+		JSON(map[string]interface{}{
+			"query": "query UserOrgOwner.*",
+			"variables": map[string]interface{}{
+				"login": "monalisa",
+			},
+		}).
+		Reply(200).
+		JSON(map[string]interface{}{
+			"data": map[string]interface{}{
+				"user": map[string]interface{}{
+					"id": "an ID",
+				},
+			},
+			"errors": []interface{}{
+				map[string]interface{}{
+					"type": "NOT_FOUND",
+					"path": []string{"organization"},
+				},
+			},
+		})
+
+	// list project items
+	gock.New("https://api.github.com").
+		Post("/graphql").
+		JSON(map[string]interface{}{
+			"query": "query UserProjectWithItems.*",
+			"variables": map[string]interface{}{
+				"firstItems":  queries.LimitDefault,
+				"afterItems":  nil,
+				"firstFields": queries.LimitMax,
+				"afterFields": nil,
+				"login":       "monalisa",
+				"number":      1,
+			},
+		}).
+		Reply(200).
+		JSON(map[string]interface{}{
+			"data": map[string]interface{}{
+				"user": map[string]interface{}{
+					"projectV2": map[string]interface{}{
+						"items": map[string]interface{}{
+							"nodes": []map[string]interface{}{
+								{
+									"id": "issue ID",
+									"content": map[string]interface{}{
+										"__typename": "Issue",
+										"title":      "an issue",
+										"number":     1,
+										"repository": map[string]string{
+											"nameWithOwner": "cli/go-gh",
+										},
+									},
+								},
+								{
+									"id": "pull request ID",
+									"content": map[string]interface{}{
+										"__typename": "PullRequest",
+										"title":      "a pull request",
+										"number":     2,
+										"repository": map[string]string{
+											"nameWithOwner": "cli/go-gh",
+										},
+									},
+								},
+								{
+									"id": "draft issue ID",
+									"content": map[string]interface{}{
+										"title":      "draft issue",
+										"__typename": "DraftIssue",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		})
+
+	client := queries.NewTestClient()
+
+	ios, _, stdout, _ := iostreams.Test()
+	ios.SetStdoutTTY(true)
+	config := listConfig{
+		opts: listOpts{
+			number: 1,
+			owner:  "monalisa",
+		},
+		client: client,
+		io:     ios,
+	}
+
+	err := runList(config)
+	assert.NoError(t, err)
+	assert.Equal(t, heredoc.Doc(`
+		TYPE         TITLE           NUMBER  REPOSITORY  ID
+		Issue        an issue        1       cli/go-gh   issue ID
+		PullRequest  a pull request  2       cli/go-gh   pull request ID
+		DraftIssue   draft issue                         draft issue ID
+  `), stdout.String())
+}
+
 func TestRunList_User(t *testing.T) {
 	defer gock.Off()
 	// gock.Observe(gock.DumpRequest)
@@ -178,8 +287,6 @@ func TestRunList_User(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			number: 1,
 			owner:  "monalisa",
@@ -286,8 +393,6 @@ func TestRunList_Org(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			number: 1,
 			owner:  "github",
@@ -384,8 +489,6 @@ func TestRunList_Me(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			number: 1,
 			owner:  "@me",
diff --git a/pkg/cmd/project/list/list_test.go b/pkg/cmd/project/list/list_test.go
index bafdf8e93..bb840caa6 100644
--- a/pkg/cmd/project/list/list_test.go
+++ b/pkg/cmd/project/list/list_test.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"testing"
 
-	"github.com/cli/cli/v2/internal/tableprinter"
+	"github.com/MakeNowJust/heredoc"
 	"github.com/cli/cli/v2/pkg/cmd/project/shared/queries"
 	"github.com/cli/cli/v2/pkg/cmdutil"
 	"github.com/cli/cli/v2/pkg/iostreams"
@@ -91,6 +91,85 @@ func TestNewCmdlist(t *testing.T) {
 	}
 }
 
+func TestRunListTTY(t *testing.T) {
+	defer gock.Off()
+	gock.Observe(gock.DumpRequest)
+	// get user ID
+	gock.New("https://api.github.com").
+		Post("/graphql").
+		MatchType("json").
+		JSON(map[string]interface{}{
+			"query": "query UserOrgOwner.*",
+			"variables": map[string]interface{}{
+				"login": "monalisa",
+			},
+		}).
+		Reply(200).
+		JSON(map[string]interface{}{
+			"data": map[string]interface{}{
+				"user": map[string]interface{}{
+					"id": "an ID",
+				},
+			},
+			"errors": []interface{}{
+				map[string]interface{}{
+					"type": "NOT_FOUND",
+					"path": []string{"organization"},
+				},
+			},
+		})
+
+	gock.New("https://api.github.com").
+		Post("/graphql").
+		Reply(200).
+		JSON(map[string]interface{}{
+			"data": map[string]interface{}{
+				"user": map[string]interface{}{
+					"login": "monalisa",
+					"projectsV2": map[string]interface{}{
+						"nodes": []interface{}{
+							map[string]interface{}{
+								"title":            "Project 1",
+								"shortDescription": "Short description 1",
+								"url":              "url1",
+								"closed":           false,
+								"ID":               "id-1",
+								"number":           1,
+							},
+							map[string]interface{}{
+								"title":            "Project 2",
+								"shortDescription": "",
+								"url":              "url2",
+								"closed":           true,
+								"ID":               "id-2",
+								"number":           2,
+							},
+						},
+					},
+				},
+			},
+		})
+
+	client := queries.NewTestClient()
+
+	ios, _, stdout, _ := iostreams.Test()
+	ios.SetStdoutTTY(true)
+	config := listConfig{
+		opts: listOpts{
+			owner: "monalisa",
+		},
+		client: client,
+		io:     ios,
+	}
+
+	err := runList(config)
+	assert.NoError(t, err)
+	assert.Equal(t, heredoc.Doc(`
+		NUMBER  TITLE      STATE  ID
+		1       Project 1  open   id-1
+  `), stdout.String())
+}
+
 func TestRunList(t *testing.T) {
 	defer gock.Off()
 	gock.Observe(gock.DumpRequest)
@@ -154,8 +233,6 @@ func TestRunList(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			owner: "monalisa",
 		},
@@ -226,8 +303,6 @@ func TestRunList_Me(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			owner: "@me",
 		},
@@ -298,8 +373,6 @@ func TestRunListViewer(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp:     tableprinter.New(ios, tableprinter.NoHeader),
 		opts:   listOpts{},
 		client: client,
 		io:     ios,
@@ -377,8 +450,6 @@ func TestRunListOrg(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			owner: "github",
 		},
@@ -432,8 +503,6 @@ func TestRunListEmpty(t *testing.T) {
 
 	ios, _, _, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp:     tableprinter.New(ios, tableprinter.NoHeader),
 		opts:   listOpts{},
 		client: client,
 		io:     ios,
@@ -509,8 +578,6 @@ func TestRunListWithClosed(t *testing.T) {
 
 	ios, _, stdout, _ := iostreams.Test()
 	config := listConfig{
-		//nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests.
-		tp: tableprinter.New(ios, tableprinter.NoHeader),
 		opts: listOpts{
 			owner:  "monalisa",
 			closed: true,
diff --git a/pkg/cmd/repo/deploy-key/list/list_test.go b/pkg/cmd/repo/deploy-key/list/list_test.go
index 4e5a57f11..0f6977db7 100644
--- a/pkg/cmd/repo/deploy-key/list/list_test.go
+++ b/pkg/cmd/repo/deploy-key/list/list_test.go
@@ -49,8 +49,8 @@ func TestListRun(t *testing.T) {
 			},
 			wantStdout: heredoc.Doc(`
 				ID    TITLE          TYPE        KEY                  CREATED AT
-				1234  Mac            read-only   ssh-rsa AAAABbBB123  1d
-				5678  hubot@Windows  read-write  ssh-rsa EEEEEEEK247  1d
+				1234  Mac            read-only   ssh-rsa AAAABbBB123  about 1 day ago
+				5678  hubot@Windows  read-write  ssh-rsa EEEEEEEK247  about 1 day ago
 			`),
 			wantStderr: "",
 		},
diff --git a/pkg/cmd/repo/list/list_test.go b/pkg/cmd/repo/list/list_test.go
index 55e4da83d..1a209e04d 100644
--- a/pkg/cmd/repo/list/list_test.go
+++ b/pkg/cmd/repo/list/list_test.go
@@ -386,9 +386,9 @@ func TestRepoList_tty(t *testing.T) {
 		Showing 3 of 3 repositories in @octocat
 
 		NAME                 DESCRIPTION          INFO          UPDATED
-		octocat/hello-world  My first repository  public        8h
-		octocat/cli          GitHub CLI           public, fork  8h
-		octocat/testing                           private       7d
+		octocat/hello-world  My first repository  public        about 8 hours ago
+		octocat/cli          GitHub CLI           public, fork  about 8 hours ago
+		octocat/testing                           private       about 7 days ago
 	`), stdout.String())
 }
 
diff --git a/pkg/cmd/run/list/list_test.go b/pkg/cmd/run/list/list_test.go
index cf5ba97ad..31680928f 100644
--- a/pkg/cmd/run/list/list_test.go
+++ b/pkg/cmd/run/list/list_test.go
@@ -169,16 +169,16 @@ func TestListRun(t *testing.T) {
 			},
 			wantOut: heredoc.Doc(`
 				STATUS  TITLE        WORKFLOW  BRANCH  EVENT  ID    ELAPSED  AGE
-				X       cool commit  CI        trunk   push   1     4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   2     4m34s    Feb 23, 2021
-				✓       cool commit  CI        trunk   push   3     4m34s    Feb 23, 2021
-				X       cool commit  CI        trunk   push   4     4m34s    Feb 23, 2021
-				X       cool commit  CI        trunk   push   1234  4m34s    Feb 23, 2021
-				-       cool commit  CI        trunk   push   6     4m34s    Feb 23, 2021
-				-       cool commit  CI        trunk   push   7     4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   8     4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   9     4m34s    Feb 23, 2021
-				X       cool commit  CI        trunk   push   10    4m34s    Feb 23, 2021
+				X       cool commit  CI        trunk   push   1     4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   2     4m34s    about 2 years ago
+				✓       cool commit  CI        trunk   push   3     4m34s    about 2 years ago
+				X       cool commit  CI        trunk   push   4     4m34s    about 2 years ago
+				X       cool commit  CI        trunk   push   1234  4m34s    about 2 years ago
+				-       cool commit  CI        trunk   push   6     4m34s    about 2 years ago
+				-       cool commit  CI        trunk   push   7     4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   8     4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   9     4m34s    about 2 years ago
+				X       cool commit  CI        trunk   push   10    4m34s    about 2 years ago
 			`),
 		},
 		{
@@ -203,16 +203,16 @@ func TestListRun(t *testing.T) {
 					}))
 			},
 			wantOut: heredoc.Doc(`
-				completed	timed_out	cool commit	CI	trunk	push	1	4m34s	Feb 23, 2021
-				in_progress		cool commit	CI	trunk	push	2	4m34s	Feb 23, 2021
-				completed	success	cool commit	CI	trunk	push	3	4m34s	Feb 23, 2021
-				completed	cancelled	cool commit	CI	trunk	push	4	4m34s	Feb 23, 2021
-				completed	failure	cool commit	CI	trunk	push	1234	4m34s	Feb 23, 2021
-				completed	neutral	cool commit	CI	trunk	push	6	4m34s	Feb 23, 2021
-				completed	skipped	cool commit	CI	trunk	push	7	4m34s	Feb 23, 2021
-				requested		cool commit	CI	trunk	push	8	4m34s	Feb 23, 2021
-				queued		cool commit	CI	trunk	push	9	4m34s	Feb 23, 2021
-				completed	stale	cool commit	CI	trunk	push	10	4m34s	Feb 23, 2021
+				completed	timed_out	cool commit	CI	trunk	push	1	4m34s	2021-02-23T04:51:00Z
+				in_progress		cool commit	CI	trunk	push	2	4m34s	2021-02-23T04:51:00Z
+				completed	success	cool commit	CI	trunk	push	3	4m34s	2021-02-23T04:51:00Z
+				completed	cancelled	cool commit	CI	trunk	push	4	4m34s	2021-02-23T04:51:00Z
+				completed	failure	cool commit	CI	trunk	push	1234	4m34s	2021-02-23T04:51:00Z
+				completed	neutral	cool commit	CI	trunk	push	6	4m34s	2021-02-23T04:51:00Z
+				completed	skipped	cool commit	CI	trunk	push	7	4m34s	2021-02-23T04:51:00Z
+				requested		cool commit	CI	trunk	push	8	4m34s	2021-02-23T04:51:00Z
+				queued		cool commit	CI	trunk	push	9	4m34s	2021-02-23T04:51:00Z
+				completed	stale	cool commit	CI	trunk	push	10	4m34s	2021-02-23T04:51:00Z
 			`),
 		},
 		{
@@ -249,107 +249,107 @@ func TestListRun(t *testing.T) {
 			},
 			wantOut: heredoc.Doc(`
 				STATUS  TITLE        WORKFLOW  BRANCH  EVENT  ID   ELAPSED  AGE
-				*       cool commit  CI        trunk   push   0    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   1    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   2    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   3    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   4    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   5    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   6    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   7    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   8    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   9    4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   10   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   11   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   12   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   13   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   14   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   15   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   16   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   17   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   18   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   19   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   20   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   21   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   22   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   23   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   24   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   25   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   26   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   27   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   28   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   29   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   30   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   31   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   32   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   33   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   34   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   35   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   36   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   37   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   38   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   39   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   40   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   41   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   42   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   43   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   44   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   45   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   46   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   47   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   48   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   49   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   50   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   51   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   52   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   53   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   54   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   55   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   56   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   57   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   58   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   59   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   60   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   61   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   62   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   63   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   64   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   65   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   66   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   67   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   68   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   69   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   70   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   71   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   72   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   73   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   74   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   75   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   76   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   77   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   78   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   79   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   80   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   81   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   82   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   83   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   84   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   85   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   86   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   87   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   88   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   89   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   90   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   91   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   92   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   93   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   94   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   95   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   96   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   97   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   98   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   99   4m34s    Feb 23, 2021
-				*       cool commit  CI        trunk   push   100  4m34s    Feb 23, 2021
+				*       cool commit  CI        trunk   push   0    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   1    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   2    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   3    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   4    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   5    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   6    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   7    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   8    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   9    4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   10   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   11   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   12   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   13   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   14   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   15   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   16   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   17   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   18   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   19   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   20   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   21   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   22   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   23   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   24   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   25   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   26   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   27   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   28   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   29   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   30   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   31   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   32   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   33   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   34   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   35   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   36   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   37   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   38   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   39   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   40   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   41   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   42   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   43   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   44   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   45   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   46   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   47   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   48   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   49   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   50   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   51   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   52   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   53   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   54   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   55   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   56   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   57   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   58   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   59   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   60   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   61   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   62   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   63   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   64   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   65   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   66   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   67   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   68   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   69   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   70   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   71   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   72   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   73   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   74   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   75   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   76   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   77   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   78   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   79   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   80   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   81   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   82   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   83   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   84   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   85   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   86   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   87   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   88   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   89   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   90   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   91   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   92   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   93   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   94   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   95   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   96   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   97   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   98   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   99   4m34s    about 2 years ago
+				*       cool commit  CI        trunk   push   100  4m34s    about 2 years ago
 			`),
 		},
 		{
@@ -386,9 +386,9 @@ func TestListRun(t *testing.T) {
 			},
 			wantOut: heredoc.Doc(`
 				STATUS  TITLE        WORKFLOW    BRANCH  EVENT  ID    ELAPSED  AGE
-				*       cool commit  a workflow  trunk   push   2     4m34s    Feb 23, 2021
-				✓       cool commit  a workflow  trunk   push   3     4m34s    Feb 23, 2021
-				X       cool commit  a workflow  trunk   push   1234  4m34s    Feb 23, 2021
+				*       cool commit  a workflow  trunk   push   2     4m34s    about 2 years ago
+				✓       cool commit  a workflow  trunk   push   3     4m34s    about 2 years ago
+				X       cool commit  a workflow  trunk   push   1234  4m34s    about 2 years ago
 			`),
 		},
 		{
diff --git a/pkg/cmd/ssh-key/list/list_test.go b/pkg/cmd/ssh-key/list/list_test.go
index 7334beccf..ab28c34c3 100644
--- a/pkg/cmd/ssh-key/list/list_test.go
+++ b/pkg/cmd/ssh-key/list/list_test.go
@@ -61,9 +61,9 @@ func TestListRun(t *testing.T) {
 			isTTY: true,
 			wantStdout: heredoc.Doc(`
 				TITLE          ID    KEY                  TYPE            ADDED
-				Mac            1234  ssh-rsa AAAABbBB123  authentication  1d
-				hubot@Windows  5678  ssh-rsa EEEEEEEK247  authentication  1d
-				Mac Signing    321   ssh-rsa AAAABbBB123  signing         1d
+				Mac            1234  ssh-rsa AAAABbBB123  authentication  about 1 day ago
+				hubot@Windows  5678  ssh-rsa EEEEEEEK247  authentication  about 1 day ago
+				Mac Signing    321   ssh-rsa AAAABbBB123  signing         about 1 day ago
 			`),
 			wantStderr: "",
 		},
@@ -138,7 +138,7 @@ func TestListRun(t *testing.T) {
 			},
 			wantStdout: heredoc.Doc(`
 				TITLE  ID    KEY                  TYPE            ADDED
-				Mac    1234  ssh-rsa AAAABbBB123  authentication  1d
+				Mac    1234  ssh-rsa AAAABbBB123  authentication  about 1 day ago
 			`),
 			wantStderr: heredoc.Doc(`
 			warning:  HTTP 404 (https://api.github.com/user/ssh_signing_keys?per_page=100)
@@ -172,7 +172,7 @@ func TestListRun(t *testing.T) {
 			},
 			wantStdout: heredoc.Doc(`
 				TITLE  ID    KEY                  TYPE     ADDED
-				Mac    1234  ssh-rsa AAAABbBB123  signing  1d
+				Mac    1234  ssh-rsa AAAABbBB123  signing  about 1 day ago
 			`),
 			wantStderr: heredoc.Doc(`
 				warning:  HTTP 404 (https://api.github.com/user/keys?per_page=100)