aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb8
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb4
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb4
-rw-r--r--actionpack/lib/action_controller/metal/live.rb4
-rw-r--r--activejob/lib/active_job/enqueuing.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb4
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/test/cases/locking_test.rb6
-rw-r--r--activesupport/CHANGELOG.md50
-rw-r--r--activesupport/lib/active_support.rb9
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb23
-rw-r--r--activesupport/test/core_ext/date_and_time_compatibility_test.rb127
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb26
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb14
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb5
-rw-r--r--activesupport/test/time_zone_test_helpers.rb8
-rw-r--r--railties/CHANGELOG.md8
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/to_time_preserves_timezone.rb10
-rw-r--r--railties/test/generators/app_generator_test.rb28
34 files changed, 390 insertions, 42 deletions
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index e31d65aac2..ea8e91ce24 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -2,17 +2,17 @@ require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
module ActionController
- # This module provides a method which will redirect browser to use HTTPS
+ # This module provides a method which will redirect the browser to use HTTPS
# protocol. This will ensure that user's sensitive information will be
- # transferred safely over the internet. You _should_ always force browser
+ # transferred safely over the internet. You _should_ always force the browser
# to use HTTPS when you're transferring sensitive information such as
# user authentication, account information, or credit card information.
#
# Note that if you are really concerned about your application security,
# you might consider using +config.force_ssl+ in your config file instead.
# That will ensure all the data transferred via HTTPS protocol and prevent
- # user from getting session hijacked when accessing the site under unsecured
- # HTTP protocol.
+ # the user from getting their session hijacked when accessing the site over
+ # unsecured HTTP protocol.
module ForceSSL
extend ActiveSupport::Concern
include AbstractController::Callbacks
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 53527c08b6..4639348509 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -310,9 +310,9 @@ module ActionController
end
# Might want a shorter timeout depending on whether the request
- # is a PATCH, PUT, or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if the client is a browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
- # allow a user to use new nonce without prompting user again for their
+ # allow a user to use new nonce without prompting the user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
return false if value.nil?
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 885ea3fefd..624a6d5b76 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -75,8 +75,8 @@ module ActionController
ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
end
- # A hook which allows you to clean up any time taken into account in
- # views wrongly, like database querying time.
+ # A hook which allows you to clean up any time, wrongly taken into account in
+ # views, like database querying time.
#
# def cleanup_view_runtime
# super - time_taken_in_something_expensive
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index fc20e7a421..6055fde4f7 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -3,7 +3,7 @@ require 'delegate'
require 'active_support/json'
module ActionController
- # Mix this module in to your controller, and all actions in that controller
+ # Mix this module into your controller, and all actions in that controller
# will be able to stream data to the client as it's written.
#
# class MyController < ActionController::Base
@@ -20,7 +20,7 @@ module ActionController
# end
# end
#
- # There are a few caveats with this use. You *cannot* write headers after the
+ # There are a few caveats with this module. You *cannot* write headers after the
# response has been committed (Response#committed? will return truthy).
# Calling +write+ or +close+ on the response stream will cause the response
# object to be committed. Make sure all headers are set before calling write
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index 22154457fd..9dc3c0fa57 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -36,7 +36,7 @@ module ActiveJob
#
# ==== Examples
#
- # class SiteScrapperJob < ActiveJob::Base
+ # class SiteScraperJob < ActiveJob::Base
# rescue_from(ErrorLoadingSite) do
# retry_job queue: :low_priority
# end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index ddfd97b537..d45c2c0b0d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -66,7 +66,7 @@ module ActiveRecord
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
def select_rows(sql, name = nil, binds = [])
- raise NotImplementedError
+ exec_query(sql, name, binds).rows
end
# Executes the SQL statement in the context of this connection and returns
@@ -221,9 +221,7 @@ module ActiveRecord
# * You are creating a nested (savepoint) transaction
#
# The mysql2 and postgresql adapters support setting the transaction
- # isolation level. However, support is disabled for MySQL versions below 5,
- # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
- # which means the isolation level gets persisted outside the transaction.
+ # isolation level.
def transaction(requires_new: nil, isolation: nil, joinable: true)
if !requires_new && current_transaction.joinable?
if isolation
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 105d9cde92..d8cc60e924 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -989,7 +989,7 @@ module ActiveRecord
insert_versions_sql(versions)
end
- def insert_versions_sql(versions)
+ def insert_versions_sql(versions) # :nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
if supports_multi_insert?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 2e18226a10..2c476ec285 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -298,7 +298,7 @@ module ActiveRecord
false
end
- # Does this adapter support multi-value insert
+ # Does this adapter support multi-value insert?
def supports_multi_insert?
true
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
index eeccb09bdf..838cb63281 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
@@ -1,3 +1,5 @@
+require 'ipaddr'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 470e03b0ed..f52f27c84d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -16,8 +16,6 @@ require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
require "active_record/connection_adapters/statement_pool"
-require 'ipaddr'
-
module ActiveRecord
module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index bf73d647c0..d64ac434e8 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -229,10 +229,6 @@ module ActiveRecord
log(sql, name) { @connection.execute(sql) }
end
- def select_rows(sql, name = nil, binds = [])
- exec_query(sql, name, binds).rows
- end
-
def begin_db_transaction #:nodoc:
log('begin transaction',nil) { @connection.transaction }
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 2336d23a1c..1040327a5d 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -190,6 +190,10 @@ module ActiveRecord
super.to_i
end
+ def serialize(value)
+ super.to_i
+ end
+
def init_with(coder)
__setobj__(coder['subtype'])
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 6c59d7337a..9fc0041892 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -169,6 +169,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 1, p1.lock_version
end
+ def test_lock_new_when_explicitly_passing_nil
+ p1 = Person.new(:first_name => 'anika', lock_version: nil)
+ p1.save!
+ assert_equal 0, p1.lock_version
+ end
+
def test_touch_existing_lock
p1 = Person.find(1)
assert_equal 0, p1.lock_version
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index f8423c3ef8..2be567c2f7 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,53 @@
+* Make `getlocal` and `getutc` always return instances of `Time` for
+ `ActiveSupport::TimeWithZone` and `DateTime`. This eliminates a possible
+ stack level too deep error in `to_time` where `ActiveSupport::TimeWithZone`
+ was wrapping a `DateTime` instance. As a consequence of this the internal
+ time value in `ActiveSupport::TimeWithZone` is now always an instance of
+ `Time` in the UTC timezone, whether that's as the UTC time directly or
+ a representation of the local time in the timezone. There should be no
+ consequences of this internal change and if there are it's a bug due to
+ leaky abstractions.
+
+ *Andrew White*
+
+* Add `DateTime#subsec` to return the fraction of a second as a `Rational`.
+
+ *Andrew White*
+
+* Add additional aliases for `DateTime#utc` to mirror the ones on
+ `ActiveSupport::TimeWithZone` and `Time`.
+
+ *Andrew White*
+
+* Add `DateTime#localtime` to return an instance of `Time` in the system's
+ local timezone. Also aliased to `getlocal`.
+
+ *Andrew White*, *Yuichiro Kaneko*
+
+* Add `Time#sec_fraction` to return the fraction of a second as a `Rational`.
+
+ *Andrew White*
+
+* Add `ActiveSupport.to_time_preserves_timezone` config option to control
+ how `to_time` handles timezones. In Ruby 2.4+ the behavior will change
+ from converting to the local system timezone, to preserving the timezone
+ of the receiver. This config option defaults to false so that apps made
+ with earlier versions of Rails are not affected when upgrading, e.g:
+
+ >> ENV['TZ'] = 'US/Eastern'
+
+ >> "2016-04-23T10:23:12.000Z".to_time
+ => "2016-04-23T06:23:12.000-04:00"
+
+ >> ActiveSupport.to_time_preserves_timezone = true
+
+ >> "2016-04-23T10:23:12.000Z".to_time
+ => "2016-04-23T10:23:12.000Z"
+
+ Fixes #24617.
+
+ *Andrew White*
+
* `ActiveSupport::TimeZone.country_zones(country_code)` looks up the
country's time zones by its two-letter ISO3166 country code, e.g.
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 72777baecd..11569add37 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -26,6 +26,7 @@ require "active_support/dependencies/autoload"
require "active_support/version"
require "active_support/logger"
require "active_support/lazy_load_hooks"
+require "active_support/core_ext/date_and_time/compatibility"
module ActiveSupport
extend ActiveSupport::Autoload
@@ -85,6 +86,14 @@ module ActiveSupport
def self.halt_callback_chains_on_return_false=(value)
Callbacks.halt_and_display_warning_on_return_false = value
end
+
+ def self.to_time_preserves_timezone
+ DateAndTime::Compatibility.preserve_timezone
+ end
+
+ def self.to_time_preserves_timezone=(value)
+ DateAndTime::Compatibility.preserve_timezone = value
+ end
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index 54244317e4..e4af984cf6 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -69,6 +69,7 @@ module ActiveSupport
if eligible_waiters?(compatible)
yield_shares(compatible: compatible, block_share: true) do
+ @cv.broadcast
@cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) }
end
end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
new file mode 100644
index 0000000000..59aa3ccac2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -0,0 +1,16 @@
+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) { false }
+
+ def to_time
+ preserve_timezone ? getlocal(utc_offset) : getlocal
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index 5450533935..86177488c0 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/blank'
require 'active_support/core_ext/date_time/calculations'
+require 'active_support/core_ext/date_time/compatibility'
require 'active_support/core_ext/date_time/conversions'
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index ac46f5ffe8..9e89a33491 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -28,6 +28,13 @@ class DateTime
end_of_day.to_i - to_i
end
+ # Returns the fraction of a second as a +Rational+
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2)
+ def subsec
+ sec_fraction
+ end
+
# Returns a new DateTime where one or more of the elements have been changed
# according to the +options+ parameter. The time options (<tt>:hour</tt>,
# <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is
@@ -143,14 +150,32 @@ class DateTime
end
alias :at_end_of_minute :end_of_minute
- # Adjusts DateTime to UTC by adding its offset value; offset is set to 0.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
+ def localtime(utc_offset = nil)
+ utc = new_offset(0)
+
+ Time.utc(
+ utc.year, utc.month, utc.day,
+ utc.hour, utc.min, utc.sec + utc.sec_fraction
+ ).getlocal(utc_offset)
+ end
+ alias_method :getlocal, :localtime
+
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
#
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
- # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
+ # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC
def utc
- new_offset(0)
+ utc = new_offset(0)
+
+ Time.utc(
+ utc.year, utc.month, utc.day,
+ utc.hour, utc.min, utc.sec + utc.sec_fraction
+ )
end
+ alias_method :getgm, :utc
alias_method :getutc, :utc
+ alias_method :gmtime, :utc
# Returns +true+ if <tt>offset == 0</tt>.
def utc?
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
new file mode 100644
index 0000000000..03e4a2adfa
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date_and_time/compatibility'
+
+class DateTime
+ prepend DateAndTime::Compatibility
+end
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 71612e09fa..946976c5e9 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -32,7 +32,7 @@ class String
parts.fetch(:offset, form == :utc ? 0 : nil)
)
- form == :utc ? time.utc : time.getlocal
+ form == :utc ? time.utc : time.to_time
end
# Converts a string to a Date value.
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 72c3234630..0bce632222 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'
+require 'active_support/core_ext/time/compatibility'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/time/zones'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 768c9a1b2c..b755726db2 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -73,6 +73,13 @@ class Time
end_of_day.to_i - to_i
end
+ # Returns the fraction of a second as a +Rational+
+ #
+ # Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2)
+ def sec_fraction
+ subsec
+ end
+
# Returns a new Time where one or more of the elements have been changed according
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
new file mode 100644
index 0000000000..945319461b
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date_and_time/compatibility'
+
+class Time
+ prepend DateAndTime::Compatibility
+end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 79cc748cf5..b1cec43124 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,6 +1,7 @@
require 'active_support/duration'
require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/date_and_time/compatibility'
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary
@@ -44,20 +45,21 @@ module ActiveSupport
PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze }
PRECISIONS[0] = '%FT%T'.freeze
- include Comparable
+ include Comparable, DateAndTime::Compatibility
attr_reader :time_zone
def initialize(utc_time, time_zone, local_time = nil, period = nil)
- @utc, @time_zone, @time = utc_time, time_zone, local_time
+ @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil
+ @time_zone, @time = time_zone, local_time
@period = @utc ? period : get_period_and_ensure_valid_local_time(period)
end
- # Returns a Time or DateTime instance that represents the time in +time_zone+.
+ # Returns a <tt>Time</tt> instance that represents the time in +time_zone+.
def time
@time ||= period.to_local(@utc)
end
- # Returns a Time or DateTime instance that represents the time in UTC.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
def utc
@utc ||= period.to_utc(@time)
end
@@ -77,10 +79,9 @@ module ActiveSupport
utc.in_time_zone(new_zone)
end
- # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your
- # system's <tt>ENV['TZ']</tt> zone.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
def localtime(utc_offset = nil)
- utc.respond_to?(:getlocal) ? utc.getlocal(utc_offset) : utc.to_time.getlocal(utc_offset)
+ utc.getlocal(utc_offset)
end
alias_method :getlocal, :localtime
@@ -401,11 +402,6 @@ module ActiveSupport
utc.to_r
end
- # Returns an instance of Time in the system timezone.
- def to_time
- utc.to_time
- end
-
# Returns an instance of DateTime with the timezone's UTC offset
#
# Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000
@@ -454,7 +450,6 @@ module ActiveSupport
# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
- # consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
return false if sym.to_sym == :acts_like_date?
time.respond_to?(sym, include_priv)
end
@@ -482,7 +477,7 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000))
+ ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec)
end
def duration_of_variable_length?(obj)
diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
new file mode 100644
index 0000000000..11cb1469da
--- /dev/null
+++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
@@ -0,0 +1,127 @@
+require 'abstract_unit'
+require 'active_support/time'
+require 'time_zone_test_helpers'
+
+class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
+ def setup
+ @utc_time = Time.utc(2016, 4, 23, 14, 11, 12)
+ @date_time = DateTime.new(2016, 4, 23, 14, 11, 12, 0)
+ @utc_offset = 3600
+ @system_offset = -14400
+ @zone = ActiveSupport::TimeZone['London']
+ end
+
+ def test_time_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_time_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = Time.new(2016, 4, 23, 15, 11, 12, 3600).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_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time
+
+ 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
+ time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).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_twz_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).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
+
+ time = ActiveSupport::TimeWithZone.new(@date_time, @zone).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
+ end
+ end
+ end
+
+ def test_twz_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).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
+
+ time = ActiveSupport::TimeWithZone.new(@date_time, @zone).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_string_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = "2016-04-23T15:11:12+01:00".to_time
+
+ 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
+ time = "2016-04-23T15:11:12+01:00".to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 16efeeadd5..fcd739c804 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -40,6 +40,24 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
Time::DATE_FORMATS.delete(:custom)
end
+ def test_localtime
+ with_env_tz 'US/Eastern' do
+ assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).localtime
+ end
+ end
+
+ def test_getlocal
+ with_env_tz 'US/Eastern' do
+ assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).getlocal
+ end
+ end
+
def test_to_date
assert_equal Date.new(2005, 2, 21), DateTime.new(2005, 2, 21, 14, 30, 0).to_date
end
@@ -50,7 +68,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_to_time
with_env_tz 'US/Eastern' do
- assert_equal Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.class
+ 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), 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
@@ -310,6 +328,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_utc
+ assert_instance_of Time, DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 16, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 15, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 10, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc
@@ -392,4 +411,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal 0, DateTime.civil(2000).nsec
assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec
end
+
+ def test_subsec
+ assert_equal 0, DateTime.civil(2000).subsec
+ assert_equal Rational(1,2), DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).subsec
+ end
end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index d8bb38621b..1205797fac 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -107,6 +107,20 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_sec_fraction
+ time = Time.utc(2016, 4, 23, 0, 0, Rational(1,10000000000))
+ assert_equal Rational(1,10000000000), time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0.0000000001)
+ assert_equal 0.0000000001.to_r, time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0, Rational(1,10000))
+ assert_equal Rational(1,10000000000), time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0, 0.0001)
+ assert_equal 0.0001.to_r / 1000000, time.sec_fraction
+ end
+
def test_beginning_of_day
assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day
with_env_tz 'US/Eastern' do
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 7acada011d..d90714acdb 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -11,10 +11,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase
@utc = Time.utc(2000, 1, 1, 0)
@time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
@twz = ActiveSupport::TimeWithZone.new(@utc, @time_zone)
+ @dt_twz = ActiveSupport::TimeWithZone.new(@utc.to_datetime, @time_zone)
end
def test_utc
assert_equal @utc, @twz.utc
+ assert_instance_of Time, @twz.utc
+ assert_instance_of Time, @dt_twz.utc
end
def test_time
@@ -47,6 +50,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_localtime
assert_equal @twz.localtime, @twz.utc.getlocal
+ assert_instance_of Time, @twz.localtime
+ assert_instance_of Time, @dt_twz.localtime
end
def test_utc?
diff --git a/activesupport/test/time_zone_test_helpers.rb b/activesupport/test/time_zone_test_helpers.rb
index 9632b89d09..eb6f7d0f85 100644
--- a/activesupport/test/time_zone_test_helpers.rb
+++ b/activesupport/test/time_zone_test_helpers.rb
@@ -13,4 +13,12 @@ module TimeZoneTestHelpers
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
+
+ def with_preserve_timezone(value)
+ old_preserve_tz = ActiveSupport.to_time_preserves_timezone
+ ActiveSupport.to_time_preserves_timezone = value
+ yield
+ ensure
+ ActiveSupport.to_time_preserves_timezone = old_preserve_tz
+ end
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 32bfdf272b..3a4a77724f 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Add `config/initializers/to_time_preserves_timezone.rb`, which tells
+ Active Support to preserve the receiver's timezone when calling `to_time`.
+ This matches the new behavior that will be part of Ruby 2.4.
+
+ Fixes #24617.
+
+ *Andrew White*
+
* Make `rails restart` command work with Puma by passing the restart command
which Puma can use to restart rails server.
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index f58e6ba653..4d5bb364b2 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -92,6 +92,7 @@ module Rails
cookie_serializer_config_exist = File.exist?('config/initializers/cookies_serializer.rb')
callback_terminator_config_exist = File.exist?('config/initializers/callback_terminator.rb')
active_record_belongs_to_required_by_default_config_exist = File.exist?('config/initializers/active_record_belongs_to_required_by_default.rb')
+ to_time_preserves_timezone_config_exist = File.exist?('config/initializers/to_time_preserves_timezone.rb')
action_cable_config_exist = File.exist?('config/cable.yml')
ssl_options_exist = File.exist?('config/initializers/ssl_options.rb')
rack_cors_config_exist = File.exist?('config/initializers/cors.rb')
@@ -112,6 +113,10 @@ module Rails
remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb'
end
+ unless to_time_preserves_timezone_config_exist
+ remove_file 'config/initializers/to_time_preserves_timezone.rb'
+ end
+
unless action_cable_config_exist
template 'config/cable.yml'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/to_time_preserves_timezone.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/to_time_preserves_timezone.rb
new file mode 100644
index 0000000000..8674be3227
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/to_time_preserves_timezone.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Preserve the timezone of the receiver when calling to `to_time`.
+# Ruby 2.4 will change the behavior of `to_time` to preserve the timezone
+# when converting to an instance of `Time` instead of the previous behavior
+# of converting to the local system timezone.
+#
+# Rails 5.0 introduced this config option so that apps made with earlier
+# versions of Rails are not affected when upgrading.
+ActiveSupport.to_time_preserves_timezone = true
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 2d9867fa9d..25a8635e7d 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -257,6 +257,34 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_rails_update_does_not_create_to_time_preserves_timezone
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.rm("#{app_root}/config/initializers/to_time_preserves_timezone.rb")
+
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/to_time_preserves_timezone.rb"
+ end
+ end
+
+ def test_rails_update_does_not_remove_to_time_preserves_timezone_if_already_present
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.touch("#{app_root}/config/initializers/to_time_preserves_timezone.rb")
+
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/to_time_preserves_timezone.rb"
+ end
+ end
+
def test_rails_update_does_not_create_ssl_options_by_default
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]