Revert "Remove deprecated support for the pre-Ruby 2.4 behavior of to_time"

This reverts commit b1b2c6d59c3e82ff8eed95f5556a902a4c03abd9.
This commit is contained in:
Matthew Draper 2024-06-02 23:52:59 +09:30
parent 0378b056de
commit 43fcdfa4d0
15 changed files with 331 additions and 123 deletions

@ -111,15 +111,17 @@ def self.cache_format_version=(value)
end
def self.to_time_preserves_timezone
ActiveSupport.deprecator.warn(
"`config.active_support.to_time_preserves_timezone` has been deprecated and will be removed in Rails 7.3."
)
DateAndTime::Compatibility.preserve_timezone
end
def self.to_time_preserves_timezone=(value)
ActiveSupport.deprecator.warn(
"`config.active_support.to_time_preserves_timezone` has been deprecated and will be removed in Rails 7.3."
)
unless value
ActiveSupport.deprecator.warn(
"Support for the pre-Ruby 2.4 behavior of to_time has been deprecated and will be removed in Rails 7.2."
)
end
DateAndTime::Compatibility.preserve_timezone = value
end
def self.utc_to_local_returns_utc_offset_times

@ -4,6 +4,15 @@
module DateAndTime
module Compatibility
# If true, +to_time+ preserves the timezone offset of receiver.
#
# NOTE: With Ruby 2.4+ the default for +to_time+ changed from
# converting to the local system time, to preserving the offset
# of the receiver. For backwards compatibility we're overriding
# this behavior, but new apps will have an initializer that sets
# this to true, because the new behavior is preferred.
mattr_accessor :preserve_timezone, instance_writer: false, default: false
# Change the output of <tt>ActiveSupport::TimeZone.utc_to_local</tt>.
#
# When +true+, it returns local times with a UTC offset, with +false+ local
@ -18,17 +27,5 @@ module Compatibility
# # With `utc_to_local_returns_utc_offset_times = true`, local time is returned with UTC offset:
# zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 -0500
mattr_accessor :utc_to_local_returns_utc_offset_times, instance_writer: false, default: false
def self.preserve_timezone
ActiveSupport.deprecator.warn(
"`DateAndTime::Compatibility.preserve_timezone` has been deprecated and will be removed in Rails 7.3."
)
end
def self.preserve_timezone=(value)
ActiveSupport.deprecator.warn(
"`DateAndTime::Compatibility.preserve_timezone=` has been deprecated and will be removed in Rails 7.3."
)
end
end
end

@ -8,9 +8,11 @@ class DateTime
silence_redefinition_of_method :to_time
# Return an instance of +Time+ with the same UTC offset
# as +self+.
# Either return an instance of +Time+ with the same UTC offset
# as +self+ or an instance of +Time+ representing the same time
# in the local system timezone depending on the setting of
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
def to_time
getlocal(utc_offset)
preserve_timezone ? getlocal(utc_offset) : getlocal
end
end

@ -8,8 +8,9 @@ class Time
silence_redefinition_of_method :to_time
# Returns +self+.
# Either return +self+ or the time in the local system timezone depending
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
def to_time
self
preserve_timezone ? self : getlocal
end
end

@ -479,10 +479,15 @@ def to_datetime
@to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
# Returns an instance of +Time+ with the same UTC offset
# as +self+.
# Returns an instance of +Time+, either with the same UTC offset
# as +self+ or in the local system timezone depending on the setting
# of +ActiveSupport.to_time_preserves_timezone+.
def to_time
@to_time_with_instance_offset ||= getlocal(utc_offset)
if preserve_timezone
@to_time_with_instance_offset ||= getlocal(utc_offset)
else
@to_time_with_system_offset ||= getlocal
end
end
# So that +self+ <tt>acts_like?(:time)</tt>.

@ -24,6 +24,11 @@
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport.deprecator.debug = true
# Default to Ruby 2.4+ to_time behavior but allow running tests with old behavior
ActiveSupport.deprecator.silence do
ActiveSupport.to_time_preserves_timezone = ENV.fetch("PRESERVE_TIMEZONES", "1") == "1"
end
ActiveSupport::Cache.format_version = 7.1
# Disable available locale checks to avoid warnings running the test suite.

@ -16,133 +16,280 @@ def setup
end
def test_time_to_time_preserves_timezone
with_env_tz "US/Eastern" do
source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
time = source.to_time
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_equal source.object_id, time.object_id
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_equal source.object_id, time.object_id
end
end
end
def test_time_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
assert_not_equal source.object_id, time.object_id
end
end
end
def test_time_to_time_frozen_preserves_timezone
with_env_tz "US/Eastern" do
source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
time = source.to_time
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_equal source.object_id, time.object_id
assert_predicate time, :frozen?
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_equal source.object_id, time.object_id
assert_predicate time, :frozen?
end
end
end
def test_time_to_time_frozen_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
assert_not_equal source.object_id, time.object_id
assert_not_predicate time, :frozen?
end
end
end
def test_datetime_to_time_preserves_timezone
with_env_tz "US/Eastern" do
source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
time = source.to_time
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
end
end
end
def test_datetime_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
end
end
end
def test_datetime_to_time_frozen_preserves_timezone
with_env_tz "US/Eastern" do
source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
time = source.to_time
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_not_predicate time, :frozen?
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_not_predicate time, :frozen?
end
end
end
def test_datetime_to_time_frozen_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
assert_not_predicate time, :frozen?
end
end
end
def test_twz_to_time_preserves_timezone
with_env_tz "US/Eastern" do
source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
time = source.to_time
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
time = source.to_time
source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
end
end
end
def test_twz_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
end
end
end
def test_twz_to_time_frozen_preserves_timezone
with_env_tz "US/Eastern" do
source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
time = source.to_time
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_not_predicate time, :frozen?
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_not_predicate time, :frozen?
source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
time = source.to_time
source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_not_predicate time, :frozen?
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_not_predicate time, :frozen?
end
end
end
def test_twz_to_time_frozen_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
assert_not_predicate time, :frozen?
source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
assert_not_predicate time, :frozen?
end
end
end
def test_string_to_time_preserves_timezone
with_env_tz "US/Eastern" do
source = "2016-04-23T15:11:12+01:00"
time = source.to_time
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
source = "2016-04-23T15:11:12+01:00"
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
end
end
end
def test_string_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
source = "2016-04-23T15:11:12+01:00"
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
end
end
end
def test_string_to_time_frozen_preserves_timezone
with_env_tz "US/Eastern" do
source = "2016-04-23T15:11:12+01:00"
time = source.to_time
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
source = "2016-04-23T15:11:12+01:00"
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_not_predicate time, :frozen?
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
assert_not_predicate time, :frozen?
end
end
end
def test_string_to_time_frozen_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
source = "2016-04-23T15:11:12+01:00"
time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
assert_not_predicate time, :frozen?
end
end
end
def test_to_time_preserves_timezone_is_deprecated
assert_deprecated(ActiveSupport.deprecator) do
current_preserve_tz = ActiveSupport.to_time_preserves_timezone
assert_not_deprecated(ActiveSupport.deprecator) do
ActiveSupport.to_time_preserves_timezone
end
assert_deprecated(ActiveSupport.deprecator) do
DateAndTime::Compatibility.preserve_timezone
end
assert_deprecated(ActiveSupport.deprecator) do
assert_not_deprecated(ActiveSupport.deprecator) do
ActiveSupport.to_time_preserves_timezone = true
end
assert_deprecated(ActiveSupport.deprecator) do
DateAndTime::Compatibility.preserve_timezone = false
ActiveSupport.to_time_preserves_timezone = false
end
ensure
ActiveSupport.deprecator.silence do
ActiveSupport.to_time_preserves_timezone = current_preserve_tz
end
end
end

@ -77,8 +77,13 @@ def test_to_time
with_env_tz "US/Eastern" do
assert_instance_of Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
if ActiveSupport.to_time_preserves_timezone
assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
else
assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
end
end
end

@ -610,10 +610,17 @@ def test_timestamp_string_to_time
def test_string_to_time_utc_offset
with_env_tz "US/Eastern" do
assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
assert_equal(-3600, "2005-02-27 22:50 -0100".to_time.utc_offset)
if ActiveSupport.to_time_preserves_timezone
assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
assert_equal(-3600, "2005-02-27 22:50 -0100".to_time.utc_offset)
else
assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset)
end
end
end

@ -517,14 +517,29 @@ def test_time_at
assert_equal time, Time.at(time)
end
def test_to_time
with_env_tz "US/Eastern" do
time = @twz.to_time
def test_to_time_with_preserve_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
time = @twz.to_time
assert_equal Time, time.class
assert_equal time.object_id, @twz.to_time.object_id
assert_equal Time.local(1999, 12, 31, 19), time
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
assert_equal Time, time.class
assert_equal time.object_id, @twz.to_time.object_id
assert_equal Time.local(1999, 12, 31, 19), time
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
end
end
end
def test_to_time_without_preserve_timezone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
time = @twz.to_time
assert_equal Time, time.class
assert_equal time.object_id, @twz.to_time.object_id
assert_equal Time.local(1999, 12, 31, 19), time
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
end
end
end

@ -16,6 +16,20 @@ def with_env_tz(new_tz = "US/Eastern")
old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ")
end
def with_preserve_timezone(value)
old_preserve_tz = ActiveSupport.to_time_preserves_timezone
ActiveSupport.deprecator.silence do
ActiveSupport.to_time_preserves_timezone = value
end
yield
ensure
ActiveSupport.deprecator.silence do
ActiveSupport.to_time_preserves_timezone = old_preserve_tz
end
end
def with_tz_mappings(mappings)
old_mappings = ActiveSupport::TimeZone::MAPPING.dup
ActiveSupport::TimeZone.clear

@ -546,14 +546,8 @@ Please refer to the [Changelog][active-support] for detailed changes.
* Remove deprecated support to passing `Dalli::Client` instances to `MemCacheStore`.
* Remove deprecated support for the pre-Ruby 2.4 behavior of `to_time` returning a `Time` object with local timezone.
### Deprecations
* Deprecate `config.active_support.to_time_preserves_timezone`.
* Deprecate `DateAndTime::Compatibility.preserve_timezone`.
### Notable changes
Active Job

@ -154,6 +154,7 @@ Below are the default values associated with each target version. In cases of co
#### Default Values for Target Version 5.0
- [`ActiveSupport.to_time_preserves_timezone`](#activesupport-to-time-preserves-timezone): `true`
- [`config.action_controller.forgery_protection_origin_check`](#config-action-controller-forgery-protection-origin-check): `true`
- [`config.action_controller.per_form_csrf_tokens`](#config-action-controller-per-form-csrf-tokens): `true`
- [`config.active_record.belongs_to_required_by_default`](#config-active-record-belongs-to-required-by-default): `true`
@ -2695,6 +2696,17 @@ Is set to `false` to disable the ability to silence logging in a block. The defa
Specifies the logger to use within cache store operations.
#### `ActiveSupport.to_time_preserves_timezone`
Specifies whether `to_time` methods preserve the UTC offset of their receivers. If `false`, `to_time` methods will convert to the local system UTC offset instead.
The default value depends on the `config.load_defaults` target version:
| Starting with version | The default value is |
| --------------------- | -------------------- |
| (original) | `false` |
| 5.0 | `true` |
#### `ActiveSupport.utc_to_local_returns_utc_offset_times`
Configures `ActiveSupport::TimeZone.utc_to_local` to return a time with a UTC

@ -114,6 +114,8 @@ def load_defaults(target_version)
action_controller.forgery_protection_origin_check = true
end
ActiveSupport.to_time_preserves_timezone = true
if respond_to?(:active_record)
active_record.belongs_to_required_by_default = true
end

@ -7,7 +7,7 @@ def test_smoke
config = config_for_defaults <<~RUBY
case target_version.to_s
when "5.0"
ActiveSupport.cache_format_version = 7.0
ActiveSupport.to_time_preserves_timezone = true
if respond_to?(:active_record)
active_record.belongs_to_required_by_default = true
@ -27,7 +27,7 @@ def test_smoke
["5.0", "5.1"].each { |k| assert_includes(config, k) }
assert_equal("7.0", config["5.0"]["ActiveSupport.cache_format_version"])
assert_equal("true", config["5.0"]["ActiveSupport.to_time_preserves_timezone"])
assert_equal("true", config["5.0"]["active_record.belongs_to_required_by_default"])
assert_equal("{ hsts: { subdomains: true } }", config["5.0"]["self.ssl_options"])
assert_equal("false", config["5.1"]["assets.unknown_asset_fallback"])