Ticket #6835: add-duration-to-date-time.diff
| File add-duration-to-date-time.diff, 8.4 kB (added by eventualbuddha, 2 years ago) |
|---|
-
lib/active_support/core_ext/date.rb
old new 1 1 require 'date' 2 require File.dirname(__FILE__) + '/date/calculations' 2 3 require File.dirname(__FILE__) + '/date/conversions' 3 4 4 5 class Date#:nodoc: 6 include ActiveSupport::CoreExtensions::Date::Calculations 5 7 include ActiveSupport::CoreExtensions::Date::Conversions 6 8 end -
lib/active_support/core_ext/numeric/time.rb
old new 3 3 module Numeric #:nodoc: 4 4 # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. 5 5 # 6 # If you need precise date calculations that doesn't just treat months as 30 days, then have 7 # a look at Time#advance. 6 # These methods use Time#advance for precise date calculations when using from_now, ago, etc. 7 # as well as adding or subtracting their results from a Time object. For example: 8 # 9 # # equivalent to Time.now.advance(:months => 1) 10 # 1.month.from_now 11 # 12 # # equivalent to Time.now.advance(:years => 2) 13 # 2.years.from_now 14 # 15 # # equivalent to Time.now.advance(:months => 4, :years => 5) 16 # (4.months + 5.years).from_now 8 17 # 9 # Some of these methods are approximations, Ruby's core 18 # While these methods provide precise calculation when used as in the examples above, care 19 # should be taken to note that this is not true if the result of `months', `years', etc is 20 # converted before use: 21 # 22 # # equivalent to 30.days.to_i.from_now 23 # 1.month.to_i.from_now 24 # 25 # # equivalent to 365.25.days.to_f.from_now 26 # 1.year.to_f.from_now 27 # 28 # In such cases, Ruby's core 10 29 # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and 11 30 # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision 12 31 # date and time arithmetic 13 32 module Time 14 33 def seconds 15 self34 Duration.new(self, [[:seconds, self]]) 16 35 end 17 36 alias :second :seconds 18 37 19 38 def minutes 20 self * 6039 Duration.new(self * 60, [[:seconds, self * 60]]) 21 40 end 22 41 alias :minute :minutes 23 42 24 43 def hours 25 self * 60.minutes44 Duration.new(self * 3600, [[:seconds, self * 3600]]) 26 45 end 27 46 alias :hour :hours 28 47 29 48 def days 30 self * 24.hours49 Duration.new(self * 24.hours, [[:days, self]]) 31 50 end 32 51 alias :day :days 33 52 34 53 def weeks 35 self * 7.days54 Duration.new(self * 7.days, [[:days, self * 7]]) 36 55 end 37 56 alias :week :weeks 38 57 39 58 def fortnights 40 self * 2.weeks59 Duration.new(self * 2.weeks, [[:days, self * 14]]) 41 60 end 42 61 alias :fortnight :fortnights 43 62 44 63 def months 45 self * 30.days64 Duration.new(self * 30.days, [[:months, self]]) 46 65 end 47 66 alias :month :months 48 67 49 68 def years 50 (self * 365.25.days).to_i69 Duration.new(self * 365.25.days, [[:years, self]]) 51 70 end 52 71 alias :year :years 53 72 … … 67 86 # Reads best without arguments: 10.minutes.from_now 68 87 alias :from_now :since 69 88 end 89 90 class Duration < Builder::BlankSlate 91 attr_accessor :value, :parts 92 93 def initialize(value, parts) 94 @value, @parts = value, parts 95 end 96 97 def +(other) 98 if Duration === other 99 Duration.new(value + other.value, @parts + other.parts) 100 else 101 Duration.new(value + other, @parts + [[:seconds, other]]) 102 end 103 end 104 105 def -(other) 106 self + (-other) 107 end 108 109 def -@ 110 Duration.new(-value, parts.map { |type,number| [type, -number] }) 111 end 112 113 def is_a?(klass) 114 klass == Duration || super 115 end 116 117 def self.===(other) 118 other.is_a?(Duration) rescue super 119 end 120 121 def since(time = ::Time.now) 122 sum(1, time) 123 end 124 alias :from_now :since 125 126 def ago(time = ::Time.now) 127 sum(-1, time) 128 end 129 alias :until :ago 130 131 def inspect 132 "(#{value}, #{parts.inspect})" 133 end 134 135 protected 136 137 def sum(sign, time = ::Time.now) 138 parts.inject(time) do |t,(type,number)| 139 case t 140 when ::Time 141 if type == :seconds 142 t + (sign * number) 143 else 144 t.advance(type => sign * number) 145 end 146 when ::Date 147 raise ArgumentError, "Adding seconds to a Date does not make sense" if type == :seconds 148 t.advance(type => sign * number) 149 end 150 end 151 end 152 153 private 154 155 def method_missing(method, *args, &block) 156 value.send(method, *args) 157 end 158 end 70 159 end 71 160 end 72 161 end -
lib/active_support/core_ext/time/calculations.rb
old new 5 5 module Calculations 6 6 def self.included(base) #:nodoc: 7 7 base.extend(ClassMethods) 8 9 base.send(:alias_method, :plus_without_duration, :+) 10 base.send(:alias_method, :+, :plus_with_duration) 11 base.send(:alias_method, :minus_without_duration, :-) 12 base.send(:alias_method, :-, :minus_with_duration) 8 13 end 9 14 10 15 module ClassMethods … … 190 195 def tomorrow 191 196 self.since(1.day) 192 197 end 198 199 def plus_with_duration(other) #:nodoc: 200 if ActiveSupport::CoreExtensions::Numeric::Duration === other 201 other.since(self) 202 else 203 plus_without_duration(other) 204 end 205 end 206 207 def minus_with_duration(other) #:nodoc: 208 if ActiveSupport::CoreExtensions::Numeric::Duration === other 209 other.until(self) 210 else 211 minus_without_duration(other) 212 end 213 end 193 214 end 194 215 end 195 216 end -
test/core_ext/numeric_ext_test.rb
old new 34 34 assert seconds.from_now >= now + seconds 35 35 end 36 36 end 37 38 def test_irregular_durations 39 assert_equal @now.advance(:days => 3000), 3000.days.since(@now) 40 assert_equal @now.advance(:months => 1), 1.month.since(@now) 41 assert_equal @now.advance(:months => -1), 1.month.until(@now) 42 assert_equal @now.advance(:years => 20), 20.years.since(@now) 43 end 44 45 def test_duration_addition 46 assert_equal @now.advance(:days => 1, :months => 1), (1.day + 1.month).since(@now) 47 assert_equal @now.advance(:days => 7), (1.week + 5.seconds - 5.seconds).since(@now) 48 assert_equal @now.advance(:years => 2), (4.years - 2.years).since(@now) 49 end 50 51 def test_time_plus_duration 52 assert_equal @now + 8, @now + 8.seconds 53 assert_equal @now + 22.9, @now + 22.9.seconds 54 assert_equal @now.advance(:days => 15), @now + 15.days 55 assert_equal @now.advance(:months => 1), @now + 1.month 56 end 57 58 def test_duration_after_convertion_is_no_longer_accurate 59 assert_equal 30.days.to_i.since(@now), 1.month.to_i.since(@now) 60 assert_equal 365.25.days.to_f.since(@now), 1.year.to_f.since(@now) 61 end 37 62 end 38 63 64 class NumericExtDateTest < Test::Unit::TestCase 65 def setup 66 @today = Date.today 67 end 68 69 def test_date_plus_duration 70 assert_equal @today + 1, @today + 1.day 71 assert_equal @today >> 1, @today + 1.month 72 assert_raises(ArgumentError) { @today + 1.second } 73 end 74 end 75 39 76 class NumericExtSizeTest < Test::Unit::TestCase 40 77 def test_unit_in_terms_of_another 41 78 relationships = {