Time Zone Patch: Solving the Asia/Rangoon Postgres Error in Alpine Linux
When Time Zones Vanish: Solving the Asia/Rangoon Postgres Error in Alpine Linux
If you recently updated your Docker images and were greeted by a PG::InvalidParameterValue: ERROR: time zone "Asia/Rangoon" not recognized, you aren’t alone. It’s a classic case of infrastructure evolving faster than application data.
The Root Cause: Why did it break?
In 1989, Rangoon was renamed Yangon. For decades, the IANA time zone database kept Asia/Rangoon as a “link” (an alias) to Asia/Yangon.
However, modern minimalist distributions like Alpine Linux have started splitting their tzdata package. To keep images small, they moved legacy aliases into a separate, optional package called tzdata-backward. If your database contains the old string and your OS doesn’t have the “backward” links, PostgreSQL—which relies on the OS for time zone definitions—will fail.
The Strategy: Defense in Depth
The best practice for solving this isn’t just a “hotfix.” It’s a layered approach that makes your application standard-compliant and environmentally resilient.
Layer 1: The Infrastructure “Safety Net”
If you need to fix production immediately, you must restore the legacy aliases at the OS level.
For Alpine Linux (Dockerfile)
Add the tzdata-backward package explicitly:
1
2
3
# Add tzdata-backward to support legacy IANA names
RUN apk add --no-cache tzdata tzdata-backward
For Debian/Ubuntu (Dockerfile)
Ensure tzdata is installed in non-interactive mode:
1
2
3
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata
Layer 2: Application-Level Normalization
Relying on the OS for legacy support is technical debt. The “clean” way is to normalize data before it hits your database.
The Normalizer Concern
Create a reusable concern for your models (e.g., User or Account):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# app/models/concerns/time_zone_normalizer.rb
module TimeZoneNormalizer
extend ActiveSupport::Concern
ZONE_MAPPINGS = {
'Asia/Rangoon' => 'Asia/Yangon',
'Asia/Katmandu' => 'Asia/Kathmandu',
'Asia/Calcutta' => 'Asia/Kolkata'
}.freeze
included do
before_validation :normalize_time_zone
end
private
def normalize_time_zone
if respond_to?(:time_zone) && ZONE_MAPPINGS.key?(time_zone)
self.time_zone = ZONE_MAPPINGS[time_zone]
end
end
end
Layer 3: The Data Migration
Clean up your existing records so your database logic (like AT TIME ZONE queries) doesn’t have to deal with deprecated strings.
1
2
3
4
5
6
7
8
9
10
11
12
class NormalizeLegacyTimeZones < ActiveRecord::Migration[7.0]
def up
mappings = { 'Asia/Rangoon' => 'Asia/Yangon', 'Asia/Katmandu' => 'Asia/Kathmandu' }
mappings.each do |old_zone, new_zone|
execute <<-SQL
UPDATE users SET time_zone = '#{new_zone}' WHERE time_zone = '#{old_zone}';
SQL
end
end
end
Layer 4: Automated Verification (The “Sanity Check”)
Prevent regressions by adding a test that ensures all time zones in your database are actually valid in your current environment.
1
2
3
4
5
6
7
8
9
10
11
# spec/models/user_spec.rb
it "contains only time zones recognized by the system" do
distinct_zones = User.pluck(:time_zone).compact.uniq
invalid_zones = distinct_zones.reject do |zone|
ActiveSupport::TimeZone[zone].present? rescue false
end
expect(invalid_zones).to be_empty, "Found unrecognized time zones in DB: #{invalid_zones}"
end
Conclusion: Build for Portability
By moving the mapping logic into your application, you gain two major benefits:
- Portability: Your app will run on any OS, even those with zero legacy time zone support.
- Predictability: You are no longer at the mercy of
tzdatamaintainers.
Don’t just fix the error—fix the architecture. Standardizing on current IANA names is a small step that prevents major headaches during your next infrastructure upgrade.