Skip to content

Commit

Permalink
quoted_date converts time-like objects to ActiveRecord::Base.default_…
Browse files Browse the repository at this point in the history
…timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc [#2946 state:resolved]
  • Loading branch information
gbuesing committed Aug 4, 2009
1 parent 9b68877 commit 6f97ad0
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 1 deletion.
2 changes: 2 additions & 0 deletions activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*

* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing]

* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]

* Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha]
Expand Down
Expand Up @@ -60,7 +60,12 @@ def quoted_false
end

def quoted_date(value)
value.to_s(:db)
if value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
else
value
end.to_s(:db)
end

def quoted_string_prefix
Expand Down
69 changes: 69 additions & 0 deletions activerecord/test/cases/base_test.rb
Expand Up @@ -464,6 +464,60 @@ def test_preserving_time_objects
end
end

def test_preserving_time_objects_with_local_time_conversion_to_default_timezone_utc
with_env_tz 'America/New_York' do
with_active_record_default_timezone :utc do
time = Time.local(2000)
topic = Topic.create('written_on' => time)
saved_time = Topic.find(topic.id).written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a
assert_equal [0, 0, 5, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
end
end
end

def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc
with_env_tz 'America/New_York' do
with_active_record_default_timezone :utc do
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
topic = Topic.create('written_on' => time)
saved_time = Topic.find(topic.id).written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
assert_equal [0, 0, 6, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
end
end
end
end

def test_preserving_time_objects_with_utc_time_conversion_to_default_timezone_local
with_env_tz 'America/New_York' do
time = Time.utc(2000)
topic = Topic.create('written_on' => time)
saved_time = Topic.find(topic.id).written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a
assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a
end
end

def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
topic = Topic.create('written_on' => time)
saved_time = Topic.find(topic.id).written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
assert_equal [0, 0, 1, 1, 1, 2000, 6, 1, false, "EST"], saved_time.to_a
end
end
end
end

def test_custom_mutator
topic = Topic.find(1)
# This mutator is protected in the class definition
Expand Down Expand Up @@ -2115,4 +2169,19 @@ def test_create_with_custom_timestamps
def test_dup
assert !Minimalistic.new.freeze.dup.frozen?
end

protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
yield
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end

def with_active_record_default_timezone(zone)
old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone
yield
ensure
ActiveRecord::Base.default_timezone = old_zone
end
end
50 changes: 50 additions & 0 deletions activerecord/test/cases/finder_test.rb
Expand Up @@ -423,6 +423,42 @@ def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not
assert_equal customers(:david), found_customer
end

def test_condition_utc_time_interpolation_with_default_timezone_local
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
topic = Topic.first
assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getutc])
end
end
end

def test_hash_condition_utc_time_interpolation_with_default_timezone_local
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
topic = Topic.first
assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getutc})
end
end
end

def test_condition_local_time_interpolation_with_default_timezone_utc
with_env_tz 'America/New_York' do
with_active_record_default_timezone :utc do
topic = Topic.first
assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getlocal])
end
end
end

def test_hash_condition_local_time_interpolation_with_default_timezone_utc
with_env_tz 'America/New_York' do
with_active_record_default_timezone :utc do
topic = Topic.first
assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getlocal})
end
end
end

def test_bind_variables
assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
Expand Down Expand Up @@ -1087,4 +1123,18 @@ def bind(statement, *vars)
ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
end
end

def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
yield
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end

def with_active_record_default_timezone(zone)
old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone
yield
ensure
ActiveRecord::Base.default_timezone = old_zone
end
end

1 comment on commit 6f97ad0

@rsim
Copy link
Contributor

@rsim rsim commented on 6f97ad0 Aug 7, 2009

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quoted_date will not change DateTime values local time zone if ActiveRecord::Base.default_timezone == :local
because DateTime objects do not have getlocal method (just getutc method).

Either this implementation should be changed or maybe ActiveSupport could add getlocal method to DateTime class (so that it would behave similar to Time class in this aspect).

Because of this issue test_saves_both_date_and_time in date_time_test.rb is not failing but should be failing (because DateTime UTC value is stored in database and then compared with local time zone Time value).

Please sign in to comment.