From 676d6a651497b56f31e00db4e7795669de0da672 Mon Sep 17 00:00:00 2001 From: gbuesing Date: Sun, 29 Jun 2008 15:17:51 -0500 Subject: TimeWithZone: when crossing DST boundary, treat Durations of days, months or years as variable-length, and all other values as absolute length. A time + 24.hours will advance exactly 24 hours, but a time + 1.day will advance 23-25 hours, depending on the day. Ensure consistent behavior across all advancing methods. --- activesupport/lib/active_support/time_with_zone.rb | 45 ++++++++++++++++------ 1 file changed, 33 insertions(+), 12 deletions(-) (limited to 'activesupport/lib/active_support/time_with_zone.rb') diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index bfc5b16039..7370a45931 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -159,19 +159,24 @@ module ActiveSupport utc == other end - # If wrapped +time+ is a DateTime, use DateTime#since instead of +. - # Otherwise, just pass on to +method_missing+. def +(other) - result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other) - result.in_time_zone(time_zone) + # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time, + # otherwise move forward from #utc, for accuracy when moving across DST boundaries + if duration_of_variable_length?(other) + method_missing(:+, other) + else + result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other) + result.in_time_zone(time_zone) + end end - - # If a time-like object is passed in, compare it with +utc+. - # Else if wrapped +time+ is a DateTime, use DateTime#ago instead of DateTime#-. - # Otherwise, just pass on to +method_missing+. + def -(other) + # If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time, + # otherwise move backwards #utc, for accuracy when moving across DST boundaries if other.acts_like?(:time) utc - other + elsif duration_of_variable_length?(other) + method_missing(:-, other) else result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other) result.in_time_zone(time_zone) @@ -179,15 +184,27 @@ module ActiveSupport end def since(other) - utc.since(other).in_time_zone(time_zone) + # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time, + # otherwise move forward from #utc, for accuracy when moving across DST boundaries + if duration_of_variable_length?(other) + method_missing(:since, other) + else + utc.since(other).in_time_zone(time_zone) + end end def ago(other) - utc.ago(other).in_time_zone(time_zone) + since(-other) end - + def advance(options) - utc.advance(options).in_time_zone(time_zone) + # If we're advancing a value of variable length (i.e., years, months, days), advance from #time, + # otherwise advance from #utc, for accuracy when moving across DST boundaries + if options.detect {|k,v| [:years, :months, :days].include? k} + method_missing(:advance, options) + else + utc.advance(options).in_time_zone(time_zone) + end end %w(year mon month day mday hour min sec).each do |method_name| @@ -279,5 +296,9 @@ module ActiveSupport def transfer_time_values_to_utc_constructor(time) ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0) end + + def duration_of_variable_length?(obj) + ActiveSupport::Duration === obj && obj.parts.flatten.detect {|p| [:years, :months, :days].include? p } + end end end -- cgit v1.2.3 From 6f1d827096ad9f1044a6da7141e7ae720a64ec6b Mon Sep 17 00:00:00 2001 From: gbuesing Date: Sun, 29 Jun 2008 15:26:56 -0500 Subject: TimeWithZone#advance: treat :weeks option as variable-length --- activesupport/lib/active_support/time_with_zone.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activesupport/lib/active_support/time_with_zone.rb') diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 7370a45931..88593eb92d 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -198,9 +198,9 @@ module ActiveSupport end def advance(options) - # If we're advancing a value of variable length (i.e., years, months, days), advance from #time, + # If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time, # otherwise advance from #utc, for accuracy when moving across DST boundaries - if options.detect {|k,v| [:years, :months, :days].include? k} + if options.detect {|k,v| [:years, :weeks, :months, :days].include? k} method_missing(:advance, options) else utc.advance(options).in_time_zone(time_zone) -- cgit v1.2.3 From 34510456585216004e483b79beeea3ddc3eb4de6 Mon Sep 17 00:00:00 2001 From: gbuesing Date: Mon, 14 Jul 2008 23:16:39 -0500 Subject: Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone --- activesupport/lib/active_support/time_with_zone.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activesupport/lib/active_support/time_with_zone.rb') diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 88593eb92d..e85bfe9b2e 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -263,7 +263,7 @@ module ActiveSupport end def marshal_load(variables) - initialize(variables[0], ::Time.send!(:get_zone, variables[1]), variables[2]) + initialize(variables[0].utc, ::Time.send!(:get_zone, variables[1]), variables[2].utc) end # Ensure proxy class responds to all methods that underlying time instance responds to. -- cgit v1.2.3