1 require 'date' 2 require 'active_support/duration' 3 require 'active_support/core_ext/time/zones' 4 require 'active_support/core_ext/object/acts_like' 5 6 class Date 7 class << self 8 # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). 9 def yesterday 10 ::Date.today.yesterday 11 end 12 13 # Returns a new Date representing the date 1 day after today (i.e. tomorrow's date). 14 def tomorrow 15 ::Date.today.tomorrow 16 end 17 18 # Returns Time.zone.today when config.time_zone is set, otherwise just returns Date.today. 19 def current 20 ::Time.zone_default ? ::Time.zone.today : ::Date.today 21 end 22 end 23 24 # Tells whether the Date object's date lies in the past 25 def past? 26 self < ::Date.current 27 end 28 29 # Tells whether the Date object's date is today 30 def today? 31 self.to_date == ::Date.current # we need the to_date because of DateTime 32 end 33 34 # Tells whether the Date object's date lies in the future 35 def future? 36 self > ::Date.current 37 end 38 39 # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) 40 # and then subtracts the specified number of seconds 41 def ago(seconds) 42 to_time.since(-seconds) 43 end 44 45 # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) 46 # and then adds the specified number of seconds 47 def since(seconds) 48 to_time.since(seconds) 49 end 50 alias :in :since 51 52 # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) 53 def beginning_of_day 54 to_time 55 end 56 alias :midnight :beginning_of_day 57 alias :at_midnight :beginning_of_day 58 alias :at_beginning_of_day :beginning_of_day 59 60 # Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59) 61 def end_of_day 62 to_time.end_of_day 63 end 64 65 def plus_with_duration(other) #:nodoc: 66 if ActiveSupport::Duration === other 67 other.since(self) 68 else 69 plus_without_duration(other) 70 end 71 end 72 alias_method :plus_without_duration, :+ 73 alias_method :+, :plus_with_duration 74 75 def minus_with_duration(other) #:nodoc: 76 if ActiveSupport::Duration === other 77 plus_with_duration(-other) 78 else 79 minus_without_duration(other) 80 end 81 end 82 alias_method :minus_without_duration, :- 83 alias_method :-, :minus_with_duration 84 85 # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with 86 # any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>. 87 def advance(options) 88 options = options.dup 89 d = self 90 d = d >> options.delete(:years) * 12 if options[:years] 91 d = d >> options.delete(:months) if options[:months] 92 d = d + options.delete(:weeks) * 7 if options[:weeks] 93 d = d + options.delete(:days) if options[:days] 94 d 95 end 96 97 # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter. 98 # 99 # Examples: 100 # 101 # Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1) 102 # Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12) 103 def change(options) 104 ::Date.new( 105 options[:year] || self.year, 106 options[:month] || self.month, 107 options[:day] || self.day 108 ) 109 end 110 111 # Returns a new Date/DateTime representing the time a number of specified months ago 112 def months_ago(months) 113 advance(:months => -months) 114 end 115 116 # Returns a new Date/DateTime representing the time a number of specified months in the future 117 def months_since(months) 118 advance(:months => months) 119 end 120 121 # Returns a new Date/DateTime representing the time a number of specified years ago 122 def years_ago(years) 123 advance(:years => -years) 124 end 125 126 # Returns a new Date/DateTime representing the time a number of specified years in the future 127 def years_since(years) 128 advance(:years => years) 129 end 130 131 # Short-hand for years_ago(1) 132 def last_year 133 years_ago(1) 134 end 135 136 # Short-hand for years_since(1) 137 def next_year 138 years_since(1) 139 end unless method_defined?(:next_year) 140 141 # Short-hand for months_ago(1) 142 def last_month 143 months_ago(1) 144 end 145 146 # Short-hand for months_since(1) 147 def next_month 148 months_since(1) 149 end unless method_defined?(:next_month) 150 151 # Returns a new Date/DateTime representing the "start" of this week (i.e, Monday; DateTime objects will have time set to 0:00) 152 def beginning_of_week 153 days_to_monday = self.wday!=0 ? self.wday-1 : 6 154 result = self - days_to_monday 155 self.acts_like?(:time) ? result.midnight : result 156 end 157 alias :monday :beginning_of_week 158 alias :at_beginning_of_week :beginning_of_week 159 160 # Returns a new Date/DateTime representing the end of this week (Sunday, DateTime objects will have time set to 23:59:59) 161 def end_of_week 162 days_to_sunday = self.wday!=0 ? 7-self.wday : 0 163 result = self + days_to_sunday.days 164 self.acts_like?(:time) ? result.end_of_day : result 165 end 166 alias :at_end_of_week :end_of_week 167 168 # Returns a new Date/DateTime representing the start of the given day in next week (default is Monday). 169 def next_week(day = :monday) 170 days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6} 171 result = (self + 7).beginning_of_week + days_into_week[day] 172 self.acts_like?(:time) ? result.change(:hour => 0) : result 173 end 174 175 # Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00) 176 def beginning_of_month 177 self.acts_like?(:time) ? change(:day => 1,:hour => 0, :min => 0, :sec => 0) : change(:day => 1) 178 end 179 alias :at_beginning_of_month :beginning_of_month 180 181 # Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00) 182 def end_of_month 183 last_day = ::Time.days_in_month( self.month, self.year ) 184 self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day) 185 end 186 alias :at_end_of_month :end_of_month 187 188 # Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00) 189 def beginning_of_quarter 190 beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month }) 191 end 192 alias :at_beginning_of_quarter :beginning_of_quarter 193 194 # Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59) 195 def end_of_quarter 196 beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month 197 end 198 alias :at_end_of_quarter :end_of_quarter 199 200 # Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00) 201 def beginning_of_year 202 self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0, :min => 0, :sec => 0) : change(:month => 1, :day => 1) 203 end 204 alias :at_beginning_of_year :beginning_of_year 205 206 # Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59) 207 def end_of_year 208 self.acts_like?(:time) ? change(:month => 12,:day => 31,:hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31) 209 end 210 alias :at_end_of_year :end_of_year 211 212 # Convenience method which returns a new Date/DateTime representing the time 1 day ago 213 def yesterday 214 self - 1 215 end 216 217 # Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time 218 def tomorrow 219 self + 1 220 end 221 end