Skip to content
This repository
tag: v1.1.6
Fetching contributors…

Cannot retrieve contributors at this time

file 189 lines (160 sloc) 7.243 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Time #:nodoc:
      # Enables the use of time calculations within Time itself
      module Calculations
        def self.append_features(base) #:nodoc:
          super
          base.extend(ClassMethods)
        end

        module ClassMethods
          # Return the number of days in the given month. If a year is given,
          # February will return the correct number of days for leap years.
          # Otherwise, this method will always report February as having 28
          # days.
          def days_in_month(month, year=nil)
            if month == 2
              !year.nil? && (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) ? 29 : 28
            elsif month <= 7
              month % 2 == 0 ? 30 : 31
            else
              month % 2 == 0 ? 31 : 30
            end
          end
        end

        # Seconds since midnight: Time.now.seconds_since_midnight
        def seconds_since_midnight
          self.hour.hours + self.min.minutes + self.sec + (self.usec/1.0e+6)
        end
            
        # Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
        # (hour, minute, 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(
            self.utc? ? :utc : :local,
            options[:year] || self.year,
            options[:month] || self.month,
            options[:mday] || self.mday,
            options[:hour] || self.hour,
            options[:min] || (options[:hour] ? 0 : self.min),
            options[:sec] || ((options[:hour] || options[:min]) ? 0 : self.sec),
            options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : self.usec)
          )
        end
        
        # Uses Date to provide precise Time calculations for years, months, and days. The +options+ parameter takes a hash with
        # any of these keys: :months, :days, :years.
        def advance(options)
          d = ::Date.new(year + (options.delete(:years) || 0), month, day)
          d = d >> options.delete(:months) if options[:months]
          d = d + options.delete(:days) if options[:days]
          change(options.merge(:year => d.year, :month => d.month, :mday => d.day))
        end

        # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
        # Do not use this method in combination with x.months, use months_ago instead!
        def ago(seconds)
          seconds.until(self)
        end

        # Returns a new Time representing the time a number of seconds since the instance time, this is basically a wrapper around
        #the Numeric extension. Do not use this method in combination with x.months, use months_since instead!
        def since(seconds)
          seconds.since(self)
        end
        alias :in :since

        # Returns a new Time representing the time a number of specified months ago
        def months_ago(months)
          months_since(-months)
        end

        def months_since(months)
          year, month, mday = self.year, self.month, self.mday

          month += months

          # in case months is negative
          while month < 1
            month += 12
            year -= 1
          end

          # in case months is positive
          while month > 12
            month -= 12
            year += 1
          end

          max = ::Time.days_in_month(month, year)
          mday = max if mday > max

          change(:year => year, :month => month, :mday => mday)
        end

        # Returns a new Time representing the time a number of specified years ago
        def years_ago(years)
          change(:year => self.year - years)
        end
        
        def years_since(years)
          change(:year => self.year + years)
        end

        # Short-hand for years_ago(1)
        def last_year
          years_ago(1)
        end
        
        # Short-hand for years_since(1)
        def next_year
          years_since(1)
        end


        # Short-hand for months_ago(1)
        def last_month
          months_ago(1)
        end

        # Short-hand for months_since(1)
        def next_month
          months_since(1)
        end
        
        # Returns a new Time representing the "start" of this week (Monday, 0:00)
        def beginning_of_week
          days_to_monday = self.wday!=0 ? self.wday-1 : 6
          (self - days_to_monday.days).midnight
        end
        alias :monday :beginning_of_week
        alias :at_beginning_of_week :beginning_of_week
        
        # Returns a new Time representing the start of the given day in next week (default is Monday).
        def next_week(day = :monday)
          days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6}
          since(1.week).beginning_of_week.since(days_into_week[day].day).change(:hour => 0)
        end
        
        # Returns a new Time representing the start of the day (0:00)
        def beginning_of_day
          (self - self.seconds_since_midnight).change(:usec => 0)
        end
        alias :midnight :beginning_of_day
        alias :at_midnight :beginning_of_day
        alias :at_beginning_of_day :beginning_of_day
        
        # Returns a new Time representing the start of the month (1st of the month, 0:00)
        def beginning_of_month
          #self - ((self.mday-1).days + self.seconds_since_midnight)
          change(:mday => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
        end
        alias :at_beginning_of_month :beginning_of_month
        
        # Returns a new Time representing the end of the month (last day of the month, 0:00)
        def end_of_month
          #self - ((self.mday-1).days + self.seconds_since_midnight)
          last_day = ::Time.days_in_month( self.month, self.year )
          change(:mday => last_day,:hour => 0, :min => 0, :sec => 0, :usec => 0)
        end
        alias :at_end_of_month :end_of_month

        # Returns a new Time representing the start of the quarter (1st of january, april, july, october, 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 Time representing the start of the year (1st of january, 0:00)
        def beginning_of_year
          change(:month => 1,:mday => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
        end
        alias :at_beginning_of_year :beginning_of_year
        
        # Convenience method which returns a new Time representing the time 1 day ago
        def yesterday
          self.ago(1.day)
        end
        
        # Convenience method which returns a new Time representing the time 1 day since the instance time
        def tomorrow
          self.since(1.day)
        end
      end
    end
  end
end
Something went wrong with that request. Please try again.