Skip to content

Commit 9dcf17e

Browse files
jasonkimjhawthorn
andcommitted
Add a config for preserving timezone information
when calling `to_time` on TimeWithZone object Co-authored-by: jhawthorn <[email protected]>
1 parent f5355a2 commit 9dcf17e

File tree

10 files changed

+121
-24
lines changed

10 files changed

+121
-24
lines changed

activesupport/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@
88

99
*Richard Böhme*, *Jean Boussier*
1010

11+
* Add a new configuration value `:zone` for `ActiveSupport.to_time_preserves_timezone` and rename the previous `true` value to `:offset`. The new default value is `:zone`.
12+
13+
*Jason Kim*, *John Hawthorn*
14+
1115
Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activesupport/CHANGELOG.md) for previous changes.

activesupport/lib/active_support.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,15 @@ def self.to_time_preserves_timezone
115115
end
116116

117117
def self.to_time_preserves_timezone=(value)
118-
unless value
118+
if !value
119119
ActiveSupport.deprecator.warn(
120-
"Support for the pre-Ruby 2.4 behavior of to_time has been deprecated and will be removed in Rails 8.0."
120+
"`to_time` will always preserve the receiver timezone rather than system local time in Rails 8.0." \
121+
"To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`."
122+
)
123+
elsif value != :zone
124+
ActiveSupport.deprecator.warn(
125+
"`to_time` will always preserve the full timezone rather than offset of the receiver in Rails 8.0. " \
126+
"To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`."
121127
)
122128
end
123129

activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ def self.preserve_timezone # :nodoc:
2626
# Only warn once, the first time the value is used (which should
2727
# be the first time #to_time is called).
2828
ActiveSupport.deprecator.warn(
29-
"to_time will always preserve the timezone offset of the receiver in Rails 8.0. " \
30-
"To opt in to the new behavior, set `ActiveSupport.to_time_preserves_timezone = true`."
29+
"`to_time` will always preserve the receiver timezone rather than system local time in Rails 8.0." \
30+
"To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`."
3131
)
3232

3333
@@preserve_timezone = false

activesupport/lib/active_support/railtie.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ class Railtie < Rails::Railtie # :nodoc:
9696
config.eager_load_namespaces << TZInfo
9797
end
9898

99+
initializer "active_support.to_time_preserves_timezone" do |app|
100+
ActiveSupport.to_time_preserves_timezone = app.config.active_support.to_time_preserves_timezone
101+
end
102+
99103
# Sets the default week start
100104
# If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
101105
initializer "active_support.initialize_beginning_of_week" do |app|

activesupport/lib/active_support/time_with_zone.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -479,11 +479,13 @@ def to_datetime
479479
@to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
480480
end
481481

482-
# Returns an instance of +Time+, either with the same UTC offset
483-
# as +self+ or in the local system timezone depending on the setting
484-
# of +ActiveSupport.to_time_preserves_timezone+.
482+
# Returns an instance of +Time+, either with the same timezone as +self+,
483+
# with the same UTC offset as +self+ or in the local system timezone
484+
# depending on the setting of +ActiveSupport.to_time_preserves_timezone+.
485485
def to_time
486-
if preserve_timezone
486+
if preserve_timezone == :zone
487+
@to_time_with_timezone ||= getlocal(time_zone)
488+
elsif preserve_timezone
487489
@to_time_with_instance_offset ||= getlocal(utc_offset)
488490
else
489491
@to_time_with_system_offset ||= getlocal

activesupport/test/core_ext/date_and_time_compatibility_test.rb

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ def test_to_time_preserves_timezone_is_deprecated
280280
ActiveSupport.to_time_preserves_timezone
281281
end
282282

283-
assert_not_deprecated(ActiveSupport.deprecator) do
284-
ActiveSupport.to_time_preserves_timezone = true
283+
assert_deprecated(ActiveSupport.deprecator) do
284+
ActiveSupport.to_time_preserves_timezone = :offset
285285
end
286286

287287
assert_deprecated(ActiveSupport.deprecator) do
@@ -306,4 +306,36 @@ def test_to_time_preserves_timezone_is_deprecated
306306
ActiveSupport.to_time_preserves_timezone = current_preserve_tz
307307
end
308308
end
309+
310+
def test_to_time_preserves_timezone_supports_new_values
311+
current_preserve_tz = ActiveSupport.to_time_preserves_timezone
312+
313+
assert_not_deprecated(ActiveSupport.deprecator) do
314+
ActiveSupport.to_time_preserves_timezone
315+
end
316+
317+
assert_not_deprecated(ActiveSupport.deprecator) do
318+
ActiveSupport.to_time_preserves_timezone = :zone
319+
end
320+
321+
assert_deprecated(ActiveSupport.deprecator) do
322+
ActiveSupport.to_time_preserves_timezone = :offset
323+
end
324+
325+
assert_deprecated(ActiveSupport.deprecator) do
326+
ActiveSupport.to_time_preserves_timezone = true
327+
end
328+
329+
assert_deprecated(ActiveSupport.deprecator) do
330+
ActiveSupport.to_time_preserves_timezone = "offset"
331+
end
332+
333+
assert_deprecated(ActiveSupport.deprecator) do
334+
ActiveSupport.to_time_preserves_timezone = :foo
335+
end
336+
ensure
337+
ActiveSupport.deprecator.silence do
338+
ActiveSupport.to_time_preserves_timezone = current_preserve_tz
339+
end
340+
end
309341
end

activesupport/test/core_ext/time_with_zone_test.rb

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,34 @@ def test_time_at
517517
assert_equal time, Time.at(time)
518518
end
519519

520-
def test_to_time_with_preserve_timezone
520+
def test_to_time_with_preserve_timezone_using_zone
521+
with_preserve_timezone(:zone) do
522+
time = @twz.to_time
523+
local_time = with_env_tz("US/Eastern") { Time.local(1999, 12, 31, 19) }
524+
525+
assert_equal Time, time.class
526+
assert_equal time.object_id, @twz.to_time.object_id
527+
assert_equal local_time, time
528+
assert_equal local_time.utc_offset, time.utc_offset
529+
assert_equal @time_zone, time.zone
530+
end
531+
end
532+
533+
def test_to_time_with_preserve_timezone_using_offset
534+
with_preserve_timezone(:offset) do
535+
with_env_tz "US/Eastern" do
536+
time = @twz.to_time
537+
538+
assert_equal Time, time.class
539+
assert_equal time.object_id, @twz.to_time.object_id
540+
assert_equal Time.local(1999, 12, 31, 19), time
541+
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
542+
assert_nil time.zone
543+
end
544+
end
545+
end
546+
547+
def test_to_time_with_preserve_timezone_using_true
521548
with_preserve_timezone(true) do
522549
with_env_tz "US/Eastern" do
523550
time = @twz.to_time
@@ -526,6 +553,7 @@ def test_to_time_with_preserve_timezone
526553
assert_equal time.object_id, @twz.to_time.object_id
527554
assert_equal Time.local(1999, 12, 31, 19), time
528555
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
556+
assert_nil time.zone
529557
end
530558
end
531559
end
@@ -539,6 +567,7 @@ def test_to_time_without_preserve_timezone
539567
assert_equal time.object_id, @twz.to_time.object_id
540568
assert_equal Time.local(1999, 12, 31, 19), time
541569
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
570+
assert_equal Time.local(1999, 12, 31, 19).zone, time.zone
542571
end
543572
end
544573
end
@@ -552,6 +581,7 @@ def test_to_time_without_preserve_timezone_configured
552581
assert_equal time.object_id, @twz.to_time.object_id
553582
assert_equal Time.local(1999, 12, 31, 19), time
554583
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
584+
assert_equal Time.local(1999, 12, 31, 19).zone, time.zone
555585

556586
assert_equal false, ActiveSupport.to_time_preserves_timezone
557587
end

guides/source/configuring.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ NOTE: If you need to apply configuration directly to a class, use a [lazy load h
5858

5959
Below are the default values associated with each target version. In cases of conflicting values, newer versions take precedence over older versions.
6060

61+
#### Default Values for Target Version 8.0
62+
63+
- [`config.active_support.to_time_preserves_timezone`](#config-active-support-to-time-preserves-timezone): `:zone`
64+
6165
#### Default Values for Target Version 7.2
6266

6367
- [`config.active_job.enqueue_after_transaction_commit`](#config-active-job-enqueue-after-transaction-commit): `:default`
@@ -154,10 +158,10 @@ Below are the default values associated with each target version. In cases of co
154158

155159
#### Default Values for Target Version 5.0
156160

157-
- [`ActiveSupport.to_time_preserves_timezone`](#activesupport-to-time-preserves-timezone): `true`
158161
- [`config.action_controller.forgery_protection_origin_check`](#config-action-controller-forgery-protection-origin-check): `true`
159162
- [`config.action_controller.per_form_csrf_tokens`](#config-action-controller-per-form-csrf-tokens): `true`
160163
- [`config.active_record.belongs_to_required_by_default`](#config-active-record-belongs-to-required-by-default): `true`
164+
- [`config.active_support.to_time_preserves_timezone`](#config-active-support-to-time-preserves-timezone): `:offset`
161165
- [`config.ssl_options`](#config-ssl-options): `{ hsts: { subdomains: true } }`
162166

163167
### Rails General Configuration
@@ -2694,24 +2698,25 @@ The default value depends on the `config.load_defaults` target version:
26942698
| (original) | `false` |
26952699
| 7.0 | `true` |
26962700

2697-
#### `ActiveSupport::Logger.silencer`
2698-
2699-
Is set to `false` to disable the ability to silence logging in a block. The default is `true`.
2700-
2701-
#### `ActiveSupport::Cache::Store.logger`
2702-
2703-
Specifies the logger to use within cache store operations.
2704-
2705-
#### `ActiveSupport.to_time_preserves_timezone`
2701+
#### `config.active_support.to_time_preserves_timezone`
27062702

2707-
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.
2703+
Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. If set to `:zone`, `to_time` methods will use the timezone of their receivers. If set to `:offset`, `to_time` methods will use the UTC offset. If `false`, `to_time` methods will convert to the local system UTC offset instead.
27082704

27092705
The default value depends on the `config.load_defaults` target version:
27102706

27112707
| Starting with version | The default value is |
27122708
| --------------------- | -------------------- |
27132709
| (original) | `false` |
2714-
| 5.0 | `true` |
2710+
| 5.0 | `:offset` |
2711+
| 8.0 | `:zone` |
2712+
2713+
#### `ActiveSupport::Logger.silencer`
2714+
2715+
Is set to `false` to disable the ability to silence logging in a block. The default is `true`.
2716+
2717+
#### `ActiveSupport::Cache::Store.logger`
2718+
2719+
Specifies the logger to use within cache store operations.
27152720

27162721
#### `ActiveSupport.utc_to_local_returns_utc_offset_times`
27172722

railties/lib/rails/application/configuration.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ def load_defaults(target_version)
114114
action_controller.forgery_protection_origin_check = true
115115
end
116116

117-
ActiveSupport.to_time_preserves_timezone = true
117+
if respond_to?(:active_support)
118+
active_support.to_time_preserves_timezone = :offset
119+
end
118120

119121
if respond_to?(:active_record)
120122
active_record.belongs_to_required_by_default = true
@@ -337,6 +339,10 @@ def load_defaults(target_version)
337339
end
338340
when "8.0"
339341
load_defaults "7.2"
342+
343+
if respond_to?(:active_support)
344+
active_support.to_time_preserves_timezone = :zone
345+
end
340346
else
341347
raise "Unknown version #{target_version.to_s.inspect}"
342348
end

railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_8_0.rb.tt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@
88
#
99
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
1010
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
11+
12+
###
13+
# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone.
14+
# If set to `:zone`, `to_time` methods will use the timezone of their receivers.
15+
# If set to `:offset`, `to_time` methods will use the UTC offset.
16+
# If `false`, `to_time` methods will convert to the local system UTC offset instead.
17+
#++
18+
# Rails.application.config.active_support.to_time_preserves_timezone = :zone

0 commit comments

Comments
 (0)