V181: phoenix_kit_users.user_timezone widens from varchar(3) to
varchar(64), so it can hold an IANA identifier.
Why it was three characters
The column was sized for the values it used to hold: "+14", "-12", "0".
A timezone was stored as an integer offset, and three characters is exactly
enough for one.
That storage decision is the root of the reported bugs. An offset cannot
carry a location, so it cannot follow daylight saving: an account set from
Helsinki in January stored "2" and read an hour behind all summer. The
picker had to name cities by their winter offset, which put Johannesburg
(UTC+2 every day) in the same row as Helsinki (UTC+2 only in winter).
Storing Europe/Helsinki instead fixes all of it — and needs 15 characters.
varchar(64) leaves room for the longest identifiers in the database
(America/Argentina/ComodRivadavia is 32) and for whatever IANA adds.
Existing rows are left exactly as they are
Widening a varchar is metadata-only in PostgreSQL: no table rewrite, no
lock beyond a brief ACCESS EXCLUSIVE, and every stored "2" stays "2".
They are deliberately NOT converted to identifiers. "2" is genuinely
ambiguous — Europe/Warsaw in summer, Africa/Johannesburg in any season —
and a migration that guessed would write a location onto an account whose
owner never chose it. PhoenixKit.Utils.TimeZone keeps reading them as fixed
offsets, so nobody's timestamps move, and the profile offers the real zone
the browser reports.
down/1
Narrowing back to varchar(3) would fail on any row holding an identifier,
so down/1 first blanks those, leaving the numeric offsets untouched. That
loses a preference rather than the migration, which is the better of the two
outcomes available when the column can no longer represent the value.