Skip to content

Commit 716e4a7

Browse files
authored
Merge pull request rails#52091 from jasonkim/to-time-use-timezone
Add a config for preserving timezone information when calling `to_time` on TimeWithZone object
2 parents f46d06b + 9dcf17e commit 716e4a7

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
@@ -309,8 +309,8 @@ def test_to_time_preserves_timezone_is_deprecated
309309
ActiveSupport.to_time_preserves_timezone
310310
end
311311

312-
assert_not_deprecated(ActiveSupport.deprecator) do
313-
ActiveSupport.to_time_preserves_timezone = true
312+
assert_deprecated(ActiveSupport.deprecator) do
313+
ActiveSupport.to_time_preserves_timezone = :offset
314314
end
315315

316316
assert_deprecated(ActiveSupport.deprecator) do
@@ -335,4 +335,36 @@ def test_to_time_preserves_timezone_is_deprecated
335335
ActiveSupport.to_time_preserves_timezone = current_preserve_tz
336336
end
337337
end
338+
339+
def test_to_time_preserves_timezone_supports_new_values
340+
current_preserve_tz = ActiveSupport.to_time_preserves_timezone
341+
342+
assert_not_deprecated(ActiveSupport.deprecator) do
343+
ActiveSupport.to_time_preserves_timezone
344+
end
345+
346+
assert_not_deprecated(ActiveSupport.deprecator) do
347+
ActiveSupport.to_time_preserves_timezone = :zone
348+
end
349+
350+
assert_deprecated(ActiveSupport.deprecator) do
351+
ActiveSupport.to_time_preserves_timezone = :offset
352+
end
353+
354+
assert_deprecated(ActiveSupport.deprecator) do
355+
ActiveSupport.to_time_preserves_timezone = true
356+
end
357+
358+
assert_deprecated(ActiveSupport.deprecator) do
359+
ActiveSupport.to_time_preserves_timezone = "offset"
360+
end
361+
362+
assert_deprecated(ActiveSupport.deprecator) do
363+
ActiveSupport.to_time_preserves_timezone = :foo
364+
end
365+
ensure
366+
ActiveSupport.deprecator.silence do
367+
ActiveSupport.to_time_preserves_timezone = current_preserve_tz
368+
end
369+
end
338370
end

activesupport/test/core_ext/time_with_zone_test.rb

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,34 @@ def test_time_at
527527
assert_equal time, Time.at(time)
528528
end
529529

530-
def test_to_time_with_preserve_timezone
530+
def test_to_time_with_preserve_timezone_using_zone
531+
with_preserve_timezone(:zone) do
532+
time = @twz.to_time
533+
local_time = with_env_tz("US/Eastern") { Time.local(1999, 12, 31, 19) }
534+
535+
assert_equal Time, time.class
536+
assert_equal time.object_id, @twz.to_time.object_id
537+
assert_equal local_time, time
538+
assert_equal local_time.utc_offset, time.utc_offset
539+
assert_equal @time_zone, time.zone
540+
end
541+
end
542+
543+
def test_to_time_with_preserve_timezone_using_offset
544+
with_preserve_timezone(:offset) do
545+
with_env_tz "US/Eastern" do
546+
time = @twz.to_time
547+
548+
assert_equal Time, time.class
549+
assert_equal time.object_id, @twz.to_time.object_id
550+
assert_equal Time.local(1999, 12, 31, 19), time
551+
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
552+
assert_nil time.zone
553+
end
554+
end
555+
end
556+
557+
def test_to_time_with_preserve_timezone_using_true
531558
with_preserve_timezone(true) do
532559
with_env_tz "US/Eastern" do
533560
time = @twz.to_time
@@ -536,6 +563,7 @@ def test_to_time_with_preserve_timezone
536563
assert_equal time.object_id, @twz.to_time.object_id
537564
assert_equal Time.local(1999, 12, 31, 19), time
538565
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
566+
assert_nil time.zone
539567
end
540568
end
541569
end
@@ -549,6 +577,7 @@ def test_to_time_without_preserve_timezone
549577
assert_equal time.object_id, @twz.to_time.object_id
550578
assert_equal Time.local(1999, 12, 31, 19), time
551579
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
580+
assert_equal Time.local(1999, 12, 31, 19).zone, time.zone
552581
end
553582
end
554583
end
@@ -562,6 +591,7 @@ def test_to_time_without_preserve_timezone_configured
562591
assert_equal time.object_id, @twz.to_time.object_id
563592
assert_equal Time.local(1999, 12, 31, 19), time
564593
assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
594+
assert_equal Time.local(1999, 12, 31, 19).zone, time.zone
565595

566596
assert_equal false, ActiveSupport.to_time_preserves_timezone
567597
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)