Ruby timezones and testing

I had a test in jch.app which fetched data by date, and kept being off by one day. The minimal failing test case became:

Timecop.freeze("2024/03/15") do
  assert_equal Date.current, Date.today
end

Turns out, this was documented behavior that goes way back to 2011. From the timecop README:

Sometimes Rails Date/Time methods don't play nicely with Ruby Date/Time methods. Be careful mixing Ruby Date.today with Rails Date.tomorrow / Date.yesterday as things might break.

Here are the differences between ruby's builtin Time and Date methods, compared with using ActiveSupport's TimeWithZone and Date extensions.

irb> ENV['TZ']
=> nil
irb> Time.now
=> 2024-08-15 13:48:14.639956 -0700  # no timezone set picks up system time
irb> ENV['TZ'] = 'US/Eastern'
=> "US/Eastern"
irb> Time.now
=> 2024-08-15 16:48:46.905847 -0400  # ENV['TZ'] set overrides system time

irb> require 'date'
irb> Date.today
=> #<Date: 2024-08-15 ((2460538j,0s,0n),+0s,2299161j)>

irb> Date.today.to_time
=> 2024-08-15 00:00:00 -0700

irb> require 'active_support'
irb> require 'active_support/core_ext/date/calculations'
irb> Time.zone = 'Asia/Tokyo'
=> "Asia/Tokyo"
irb> Time.now
=> 2024-08-15 13:56:27.470616 -0700  # system time!
irb> Time.zone.now
=> Fri, 16 Aug 2024 05:56:30.666951000 JST +09:00  # the following day b/c of +9 compared to my -7 system offset
irb> Date.today  # system time!
=> Thu, 15 Aug 2024
irb> Date.current  # Time.zone date
=> Fri, 16 Aug 2024

Before I figured this out, my fix for the test was to stub

Date.stubs(:current).returns(Date.parse("2024-01-01"))

Other methods have dates parameterized for testing. Since I'm not using any of timecop's travel functionality, I decided to remove the dependency.

Edit: 2024-08-28 Rails has ActiveSupport::Testing::TimeHelpers to do this without a dependency:

travel 1.day
travel_do Date.parse('2024-08-28') { ... }
freeze_time {... }
freeze_time
# ...
unfreeze_time

Resources