fix(day): render unix timestamps in the time zone the caller asked for (#1659)

Fixes #

## Proposed Changes

- `pkg/day.Format` now applies the `tz` argument it already loaded with
`time.Time.In`; before, `time.LoadLocation(tz)` was called and its
result dropped, so every rendered date was the server's local zone.
- An empty or unresolvable zone keeps the previous local-time behaviour,
so nothing shifts when the site time zone is missing.
- Adds a table-driven `TestFormatTimezone` (UTC / Tokyo / New York /
Kolkata / Los Angeles) plus a fallback test.

## Additional Info

Reproducer: `Format(1700000000, "YYYY-MM-DD HH:mm", "Asia/Tokyo")`
returned `2023-11-14 22:13`
instead of `2023-11-15 07:13`. This is the helper behind the
`translatorTimeFormat` email/UI
templates and behind the "suspended until" text that passes `"UTC"`
explicitly
(`internal/schema/user_schema.go:202`). No new dependency; `go mod tidy`
and
`golangci-lint v2.6.2` (`make lint`) are clean.
diff --git a/pkg/day/day.go b/pkg/day/day.go
index 797da68..6c1cb49 100644
--- a/pkg/day/day.go
+++ b/pkg/day/day.go
@@ -59,8 +59,13 @@
 		from = suffix
 	}
 
-	_, _ = time.LoadLocation(tz)
-	formatted = time.Unix(unix, 0).Format(toFormat.String())
+	loc, err := time.LoadLocation(tz)
+	if err != nil || tz == "" {
+		// An empty or unknown zone must not shift the instant that is being
+		// rendered: fall back to the server local zone.
+		loc = time.Local
+	}
+	formatted = time.Unix(unix, 0).In(loc).Format(toFormat.String())
 	return
 }
 
diff --git a/pkg/day/day_test.go b/pkg/day/day_test.go
index 1ff7576..3dd0f37 100644
--- a/pkg/day/day_test.go
+++ b/pkg/day/day_test.go
@@ -35,7 +35,7 @@
 	tz := "UTC"
 
 	actual := Format(sec, "MMM D, YYYY [a las] HH:mm", tz)
-	expected := time.Unix(sec, 0).Format("Jan 2, 2006") + " a las " + time.Unix(sec, 0).Format("15:04")
+	expected := time.Unix(sec, 0).UTC().Format("Jan 2, 2006") + " a las " + time.Unix(sec, 0).UTC().Format("15:04")
 	assert.Equal(t, expected, actual)
 }
 
@@ -102,3 +102,44 @@
 		}
 	}
 }
+
+// Format takes an IANA time zone name and must render the instant in that zone.
+// Before the fix the loaded location was discarded, so every caller (the UI,
+// the email templates and the "account suspended until" notice, which passes
+// "UTC" explicitly) got the server's local zone instead.
+func TestFormatTimezone(t *testing.T) {
+	// 2023-11-14 22:13:20 UTC
+	const sec = int64(1700000000)
+
+	tests := []struct {
+		name     string
+		format   string
+		tz       string
+		expected string
+	}{
+		{"utc", "YYYY-MM-DD HH:mm", "UTC", "2023-11-14 22:13"},
+		{"tokyo_crosses_midnight", "YYYY-MM-DD HH:mm", "Asia/Tokyo", "2023-11-15 07:13"},
+		{"new_york", "YYYY-MM-DD HH:mm", "America/New_York", "2023-11-14 17:13"},
+		{"kolkata_half_hour_offset", "YYYY-MM-DD HH:mm", "Asia/Kolkata", "2023-11-15 03:43"},
+		{"los_angeles_long_date", "MMMM D, YYYY HH:mm", "America/Los_Angeles", "November 14, 2023 14:13"},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, tt.expected, Format(sec, tt.format, tt.tz))
+		})
+	}
+}
+
+// A zone that cannot be resolved must fall back to the server's local zone
+// instead of failing or silently shifting the instant.
+func TestFormatUnknownTimezoneFallsBackToLocal(t *testing.T) {
+	const sec = int64(1700000000)
+	expected := time.Unix(sec, 0).In(time.Local).Format("2006-01-02 15:04")
+
+	for _, tz := range []string{"", "Not/AZone", "UTC+99"} {
+		t.Run("tz="+tz, func(t *testing.T) {
+			assert.Equal(t, expected, Format(sec, "YYYY-MM-DD HH:mm", tz))
+		})
+	}
+}