diff --git a/lib/tzinfo/country_timezone.rb b/lib/tzinfo/country_timezone.rb index 23dac7ae..d46bec0d 100644 --- a/lib/tzinfo/country_timezone.rb +++ b/lib/tzinfo/country_timezone.rb @@ -79,7 +79,13 @@ def latitude # calculated multiple times in concurrently executing threads. It is not # worth the overhead of locking to ensure that @latitude is only # calculated once. - @latitude ||= RubyCoreSupport.rational_new!(@latitude_numerator, @latitude_denominator) + unless @latitude + result = RubyCoreSupport.rational_new!(@latitude_numerator, @latitude_denominator) + return result if frozen? + @latitude = result + end + + @latitude end # The longitude of this timezone in degrees as a Rational. @@ -88,7 +94,13 @@ def longitude # calculated multiple times in concurrently executing threads. It is not # worth the overhead of locking to ensure that @longitude is only # calculated once. - @longitude ||= RubyCoreSupport.rational_new!(@longitude_numerator, @longitude_denominator) + unless @longitude + result = RubyCoreSupport.rational_new!(@longitude_numerator, @longitude_denominator) + return result if frozen? + @longitude = result + end + + @longitude end # Returns true if and only if the given CountryTimezone is equal to the diff --git a/lib/tzinfo/ruby_country_info.rb b/lib/tzinfo/ruby_country_info.rb index b23fe8a4..e51915d6 100644 --- a/lib/tzinfo/ruby_country_info.rb +++ b/lib/tzinfo/ruby_country_info.rb @@ -22,7 +22,9 @@ def zone_identifiers # calculated once. unless @zone_identifiers - @zone_identifiers = zones.collect {|zone| zone.identifier}.freeze + result = zones.collect {|zone| zone.identifier}.freeze + return result if frozen? + @zone_identifiers = result end @zone_identifiers @@ -40,8 +42,10 @@ def zones unless @zones zones = Zones.new @block.call(zones) if @block + result = zones.list.freeze + return result if frozen? @block = nil - @zones = zones.list.freeze + @zones = result end @zones diff --git a/lib/tzinfo/time_or_datetime.rb b/lib/tzinfo/time_or_datetime.rb index 248d095f..f358005f 100644 --- a/lib/tzinfo/time_or_datetime.rb +++ b/lib/tzinfo/time_or_datetime.rb @@ -54,11 +54,14 @@ def to_time # calculated once. unless @time - if @timestamp - @time = Time.at(@timestamp).utc + result = if @timestamp + Time.at(@timestamp).utc else - @time = Time.utc(year, mon, mday, hour, min, sec, usec) + Time.utc(year, mon, mday, hour, min, sec, usec) end + + return result if frozen? + @time = result end @time @@ -78,7 +81,9 @@ def to_datetime # Avoid using Rational unless necessary. u = usec s = u == 0 ? sec : Rational(sec * 1000000 + u, 1000000) - @datetime = RubyCoreSupport.datetime_new(year, mon, mday, hour, min, s) + result = RubyCoreSupport.datetime_new(year, mon, mday, hour, min, s) + return result if frozen? + @datetime = result end @datetime @@ -92,7 +97,9 @@ def to_i # calculated once. unless @timestamp - @timestamp = to_time.to_i + result = to_time.to_i + return result if frozen? + @timestamp = result end @timestamp diff --git a/lib/tzinfo/timezone_period.rb b/lib/tzinfo/timezone_period.rb index eeadfeb3..bc87df24 100644 --- a/lib/tzinfo/timezone_period.rb +++ b/lib/tzinfo/timezone_period.rb @@ -71,7 +71,9 @@ def utc_total_offset_rational # to ensure that @zone_identifiers is only calculated once. unless @utc_total_offset_rational - @utc_total_offset_rational = OffsetRationals.rational_for_offset(utc_total_offset) + result = OffsetRationals.rational_for_offset(utc_total_offset) + return result if frozen? + @utc_total_offset_rational = result end @utc_total_offset_rational end diff --git a/lib/tzinfo/timezone_proxy.rb b/lib/tzinfo/timezone_proxy.rb index 092bf971..c913011c 100644 --- a/lib/tzinfo/timezone_proxy.rb +++ b/lib/tzinfo/timezone_proxy.rb @@ -93,7 +93,13 @@ def real_timezone # calculated multiple times in concurrently executing threads. It is not # worth the overhead of locking to ensure that @real_timezone is only # calculated once. - @real_timezone ||= Timezone.get(@identifier) + unless @real_timezone + result = Timezone.get(@identifier) + return result if frozen? + @real_timezone = result + end + + @real_timezone end end end diff --git a/lib/tzinfo/timezone_transition.rb b/lib/tzinfo/timezone_transition.rb index bb5e3085..b905c627 100644 --- a/lib/tzinfo/timezone_transition.rb +++ b/lib/tzinfo/timezone_transition.rb @@ -43,7 +43,12 @@ def local_end_at # worth the overhead of locking to ensure that @local_end_at is only # calculated once. - @local_end_at = at.add_with_convert(@previous_offset.utc_total_offset) unless @local_end_at + unless @local_end_at + result = at.add_with_convert(@previous_offset.utc_total_offset) + return result if frozen? + @local_end_at = result + end + @local_end_at end @@ -67,7 +72,12 @@ def local_start_at # worth the overhead of locking to ensure that @local_start_at is only # calculated once. - @local_start_at = at.add_with_convert(@offset.utc_total_offset) unless @local_start_at + unless @local_start_at + result = at.add_with_convert(@offset.utc_total_offset) + return result if frozen? + @local_start_at = result + end + @local_start_at end diff --git a/lib/tzinfo/timezone_transition_definition.rb b/lib/tzinfo/timezone_transition_definition.rb index 26c7da86..016816b8 100644 --- a/lib/tzinfo/timezone_transition_definition.rb +++ b/lib/tzinfo/timezone_transition_definition.rb @@ -71,13 +71,16 @@ def at # overhead of locking to ensure that @at is only calculated once. unless @at - unless @denominator - @at = TimeOrDateTime.new(@numerator_or_time) + result = unless @denominator + TimeOrDateTime.new(@numerator_or_time) else r = RubyCoreSupport.rational_new!(@numerator_or_time, @denominator) dt = RubyCoreSupport.datetime_new!(r, 0, Date::ITALY) - @at = TimeOrDateTime.new(dt) + TimeOrDateTime.new(dt) end + + return result if frozen? + @at = result end @at diff --git a/lib/tzinfo/zoneinfo_country_info.rb b/lib/tzinfo/zoneinfo_country_info.rb index a1230e6e..c99acaa8 100644 --- a/lib/tzinfo/zoneinfo_country_info.rb +++ b/lib/tzinfo/zoneinfo_country_info.rb @@ -20,7 +20,9 @@ def zone_identifiers # calculated once. unless @zone_identifiers - @zone_identifiers = zones.collect {|zone| zone.identifier}.freeze + result = zones.collect {|zone| zone.identifier}.freeze + return result if frozen? + @zone_identifiers = result end @zone_identifiers diff --git a/test/tc_country_timezone.rb b/test/tc_country_timezone.rb index 1bf85bf3..77fd7d14 100644 --- a/test/tc_country_timezone.rb +++ b/test/tc_country_timezone.rb @@ -17,6 +17,12 @@ def test_latitude_new! ct = CountryTimezone.new!('Europe/London', 2059, 40, -5, 16) assert_equal(Rational(2059, 40), ct.latitude) end + + def test_latitude_after_freeze_new! + ct = CountryTimezone.new!('Europe/London', 2059, 40, -5, 16) + ct.freeze + assert_equal(Rational(2059, 40), ct.latitude) + end def test_latitude_new ct = CountryTimezone.new('Europe/London', Rational(2059, 40), Rational(-5, 16)) @@ -27,6 +33,12 @@ def test_longitude_new! ct = CountryTimezone.new!('Europe/London', 2059, 40, -5, 16) assert_equal(Rational(-5, 16), ct.longitude) end + + def test_longitude_after_freeze_new! + ct = CountryTimezone.new!('Europe/London', 2059, 40, -5, 16) + ct.freeze + assert_equal(Rational(-5, 16), ct.longitude) + end def test_longitude_new ct = CountryTimezone.new('Europe/London', Rational(2059, 40), Rational(-5, 16)) diff --git a/test/tc_ruby_country_info.rb b/test/tc_ruby_country_info.rb index 7893f796..c99f09f6 100644 --- a/test/tc_ruby_country_info.rb +++ b/test/tc_ruby_country_info.rb @@ -37,6 +37,19 @@ def test_zone_identifiers assert_equal(['ZZ/TimezoneB', 'ZZ/TimezoneA', 'ZZ/TimezoneC', 'ZZ/TimezoneD'], ci.zone_identifiers) assert(ci.zone_identifiers.frozen?) end + + def test_zone_identifiers_after_freeze + ci = RubyCountryInfo.new('ZZ', 'Zzz') do |c| + c.timezone('ZZ/TimezoneB', 1, 2, 1, 2, 'Timezone B') + c.timezone('ZZ/TimezoneA', 1, 4, 1, 4, 'Timezone A') + c.timezone('ZZ/TimezoneC', -10, 3, -20, 7, 'C') + c.timezone('ZZ/TimezoneD', -10, 3, -20, 7) + end + + ci.freeze + + assert_equal(['ZZ/TimezoneB', 'ZZ/TimezoneA', 'ZZ/TimezoneC', 'ZZ/TimezoneD'], ci.zone_identifiers) + end def test_zones_empty ci = RubyCountryInfo.new('ZZ', 'Zzz') {|c| } @@ -65,6 +78,23 @@ def test_zones ci.zones) assert(ci.zones.frozen?) end + + def test_zones_after_freeze + ci = RubyCountryInfo.new('ZZ', 'Zzz') do |c| + c.timezone('ZZ/TimezoneB', 1, 2, 1, 2, 'Timezone B') + c.timezone('ZZ/TimezoneA', 1, 4, 1, 4, 'Timezone A') + c.timezone('ZZ/TimezoneC', -10, 3, -20, 7, 'C') + c.timezone('ZZ/TimezoneD', -10, 3, -20, 7) + end + + ci.freeze + + assert_equal([CountryTimezone.new!('ZZ/TimezoneB', 1, 2, 1, 2, 'Timezone B'), + CountryTimezone.new!('ZZ/TimezoneA', 1, 4, 1, 4, 'Timezone A'), + CountryTimezone.new!('ZZ/TimezoneC', -10, 3, -20, 7, 'C'), + CountryTimezone.new!('ZZ/TimezoneD', -10, 3, -20, 7)], + ci.zones) + end def test_deferred_evaluate block_called = false diff --git a/test/tc_time_or_datetime.rb b/test/tc_time_or_datetime.rb index 05f574dd..9f38bfbe 100644 --- a/test/tc_time_or_datetime.rb +++ b/test/tc_time_or_datetime.rb @@ -128,6 +128,11 @@ def test_to_time_trunc_to_usec assert_equal(Time.utc(2006, 3, 24, 15, 32, 3, 721123), TimeOrDateTime.new(DateTime.new(2006, 3, 24, 15, 32, 3 + Rational(7211239, 10000000))).to_time) end + + def test_to_time_after_freeze + assert_equal(Time.utc(2006, 3, 24, 15, 32, 3), TimeOrDateTime.new(DateTime.new(2006, 3, 24, 15, 32, 3)).freeze.to_time) + assert_equal(Time.utc(2006, 3, 24, 15, 32, 3), TimeOrDateTime.new(1143214323).freeze.to_time) + end def test_to_datetime assert_equal(DateTime.new(2006, 3, 24, 15, 32, 3), @@ -157,6 +162,11 @@ def test_to_datetime_trunc_to_usec assert_equal(DateTime.new(2006, 3, 24, 15, 32, 3 + Rational(721123, 1000000)), TimeOrDateTime.new(Time.utc(2006, 3, 24, 15, 32, 3, 721123 + Rational(9, 10))).to_datetime) end + + def test_to_datetime_after_freeze + assert_equal(DateTime.new(2006, 3, 24, 15, 32, 3), TimeOrDateTime.new(Time.utc(2006, 3, 24, 15, 32, 3)).freeze.to_datetime) + assert_equal(DateTime.new(2006, 3, 24, 15, 32, 3), TimeOrDateTime.new(1143214323).freeze.to_datetime) + end def test_to_i assert_equal(1143214323, @@ -172,6 +182,11 @@ def test_to_i assert_equal(1143214323, TimeOrDateTime.new('1143214323').to_i) end + + def test_to_i_after_freeze + assert_equal(1143214323, TimeOrDateTime.new(Time.utc(2006, 3, 24, 15, 32, 3)).freeze.to_i) + assert_equal(1143214323, TimeOrDateTime.new(DateTime.new(2006, 3, 24, 15, 32, 3)).freeze.to_i) + end def test_to_orig assert_equal(Time.utc(2006, 3, 24, 15, 32, 3), diff --git a/test/tc_timezone_period.rb b/test/tc_timezone_period.rb index be7ef3fa..bcfb8d57 100644 --- a/test/tc_timezone_period.rb +++ b/test/tc_timezone_period.rb @@ -149,6 +149,13 @@ def test_initialize_offset assert_nil(p.local_end) assert_nil(p.local_end_time) end + + def test_utc_total_offset_rational_after_freeze + o = TimezoneOffset.new(3600, 0, :TEST) + p = TimezonePeriod.new(nil, nil, o) + p.freeze + assert_equal(Rational(1, 24), p.utc_total_offset_rational) + end def test_dst p1 = TimezonePeriod.new(nil, nil, TimezoneOffset.new(-14400, 3600, :TEST)) diff --git a/test/tc_timezone_proxy.rb b/test/tc_timezone_proxy.rb index 55718aa6..22ba3d4c 100644 --- a/test/tc_timezone_proxy.rb +++ b/test/tc_timezone_proxy.rb @@ -88,6 +88,15 @@ def test_canonical_linked assert_same(Timezone.get('UTC'), proxy.canonical_zone) end end + + def test_after_freeze + proxy = TimezoneProxy.new('Europe/London') + real = Timezone.get('Europe/London') + t = Time.utc(2017, 6, 1) + proxy.freeze + assert_equal('Europe/London', proxy.identifier) + assert_equal(real.utc_to_local(t), proxy.utc_to_local(t)) + end def test_equals assert_equal(true, TimezoneProxy.new('Europe/London') == TimezoneProxy.new('Europe/London')) diff --git a/test/tc_timezone_transition.rb b/test/tc_timezone_transition.rb index 8941ec97..3dba5182 100644 --- a/test/tc_timezone_transition.rb +++ b/test/tc_timezone_transition.rb @@ -68,6 +68,13 @@ def test_local_end_at assert(TimeOrDateTime.new(DateTime.new(2006, 5, 30, 1, 31, 20)).eql?(t2.local_end_at)) assert(TimeOrDateTime.new(Time.utc(2006, 5, 30, 1, 31, 20)).eql?(t3.local_end_at)) end + + def test_local_end_at_after_freeze + t = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT), + TimezoneOffset.new(3600, 0, :TST), 1148949080) + t.freeze + assert(TimeOrDateTime.new(1148952680).eql?(t.local_end_at)) + end def test_local_end t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT), @@ -107,6 +114,13 @@ def test_local_start_at assert(TimeOrDateTime.new(DateTime.new(2006, 5, 30, 2, 31, 20)).eql?(t2.local_start_at)) assert(TimeOrDateTime.new(Time.utc(2006, 5, 30, 2, 31, 20)).eql?(t3.local_start_at)) end + + def test_local_start_at_after_freeze + t = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT), + TimezoneOffset.new(3600, 0, :TST), 1148949080) + t.freeze + assert(TimeOrDateTime.new(1148956280).eql?(t.local_start_at)) + end def test_local_start t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT), diff --git a/test/tc_timezone_transition_definition.rb b/test/tc_timezone_transition_definition.rb index a542c129..b66233b2 100644 --- a/test/tc_timezone_transition_definition.rb +++ b/test/tc_timezone_transition_definition.rb @@ -70,6 +70,17 @@ def test_at_after_32bit assert(TimeOrDateTime.new(DateTime.new(2038, 1, 19, 3, 14, 8)).eql?(t.at)) end end + + def test_at_after_freeze + t1 = TimezoneTransitionDefinition.new(TimezoneOffset.new(3600, 3600, :TDT), + TimezoneOffset.new(3600, 0, :TST), 1148949080) + t2 = TimezoneTransitionDefinition.new(TimezoneOffset.new(3600, 3600, :TDT), + TimezoneOffset.new(3600, 0, :TST), 5300392727, 2160) + t1.freeze + t2.freeze + assert(TimeOrDateTime.new(1148949080).eql?(t1.at)) + assert(TimeOrDateTime.new(DateTime.new(2006, 5, 30, 0, 31, 20)).eql?(t2.at)) + end def test_eql_timestamp t1 = TimezoneTransitionDefinition.new(TimezoneOffset.new(3600, 3600, :TDT), diff --git a/test/tc_zoneinfo_country_info.rb b/test/tc_zoneinfo_country_info.rb index 67a06311..f54e9b16 100644 --- a/test/tc_zoneinfo_country_info.rb +++ b/test/tc_zoneinfo_country_info.rb @@ -35,6 +35,20 @@ def test_zone_identifiers assert(!ci.zones.equal?(zones)) assert(!zones.frozen?) end + + def test_zone_identifiers_after_freeze + zones = [ + CountryTimezone.new('ZZ/TimezoneB', Rational(1, 2), Rational(1, 2), 'Timezone B'), + CountryTimezone.new('ZZ/TimezoneA', Rational(1, 4), Rational(1, 4), 'Timezone A'), + CountryTimezone.new('ZZ/TimezoneC', Rational(-10, 3), Rational(-20, 7), 'C'), + CountryTimezone.new('ZZ/TimezoneD', Rational(-10, 3), Rational(-20, 7)) + ] + + ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', zones) + ci.freeze + + assert_equal(['ZZ/TimezoneB', 'ZZ/TimezoneA', 'ZZ/TimezoneC', 'ZZ/TimezoneD'], ci.zone_identifiers) + end def test_zones_empty ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', [])