timecop: Timecop.freeze with Dates is lossy and users should be warned
Realistically, ActiveSupport’s Time.zone being set and Timecop.freeze-ing with Date objects are not compatible. Date is a lossy representation of Time in Ruby anyway since it doesn’t store offset, and the assumptions we make to handle that are surprising.
I think that freezing to Date objects should be deprecated.
Consider the following:
require 'active_support/all'
# The following timezones were chosen because they will always
# exemplify this bug; other timezone combinations may only show
# it during certain parts of the day.
ENV['TZ'] = 'Pacific/Kiritimati' # UTC+14:00
Time.zone = 'Midway Island' # UTC-11:00
Timecop.freeze do # prevent drift
puts Date.today #=> 2013-07-19
# Date.today is generated using ENV['TZ'] or the computer's clock,
# independent of Time.zone.
Timecop.freeze(Date.today) do
# But in freezing, we used Time.zone and *assumed* that the date given was in
# the timezone set for ActiveSupport, then froze time to that day's beginning
# timestamp.
# Here we encounter drift.
puts Date.today #=> 2013-07-20
Timecop.freeze(Date.today) do
# And we can drift again.
puts Date.today #=> 2013-07-21
end
end
end
About this issue
- Original URL
- State: closed
- Created 11 years ago
- Comments: 18 (6 by maintainers)
@brokenladder because of the interplay of
ENV['TZ']andTime.zone, more setup was required to exemplify the bug:My point though, was not just that there is a bug, but that the assumptions inherent in making the conversion from
Dateto aTimeor anActiveSupport::TimeWithZoneobject make supportingDateas an input faulty at best.A date is defined as the period between a midnight and the following midnight, so moment-in-time data (such as the timestamp at the beginning of that day) cannot be extracted without additional information or assumptions about the UTC-offset; it is, therefore a lossy representation of time. Timecop is built to freeze the time, not the date, and using
Dateas an input forces Timecop to make assumptions about the UTC-offset, in this case using the one provided by Time.zone (where Date.today does not).The first example you gave does not go through
Time.parse, since it is supplying aDateobject toTimecop.freeze, and is not representative of the context in which it was quoted. ActiveSupport’sString#to_datemakes no assumptions about timezone becauseDateobjects don’t need this, but it forces Timecop to make an assumption when converting the providedDateobject to aTime.In the second example (which does go through
Time.parse), the same assumptions are being made about utc-offset by bothTime.parseandDate.today, the two will always agree, regardless of timezone settings inTime.zoneorENV['TZ'].