diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go index 3db5f38ab..5e88b2b62 100644 --- a/pkg/cmd/helpers_test.go +++ b/pkg/cmd/helpers_test.go @@ -45,6 +45,9 @@ func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() } func init() { action.Timestamper = testTimestamper + // Timestamps are rendered in local time; pin the local zone to UTC so + // golden-file output does not depend on the machine's timezone. + time.Local = time.UTC } func runTestCmd(t *testing.T, tests []cmdTestCase) { diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index f53a2f1a6..388ed7f3b 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -194,7 +194,7 @@ func (r releaseHistory) WriteTable(out io.Writer) error { tbl := uitable.New() tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION") for _, item := range r { - tbl.AddRow(item.Revision, item.Updated.Format(time.ANSIC), item.Status, item.Chart, item.AppVersion, item.Description) + tbl.AddRow(item.Revision, item.Updated.Local().Format(time.ANSIC), item.Status, item.Chart, item.AppVersion, item.Description) } return output.EncodeTable(out, tbl) } @@ -218,7 +218,7 @@ func (r releaseHistoryWithRollback) WriteTable(out io.Writer) error { if item.RollbackRevision > 0 { rollback = strconv.Itoa(item.RollbackRevision) } - tbl.AddRow(item.Revision, item.Updated.Format(time.ANSIC), item.Status, item.Chart, item.AppVersion, rollback, item.Description) + tbl.AddRow(item.Revision, item.Updated.Local().Format(time.ANSIC), item.Status, item.Chart, item.AppVersion, rollback, item.Description) } return output.EncodeTable(out, tbl) } diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index 2bace98e0..a8e1b6229 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -17,8 +17,10 @@ limitations under the License. package cmd import ( + "bytes" "encoding/json" "fmt" + "strings" "testing" "time" @@ -30,6 +32,31 @@ import ( release "helm.sh/helm/v4/pkg/release/v1" ) +// Regression test for #31902: table timestamps must be rendered in the +// local timezone (which Go derives from TZ) rather than the zone the +// release happened to be stored with. +func TestHistoryTableUsesLocalTime(t *testing.T) { + originalLocal := time.Local + time.Local = time.FixedZone("UTC+2", 2*60*60) + defer func() { time.Local = originalLocal }() + + h := releaseHistory{{ + Revision: 1, + Updated: time.Date(2026, 3, 4, 14, 38, 52, 0, time.UTC), + Status: "deployed", + Chart: "foo-0.1.0", + AppVersion: "1.0", + Description: "Install complete", + }} + + var buf bytes.Buffer + require.NoError(t, h.WriteTable(&buf)) + + if !strings.Contains(buf.String(), "Wed Mar 4 16:38:52 2026") { + t.Fatalf("expected timestamp rendered in local zone (UTC+2), got:\n%s", buf.String()) + } +} + func TestHistoryCmd(t *testing.T) { mk := func(name string, vers int, status common.Status) *release.Release { return release.Mock(&release.MockReleaseOptions{ diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go index 54becddca..4b3867e33 100644 --- a/pkg/cmd/list.go +++ b/pkg/cmd/list.go @@ -171,9 +171,9 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string, noHead t := "-" if tspb := r.Info.LastDeployed; !tspb.IsZero() { if timeFormat != "" { - t = tspb.Format(timeFormat) + t = tspb.Local().Format(timeFormat) } else { - t = tspb.String() + t = tspb.Local().String() } } element.Updated = t diff --git a/pkg/cmd/status.go b/pkg/cmd/status.go index 6998e341a..cfef344ef 100644 --- a/pkg/cmd/status.go +++ b/pkg/cmd/status.go @@ -147,7 +147,7 @@ func (s statusPrinter) WriteTable(out io.Writer) error { rel := s.getV1Release() _, _ = fmt.Fprintf(out, "NAME: %s\n", rel.Name) if !rel.Info.LastDeployed.IsZero() { - _, _ = fmt.Fprintf(out, "LAST DEPLOYED: %s\n", rel.Info.LastDeployed.Format(time.ANSIC)) + _, _ = fmt.Fprintf(out, "LAST DEPLOYED: %s\n", rel.Info.LastDeployed.Local().Format(time.ANSIC)) } _, _ = fmt.Fprintf(out, "NAMESPACE: %s\n", coloroutput.ColorizeNamespace(rel.Namespace, s.noColor)) _, _ = fmt.Fprintf(out, "STATUS: %s\n", coloroutput.ColorizeStatus(rel.Info.Status, s.noColor)) @@ -197,8 +197,8 @@ func (s statusPrinter) WriteTable(out io.Writer) error { } _, _ = fmt.Fprintf(out, "TEST SUITE: %s\n%s\n%s\n%s\n", h.Name, - "Last Started: "+h.LastRun.StartedAt.Format(time.ANSIC), - "Last Completed: "+h.LastRun.CompletedAt.Format(time.ANSIC), + "Last Started: "+h.LastRun.StartedAt.Local().Format(time.ANSIC), + "Last Completed: "+h.LastRun.CompletedAt.Local().Format(time.ANSIC), "Phase: "+h.LastRun.Phase, ) }