calculations.rb 8.8 KB
Newer Older
1
require 'date'
2
require 'active_support/duration'
3
require 'active_support/core_ext/object/acts_like'
4 5
require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/time/zones'
6

7
class Date
8 9
  DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }

10 11 12
  class << self
    # Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
    def yesterday
13
      ::Date.current.yesterday
14
    end
15 16 17

    # Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
    def tomorrow
18
      ::Date.current.tomorrow
19 20
    end

21
    # Returns Time.zone.today when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns Date.today.
22
    def current
23
      ::Time.zone ? ::Time.zone.today : ::Date.today
24 25 26
    end
  end

27
  # Returns true if the Date object's date lies in the past. Otherwise returns false.
28 29 30 31
  def past?
    self < ::Date.current
  end

32
  # Returns true if the Date object's date is today.
33 34 35 36
  def today?
    self.to_date == ::Date.current # we need the to_date because of DateTime
  end

37
  # Returns true if the Date object's date lies in the future.
38 39 40 41 42
  def future?
    self > ::Date.current
  end

  # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
43
  # and then subtracts the specified number of seconds.
44
  def ago(seconds)
45
    to_time_in_current_zone.since(-seconds)
46 47 48 49 50
  end

  # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
  # and then adds the specified number of seconds
  def since(seconds)
51
    to_time_in_current_zone.since(seconds)
52 53 54 55 56
  end
  alias :in :since

  # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
  def beginning_of_day
57
    to_time_in_current_zone
58 59 60 61 62 63 64
  end
  alias :midnight :beginning_of_day
  alias :at_midnight :beginning_of_day
  alias :at_beginning_of_day :beginning_of_day

  # Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
  def end_of_day
65
    to_time_in_current_zone.end_of_day
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
  end

  def plus_with_duration(other) #:nodoc:
    if ActiveSupport::Duration === other
      other.since(self)
    else
      plus_without_duration(other)
    end
  end
  alias_method :plus_without_duration, :+
  alias_method :+, :plus_with_duration

  def minus_with_duration(other) #:nodoc:
    if ActiveSupport::Duration === other
      plus_with_duration(-other)
    else
      minus_without_duration(other)
    end
  end
  alias_method :minus_without_duration, :-
  alias_method :-, :minus_with_duration

88
  # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
89 90
  # any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
  def advance(options)
91
    options = options.dup
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
    d = self
    d = d >> options.delete(:years) * 12 if options[:years]
    d = d >> options.delete(:months)     if options[:months]
    d = d +  options.delete(:weeks) * 7  if options[:weeks]
    d = d +  options.delete(:days)       if options[:days]
    d
  end

  # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
  #
  # Examples:
  #
  #   Date.new(2007, 5, 12).change(:day => 1)                  # => Date.new(2007, 5, 1)
  #   Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12)
  def change(options)
    ::Date.new(
      options[:year]  || self.year,
      options[:month] || self.month,
      options[:day]   || self.day
    )
  end

114 115 116 117 118
  # Returns a new Date/DateTime representing the time a number of specified weeks ago.
  def weeks_ago(weeks)
    advance(:weeks => -weeks)
  end

119
  # Returns a new Date/DateTime representing the time a number of specified months ago.
120 121 122 123
  def months_ago(months)
    advance(:months => -months)
  end

124
  # Returns a new Date/DateTime representing the time a number of specified months in the future.
125 126 127 128
  def months_since(months)
    advance(:months => months)
  end

129
  # Returns a new Date/DateTime representing the time a number of specified years ago.
130 131 132 133
  def years_ago(years)
    advance(:years => -years)
  end

134
  # Returns a new Date/DateTime representing the time a number of specified years in the future.
135 136 137 138
  def years_since(years)
    advance(:years => years)
  end

139 140
  # Returns number of days to start of this week. Week is assumed to start on
  # +start_day+, default is +:monday+.
141 142 143
  def days_to_week_start(start_day = :monday)
    start_day_number = DAYS_INTO_WEEK[start_day]
    current_day_number = wday != 0 ? wday - 1 : 6
144
    (current_day_number - start_day_number) % 7
145 146
  end

147 148 149
  # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
  # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
  # have their time set to 0:00.
150 151 152
  def beginning_of_week(start_day = :monday)
    days_to_start = days_to_week_start(start_day)
    result = self - days_to_start
153
    acts_like?(:time) ? result.midnight : result
154 155 156
  end
  alias :at_beginning_of_week :beginning_of_week

157 158 159 160 161 162
  # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
  # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00.
  def monday
    beginning_of_week
  end

V
Vijay Dev 已提交
163 164 165
  # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
  # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
  # have their time set to 23:59:59.
166 167 168
  def end_of_week(start_day = :monday)
    days_to_end = 6 - days_to_week_start(start_day)
    result = self + days_to_end.days
169 170 171 172
    self.acts_like?(:time) ? result.end_of_day : result
  end
  alias :at_end_of_week :end_of_week

173 174 175 176 177 178
  # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
  # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59.
  def sunday
    end_of_week
  end

179 180
  # Returns a new +Date+/+DateTime+ representing the given +day+ in the previous
  # week. Default is +:monday+. +DateTime+ objects have their time set to 0:00.
181 182 183 184
  def prev_week(day = :monday)
    result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day]
    self.acts_like?(:time) ? result.change(:hour => 0) : result
  end
185 186 187 188 189 190 191
  alias :last_week :prev_week

  # Alias of prev_month
  alias :last_month :prev_month

  # Alias of prev_year
  alias :last_year :prev_year
192

193
  # Returns a new Date/DateTime representing the start of the given day in next week (default is :monday).
194
  def next_week(day = :monday)
195
    result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day]
196 197 198 199 200
    self.acts_like?(:time) ? result.change(:hour => 0) : result
  end

  # 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)
  def beginning_of_month
201
    self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
  end
  alias :at_beginning_of_month :beginning_of_month

  # 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)
  def end_of_month
    last_day = ::Time.days_in_month( self.month, self.year )
    self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day)
  end
  alias :at_end_of_month :end_of_month

  # 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)
  def beginning_of_quarter
    beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
  end
  alias :at_beginning_of_quarter :beginning_of_quarter

  # 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)
  def end_of_quarter
    beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
  end
  alias :at_end_of_quarter :end_of_quarter

  # Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00)
  def beginning_of_year
226
    self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1)
227 228 229 230 231
  end
  alias :at_beginning_of_year :beginning_of_year

  # Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59)
  def end_of_year
232
    self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
233 234 235 236 237 238 239 240 241 242 243
  end
  alias :at_end_of_year :end_of_year

  # Convenience method which returns a new Date/DateTime representing the time 1 day ago
  def yesterday
    self - 1
  end

  # Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time
  def tomorrow
    self + 1
244
  end
J
Jeremy Kemper 已提交
245
end