Skip to content

Commit

Permalink
Make Time#change work with offsets other than UTC or local
Browse files Browse the repository at this point in the history
Use Time.new to create times where the current offset is not zero or
not in the local time zone - closes rails#4847 and rails#6651.
  • Loading branch information
pixeltrix committed Jul 1, 2012
1 parent 73bddd1 commit 98b46bf
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 10 deletions.
2 changes: 2 additions & 0 deletions activesupport/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##

* Time#change now works with time values with offsets other than UTC or the local time zone. *Andrew White*

* AS::Callbacks: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev*

* Add `Time#prev_quarter' and 'Time#next_quarter' short-hands for months_ago(3) and months_since(3). *SungHee Kang*
Expand Down
25 changes: 15 additions & 10 deletions activesupport/lib/active_support/core_ext/time/calculations.rb
Expand Up @@ -86,16 +86,21 @@ def seconds_since_midnight
# (hour, min, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
# minute is passed, then sec and usec is set to 0.
def change(options)
::Time.send(
utc? ? :utc_time : :local_time,
options.fetch(:year, year),
options.fetch(:month, month),
options.fetch(:day, day),
options.fetch(:hour, hour),
options.fetch(:min, options[:hour] ? 0 : min),
options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec),
options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
)
new_year = options.fetch(:year, year)
new_month = options.fetch(:month, month)
new_day = options.fetch(:day, day)
new_hour = options.fetch(:hour, hour)
new_min = options.fetch(:min, options[:hour] ? 0 : min)
new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))

if utc?
::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
elsif zone
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
else
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
end
end

# Uses Date to provide precise Time calculations for years, months, and days.
Expand Down
31 changes: 31 additions & 0 deletions activesupport/test/core_ext/time_ext_test.rb
Expand Up @@ -459,6 +459,15 @@ def test_utc_change
assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(:min => 45)
end

def test_offset_change
assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2006)
assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:month => 6)
assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2012, :month => 9)
assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16)
assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16, :min => 45)
assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:min => 45)
end

def test_advance
assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 1)
assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:months => 4)
Expand Down Expand Up @@ -503,6 +512,28 @@ def test_utc_advance
assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)
end

def test_offset_advance
assert_equal Time.new(2006,2,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 1)
assert_equal Time.new(2005,6,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:months => 4)
assert_equal Time.new(2005,3,21,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3)
assert_equal Time.new(2005,3,25,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.5)
assert_in_delta Time.new(2005,3,26,12,51,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.7), 1
assert_equal Time.new(2005,3,5,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5)
assert_equal Time.new(2005,3,6,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.5)
assert_in_delta Time.new(2005,3,6,8,3,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.7), 1
assert_equal Time.new(2012,9,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 7)
assert_equal Time.new(2013,10,3,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 19, :days => 11)
assert_equal Time.new(2013,10,17,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5)
assert_equal Time.new(2001,12,27,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => -3, :months => -2, :days => -1)
assert_equal Time.new(2005,2,28,15,15,10,'-08:00'), Time.new(2004,2,29,15,15,10,'-08:00').advance(:years => 1) #leap day plus one year
assert_equal Time.new(2005,2,28,20,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5)
assert_equal Time.new(2005,2,28,15,22,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:minutes => 7)
assert_equal Time.new(2005,2,28,15,15,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:seconds => 9)
assert_equal Time.new(2005,2,28,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5, :minutes => 7, :seconds => 9)
assert_equal Time.new(2005,2,28,10,8,1,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => -5, :minutes => -7, :seconds => -9)
assert_equal Time.new(2013,10,17,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)
end

def test_advance_with_nsec
t = Time.at(0, Rational(108635108, 1000))
assert_equal t, t.advance(:months => 0)
Expand Down

0 comments on commit 98b46bf

Please sign in to comment.