Timezone Testing Strategy
Overview
This project uses Zonebie for timezone randomization testing. Zonebie is always enabled to ensure our code works correctly regardless of server timezone.
Current Configuration
- Always Active: Zonebie randomizes
Time.zonefor each test run (unless overridden) - Override: Set
ZONEBIE_TZenvironment variable to test in a specific timezone - Application Default: Production uses
Asia/Bangkoktimezone
Why Zonebie?
The application serves users across multiple timezones, and the server timezone in different environments might vary:
- Local dev: Might be your system timezone
- CI/CD (GitHub Actions): UTC
- Production: Asia/Bangkok
Zonebie randomizes the timezone for each test run to catch timezone assumptions early, ensuring our code works correctly regardless of where it’s deployed.
Usage
Regular Testing (Random Timezone - Default)
bundle exec rspec
# Zonebie automatically randomizes timezone
# Example output: [Zonebie] Setting timezone: ZONEBIE_TZ="America/Los_Angeles"
Test Specific Timezone
ZONEBIE_TZ="America/New_York" bundle exec rspec
ZONEBIE_TZ="UTC" bundle exec rspec
ZONEBIE_TZ="Europe/London" bundle exec rspec
ZONEBIE_TZ="Asia/Bangkok" bundle exec rspec # Test with production timezone
When Zonebie Matters
✅ Zonebie catches bugs in:
- Date/time calculations and comparisons
- Timezone conversions
- Time-dependent business logic
- Date range validations
- Scheduled job timing
- Timestamp formatting
❌ Zonebie doesn’t affect:
- Tests that don’t use time/dates
- Database queries without time conditions
- String manipulation
- Non-temporal business logic
Fixing Timezone-Dependent Tests
If a test fails due to timezone randomization, it likely has timezone assumptions. Here’s how to fix:
Problem: Date/Time Calculations Across Timezone Boundaries
# ❌ BAD: Uses Date.current which changes with Time.zone
let(:today) { Time.now_in_tz(time_zone).to_date }
let!(:auto_extend_add_on) do
create(:restaurant_add_on, end_date: today) # Factory uses Date.current
end
Solution 1: Wrap Setup in Time.use_zone
# ✅ GOOD: Ensure consistent timezone for all date calculations
around do |example|
Time.use_zone('Asia/Bangkok') do
example.run
end
end
let(:today) { Time.zone.today }
let!(:auto_extend_add_on) do
create(:restaurant_add_on, end_date: today) # Now uses Bangkok timezone
end
Solution 2: Use Explicit Timezone in Factories
# ✅ GOOD: Explicit timezone handling
let(:time_zone) { 'Asia/Bangkok' }
let(:today) { Time.use_zone(time_zone) { Time.zone.today } }
let!(:auto_extend_add_on) do
Time.use_zone(time_zone) do
create(:restaurant_add_on, end_date: today)
end
end
Problem: Hardcoded Time Expectations
# ❌ BAD: Assumes Bangkok timezone
it 'shows correct time' do
travel_to Time.zone.local(2026, 1, 8, 14, 30, 45) do
expect(some_time_display).to eq('21:30:45') # Assumes +07 offset
end
end
Solution: Use Explicit Timezone
# ✅ GOOD: Explicit timezone handling
it 'shows correct time' do
Time.use_zone('Asia/Bangkok') do
travel_to Time.zone.local(2026, 1, 8, 14, 30, 45) do
expect(some_time_display).to match(/\d{2}:\d{2}:\d{2}/) # Format check only
end
end
end
Problem: Relative Time Comparisons Without Timezone Context
# ❌ BAD: Timecop.freeze without Time.use_zone
Timecop.freeze(Time.zone.parse('2026-01-15 18:40:00')) do
# Time.zone could be anything due to Zonebie
end
Solution: Always Wrap with Time.use_zone
# ✅ GOOD: Explicit timezone + time freezing
Time.use_zone('Asia/Bangkok') do
Timecop.freeze(Time.zone.parse('2026-01-15 18:40:00')) do
# Now we know exactly what timezone we're in
end
end
Current Test Status
All tests should pass with Zonebie randomization. If you encounter timezone-related failures:
- Check if the test uses
Time.zone,Date.current, or time calculations - Wrap the test setup and execution in
Time.use_zone('Asia/Bangkok') - Ensure factories are created within the same timezone context
- Use
aroundblocks for consistent timezone handling across all examples
Previously Failing Tests (Now Fixed)
These tests required timezone fixes and serve as examples:
spec/workers/schedule_workers/auto_extend_add_on_sub_worker_spec.rb- RequiredTime.use_zonewrapperspec/my_lib/seat_availability_spec.rb- Requiredaroundblock withTime.use_zonespec/my_lib/agents/update_for_owner_spec.rb- Requiredaroundblock withTime.use_zone
Caveat (June 2026):
seat_availability_specflaked again after the timezone fix, and the random Zonebie timezone in the CI log turned out to be a red herring — the real cause was anAdminSettingvalue leaking throughRails.cachefrom another spec. If a spec is already fully pinned withTime.use_zoneand still flakes, see Flaky Specs: AdminSetting Cache Leak before reaching for more timezone fixes.
Technical Details
Configuration Files
spec/rails_helper.rb: Always loads Zonebie (require 'zonebie/rspec')- No conditional loading - Zonebie is always active
How It Works
- Zonebie loads automatically when RSpec starts
- It randomly selects a timezone and sets
Time.zone(unlessZONEBIE_TZis set) - Each test run gets a different random timezone
- Tests must use
Time.use_zoneto ensure correct timezone handling
Best Practices
✅ Good Timezone Practices
# Always use Time.use_zone for timezone-specific operations
Time.use_zone('Asia/Bangkok') do
Time.zone.now # Guaranteed to be Bangkok time
end
# Use around blocks for consistent test timezone
around do |example|
Time.use_zone('Asia/Bangkok') do
example.run
end
end
# Use Time.zone methods instead of Time.now
Time.zone.now # Respects Time.zone setting
Time.zone.today # Date in current timezone
# Create records within timezone context
Time.use_zone('Asia/Bangkok') do
create(:restaurant_add_on, end_date: Time.zone.today)
end
# Pass timezone explicitly to HhTime methods
HhTime.time_has_passed?(date, time, 'Asia/Bangkok')
❌ Avoid
# Don't use Date.current without timezone context
let(:today) { Date.current } # Changes with Time.zone!
# Don't use Time.now (ignores Time.zone)
Time.now # System time, not timezone-aware
# Don't assume Time.zone is Bangkok
Time.zone.now.hour # Depends on current Time.zone
# Don't create records without timezone context
create(:restaurant_add_on, end_date: Date.current) # Fails with Zonebie
# Don't hardcode timezone offsets
time + 7.hours # What if server timezone changes?
Migration Strategy
For existing tests that fail with Zonebie:
- Identify the timezone assumption - Look for
Date.current,Time.zone.now, time calculations - Add
Time.use_zonewrapper - Wrap test setup and execution - Use
aroundblocks - For consistent timezone across all examples in a context - Test the fix - Run with
ZONEBIE_TZ="UTC"andZONEBIE_TZ="America/New_York"
Gradual Adoption
Zonebie is already enabled by default and working for most tests:
- ✅ Phase 1: Zonebie enabled by default (current state)
- ✅ Phase 2: Fix timezone-dependent tests (ongoing)
- 📋 Phase 3: Document all timezone-sensitive areas
- 📋 Phase 4: Add timezone testing guidelines to PR reviews
Resources
- Zonebie GitHub
- Rails Time Zone Guide
- HhTime module - Project’s timezone utility methods