Merge branch 'tzinfo2'

This commit is contained in:
Kasper Timm Hansen 2020-03-28 20:53:24 +01:00
commit bf34c808f9
No known key found for this signature in database
GPG Key ID: 191153215EDA53D8
12 changed files with 113 additions and 27 deletions

View File

@ -73,7 +73,7 @@ PATH
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 2.0)
zeitwerk (~> 2.2, >= 2.2.2) zeitwerk (~> 2.2, >= 2.2.2)
rails (6.1.0.alpha) rails (6.1.0.alpha)
actioncable (= 6.1.0.alpha) actioncable (= 6.1.0.alpha)
@ -489,14 +489,12 @@ GEM
eventmachine (~> 1.0, >= 1.0.4) eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3) rack (>= 1, < 3)
thor (1.0.1) thor (1.0.1)
thread_safe (0.3.6)
thread_safe (0.3.6-java)
tilt (2.0.10) tilt (2.0.10)
turbolinks (5.2.1) turbolinks (5.2.1)
turbolinks-source (~> 5.2) turbolinks-source (~> 5.2)
turbolinks-source (5.2.0) turbolinks-source (5.2.0)
tzinfo (1.2.6) tzinfo (2.0.0)
thread_safe (~> 0.1) concurrent-ruby (~> 1.0)
tzinfo-data (1.2019.3) tzinfo-data (1.2019.3)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
uber (0.1.0) uber (0.1.0)

View File

@ -1,3 +1,16 @@
* Update to TZInfo v2.0.0.
This changes the output of `ActiveSupport::TimeZone.utc_to_local`, but
can be controlled with the
`Rails.application.config.active_support.utc_to_local_returns_utc_offset_times` config.
New Rails 6.1 apps have it enabled by default, existing apps can upgrade
via the config in config/initializers/new_framework_defaults_6_1.rb
See the `utc_to_local_returns_utc_offset_times` documentation for details.
*Phil Ross and Jared Beck*
* Add Date and Time `#yesterday?` and `#tomorrow?` alongside `#today?`. * Add Date and Time `#yesterday?` and `#tomorrow?` alongside `#today?`.
Aliased to `#prev_day?` and `#next_day?` to match the existing `#prev/next_day` methods. Aliased to `#prev_day?` and `#next_day?` to match the existing `#prev/next_day` methods.

View File

@ -34,7 +34,7 @@ Gem::Specification.new do |s|
# https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
s.add_dependency "i18n", ">= 1.6", "< 2" s.add_dependency "i18n", ">= 1.6", "< 2"
s.add_dependency "tzinfo", "~> 1.1" s.add_dependency "tzinfo", "~> 2.0"
s.add_dependency "minitest", "~> 5.1" s.add_dependency "minitest", "~> 5.1"
s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2" s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2"

View File

@ -95,6 +95,14 @@ module ActiveSupport
def self.to_time_preserves_timezone=(value) def self.to_time_preserves_timezone=(value)
DateAndTime::Compatibility.preserve_timezone = value DateAndTime::Compatibility.preserve_timezone = value
end end
def self.utc_to_local_returns_utc_offset_times
DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times
end
def self.utc_to_local_returns_utc_offset_times=(value)
DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times = value
end
end end
autoload :I18n, "active_support/i18n" autoload :I18n, "active_support/i18n"

View File

@ -12,5 +12,20 @@ module DateAndTime
# this behavior, but new apps will have an initializer that sets # this behavior, but new apps will have an initializer that sets
# this to true, because the new behavior is preferred. # this to true, because the new behavior is preferred.
mattr_accessor :preserve_timezone, instance_writer: false, default: false 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 an UTC offset, with `false` local
# times are returned as UTC.
#
# # Given this zone:
# zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
#
# # With `utc_to_local_returns_utc_offset_times = false`, local time is converted to UTC:
# zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 UTC
#
# # 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
end end
end end

View File

@ -57,12 +57,12 @@ module ActiveSupport
# Returns a <tt>Time</tt> instance that represents the time in +time_zone+. # Returns a <tt>Time</tt> instance that represents the time in +time_zone+.
def time def time
@time ||= period.to_local(@utc) @time ||= incorporate_utc_offset(@utc, utc_offset)
end end
# Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone. # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
def utc def utc
@utc ||= period.to_utc(@time) @utc ||= incorporate_utc_offset(@time, -utc_offset)
end end
alias_method :comparable_time, :utc alias_method :comparable_time, :utc
alias_method :getgm, :utc alias_method :getgm, :utc
@ -104,13 +104,13 @@ module ActiveSupport
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.now.utc? # => false # Time.zone.now.utc? # => false
def utc? def utc?
period.offset.abbreviation == :UTC || period.offset.abbreviation == :UCT zone == "UTC" || zone == "UCT"
end end
alias_method :gmt?, :utc? alias_method :gmt?, :utc?
# Returns the offset from current time to UTC time in seconds. # Returns the offset from current time to UTC time in seconds.
def utc_offset def utc_offset
period.utc_total_offset period.observed_utc_offset
end end
alias_method :gmt_offset, :utc_offset alias_method :gmt_offset, :utc_offset
alias_method :gmtoff, :utc_offset alias_method :gmtoff, :utc_offset
@ -132,7 +132,7 @@ module ActiveSupport
# Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)"
# Time.zone.now.zone # => "EST" # Time.zone.now.zone # => "EST"
def zone def zone
period.zone_identifier.to_s period.abbreviation
end end
# Returns a string of the object's date, time, zone, and offset from UTC. # Returns a string of the object's date, time, zone, and offset from UTC.
@ -538,6 +538,16 @@ module ActiveSupport
end end
private private
SECONDS_PER_DAY = 86400
def incorporate_utc_offset(time, offset)
if time.kind_of?(Date)
time + Rational(offset, SECONDS_PER_DAY)
else
time + offset
end
end
def get_period_and_ensure_valid_local_time(period) def get_period_and_ensure_valid_local_time(period)
# we don't want a Time.local instance enforcing its own DST rules as well, # we don't want a Time.local instance enforcing its own DST rules as well,
# so transfer time values to a utc constructor if necessary # so transfer time values to a utc constructor if necessary

View File

@ -203,7 +203,7 @@ module ActiveSupport
end end
def find_tzinfo(name) def find_tzinfo(name)
TZInfo::Timezone.new(MAPPING[name] || name) TZInfo::Timezone.get(MAPPING[name] || name)
end end
alias_method :create, :new alias_method :create, :new
@ -273,7 +273,7 @@ module ActiveSupport
memo memo
end end
else else
create(tz_id, nil, TZInfo::Timezone.new(tz_id)) create(tz_id, nil, TZInfo::Timezone.get(tz_id))
end end
end.sort! end.sort!
end end
@ -302,11 +302,7 @@ module ActiveSupport
# Returns the offset of this time zone from UTC in seconds. # Returns the offset of this time zone from UTC in seconds.
def utc_offset def utc_offset
if @utc_offset @utc_offset || tzinfo&.current_period&.base_utc_offset
@utc_offset
else
tzinfo.current_period.utc_offset if tzinfo && tzinfo.current_period
end
end end
# Returns a formatted string of the offset from UTC, or an alternative # Returns a formatted string of the offset from UTC, or an alternative
@ -507,10 +503,17 @@ module ActiveSupport
end end
# Adjust the given time to the simultaneous time in the time zone # Adjust the given time to the simultaneous time in the time zone
# represented by +self+. Returns a Time.utc() instance -- if you want an # represented by +self+. Returns a local time with the appropriate offset
# ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead. # -- if you want an ActiveSupport::TimeWithZone instance, use
# Time#in_time_zone() instead.
#
# As of tzinfo 2, utc_to_local returns a Time with a non-zero utc_offset.
# See the `utc_to_local_returns_utc_offset_times` config for more info.
def utc_to_local(time) def utc_to_local(time)
tzinfo.utc_to_local(time) tzinfo.utc_to_local(time).yield_self do |t|
ActiveSupport.utc_to_local_returns_utc_offset_times ?
t : Time.utc(t.year, t.month, t.day, t.hour, t.min, t.sec, t.sec_fraction)
end
end end
# Adjust the given time to the simultaneous time in UTC. Returns a # Adjust the given time to the simultaneous time in UTC. Returns a

View File

@ -10,8 +10,15 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_utc_to_local def test_utc_to_local
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
assert_equal Time.utc(1999, 12, 31, 19), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500
assert_equal Time.utc(2000, 6, 30, 20), zone.utc_to_local(Time.utc(2000, 7)) # dst offset -0400 with_utc_to_local_returns_utc_offset_times false do
assert_equal Time.utc(1999, 12, 31, 19), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500
assert_equal Time.utc(2000, 6, 30, 20), zone.utc_to_local(Time.utc(2000, 7)) # dst offset -0400
end
with_utc_to_local_returns_utc_offset_times true do
assert_equal Time.new(1999, 12, 31, 19, 0, 0, -18000), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500
assert_equal Time.new(2000, 6, 30, 20, 0, 0, -14400), zone.utc_to_local(Time.utc(2000, 7)) # dst offset -0400
end
end end
def test_local_to_utc def test_local_to_utc
@ -22,7 +29,7 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_period_for_local def test_period_for_local
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
assert_instance_of TZInfo::TimezonePeriod, zone.period_for_local(Time.utc(2000)) assert_kind_of TZInfo::TimezonePeriod, zone.period_for_local(Time.utc(2000))
end end
ActiveSupport::TimeZone::MAPPING.each_key do |name| ActiveSupport::TimeZone::MAPPING.each_key do |name|
@ -54,7 +61,7 @@ class TimeZoneTest < ActiveSupport::TestCase
define_method("test_utc_offset_for_#{name}") do define_method("test_utc_offset_for_#{name}") do
period = zone.tzinfo.current_period period = zone.tzinfo.current_period
assert_equal period.utc_offset, zone.utc_offset assert_equal period.base_utc_offset, zone.utc_offset
end end
end end
@ -96,8 +103,15 @@ class TimeZoneTest < ActiveSupport::TestCase
zone = ActiveSupport::TimeZone["America/Montevideo"] zone = ActiveSupport::TimeZone["America/Montevideo"]
assert_equal ActiveSupport::TimeZone, zone.class assert_equal ActiveSupport::TimeZone, zone.class
assert_equal zone.object_id, ActiveSupport::TimeZone["America/Montevideo"].object_id assert_equal zone.object_id, ActiveSupport::TimeZone["America/Montevideo"].object_id
assert_equal Time.utc(2010, 1, 31, 22), zone.utc_to_local(Time.utc(2010, 2)) # daylight saving offset -0200
assert_equal Time.utc(2010, 3, 31, 21), zone.utc_to_local(Time.utc(2010, 4)) # standard offset -0300 with_utc_to_local_returns_utc_offset_times false do
assert_equal Time.utc(2010, 1, 31, 22), zone.utc_to_local(Time.utc(2010, 2)) # daylight saving offset -0200
assert_equal Time.utc(2010, 3, 31, 21), zone.utc_to_local(Time.utc(2010, 4)) # standard offset -0300
end
with_utc_to_local_returns_utc_offset_times true do
assert_equal Time.new(2010, 1, 31, 22, 0, 0, -7200), zone.utc_to_local(Time.utc(2010, 2)) # daylight saving offset -0200
assert_equal Time.new(2010, 3, 31, 21, 0, 0, -10800), zone.utc_to_local(Time.utc(2010, 4)) # standard offset -0300
end
end end
def test_today def test_today

View File

@ -36,4 +36,12 @@ module TimeZoneTestHelpers
ActiveSupport::TimeZone::MAPPING.clear ActiveSupport::TimeZone::MAPPING.clear
ActiveSupport::TimeZone::MAPPING.merge!(old_mappings) ActiveSupport::TimeZone::MAPPING.merge!(old_mappings)
end end
def with_utc_to_local_returns_utc_offset_times(value)
old_tzinfo2_format = ActiveSupport.utc_to_local_returns_utc_offset_times
ActiveSupport.utc_to_local_returns_utc_offset_times = value
yield
ensure
ActiveSupport.utc_to_local_returns_utc_offset_times = old_tzinfo2_format
end
end end

View File

@ -178,6 +178,10 @@ module Rails
if respond_to?(:action_dispatch) if respond_to?(:action_dispatch)
action_dispatch.cookies_same_site_protection = :lax action_dispatch.cookies_same_site_protection = :lax
end end
if respond_to?(:active_support)
active_support.utc_to_local_returns_utc_offset_times = true
end
else else
raise "Unknown version #{target_version.to_s.inspect}" raise "Unknown version #{target_version.to_s.inspect}"
end end

View File

@ -21,3 +21,7 @@
# This change is not backwards compatible with earlier Rails versions. # This change is not backwards compatible with earlier Rails versions.
# It's best enabled when your entire app is migrated and stable on 6.1. # It's best enabled when your entire app is migrated and stable on 6.1.
# Rails.application.config.action_dispatch.cookies_same_site_protection = :lax # Rails.application.config.action_dispatch.cookies_same_site_protection = :lax
# Specify whether `ActiveSupport::TimeZone.utc_to_local` returns a time with an
# UTC offset or a UTC time.
# Rails.application.config.active_support.utc_to_local_returns_utc_offset_times = true

View File

@ -2295,6 +2295,15 @@ module ApplicationTests
assert_equal :lax, Rails.application.config.action_dispatch.cookies_same_site_protection assert_equal :lax, Rails.application.config.action_dispatch.cookies_same_site_protection
end end
test "enables utc_to_local_returns_utc_offset_times by default" do
remove_from_config '.*config\.load_defaults.*\n'
add_to_config 'config.load_defaults "6.1"'
app "development"
assert_equal true, Rails.application.config.active_support.utc_to_local_returns_utc_offset_times
end
test "ActiveStorage.queues[:analysis] is :active_storage_analysis by default" do test "ActiveStorage.queues[:analysis] is :active_storage_analysis by default" do
app "development" app "development"