1 require 'active_support/duration'
3 module ActiveSupport
#:nodoc:
4 module CoreExtensions
#:nodoc:
6 # Enables the use of time calculations within Time itself
8 def self.included(base
) #:nodoc:
9 base
.extend ClassMethods
12 alias_method
:plus_without_duration, :+
13 alias_method
:+, :plus_with_duration
15 alias_method
:minus_without_duration, :-
16 alias_method
:-, :minus_with_duration
18 alias_method
:minus_without_coercion, :-
19 alias_method
:-, :minus_with_coercion
21 alias_method
:compare_without_coercion, :<=>
22 alias_method
:<=>, :compare_with_coercion
26 COMMON_YEAR_DAYS_IN_MONTH
= [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
29 # Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances
34 # Return the number of days in the given month.
35 # If no year is specified, it will use the current year.
36 def days_in_month(month
, year
= now
.year
)
37 return 29 if month
== 2 && ::Date.gregorian_leap
?(year
)
38 COMMON_YEAR_DAYS_IN_MONTH
[month
]
41 # Returns a new Time if requested year can be accommodated by Ruby's Time class
42 # (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
43 # otherwise returns a DateTime
44 def time_with_datetime_fallback(utc_or_local
, year
, month
=1, day
=1, hour
=0, min
=0, sec
=0, usec
=0)
45 ::Time.send(utc_or_local
, year
, month
, day
, hour
, min
, sec
, usec
)
47 offset
= utc_or_local
.to_sym
== :local ? ::DateTime.local_offset
: 0
48 ::DateTime.civil(year
, month
, day
, hour
, min
, sec
, offset
)
51 # Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:utc</tt>.
53 time_with_datetime_fallback(:utc, *args
)
56 # Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:local</tt>.
58 time_with_datetime_fallback(:local, *args
)
62 # Tells whether the Time object's time lies in the past
67 # Tells whether the Time object's time is today
69 self.to_date
== ::Date.current
72 # Tells whether the Time object's time lies in the future
77 # Seconds since midnight: Time.now.seconds_since_midnight
78 def seconds_since_midnight
79 self.to_i
- self.change(:hour => 0).to_i
+ (self.usec
/1.0e+6)
82 # Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
83 # (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
84 # minute is passed, then sec and usec is set to 0.
87 self.utc
? ? :utc_time : :local_time,
88 options
[:year] || self.year
,
89 options
[:month] || self.month
,
90 options
[:day] || self.day
,
91 options
[:hour] || self.hour
,
92 options
[:min] || (options
[:hour] ? 0 : self.min
),
93 options
[:sec] || ((options
[:hour] || options
[:min]) ? 0 : self.sec
),
94 options
[:usec] || ((options
[:hour] || options
[:min] || options
[:sec]) ? 0 : self.usec
)
98 # Uses Date to provide precise Time calculations for years, months, and days.
99 # The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
100 # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
101 # <tt>:minutes</tt>, <tt>:seconds</tt>.
103 unless options
[:weeks].nil?
104 options
[:weeks], partial_weeks
= options
[:weeks].divmod(1)
105 options
[:days] = (options
[:days] || 0) + 7 * partial_weeks
108 unless options
[:days].nil?
109 options
[:days], partial_days
= options
[:days].divmod(1)
110 options
[:hours] = (options
[:hours] || 0) + 24 * partial_days
113 d
= to_date
.advance(options
)
114 time_advanced_by_date
= change(:year => d
.year
, :month => d
.month
, :day => d
.day
)
115 seconds_to_advance
= (options
[:seconds] || 0) + (options
[:minutes] || 0) * 60 + (options
[:hours] || 0) * 3600
116 seconds_to_advance
== 0 ? time_advanced_by_date
: time_advanced_by_date
.since(seconds_to_advance
)
119 # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
124 # Returns a new Time representing the time a number of seconds since the instance time, this is basically a wrapper around
125 # the Numeric extension.
127 f
= seconds
.since(self)
128 if ActiveSupport
::Duration === seconds
131 initial_dst
= self.dst
? ? 1 : 0
132 final_dst
= f
.dst
? ? 1 : 0
133 (seconds
.abs
>= 86400 && initial_dst
!= final_dst
) ? f
+ (initial_dst
- final_dst
).hours
: f
136 self.to_datetime
.since(seconds
)
140 # Returns a new Time representing the time a number of specified months ago
141 def months_ago(months
)
142 advance(:months => -months
)
145 # Returns a new Time representing the time a number of specified months in the future
146 def months_since(months
)
147 advance(:months => months
)
150 # Returns a new Time representing the time a number of specified years ago
152 advance(:years => -years
)
155 # Returns a new Time representing the time a number of specified years in the future
156 def years_since(years
)
157 advance(:years => years
)
160 # Short-hand for years_ago(1)
165 # Short-hand for years_since(1)
171 # Short-hand for months_ago(1)
176 # Short-hand for months_since(1)
181 # Returns a new Time representing the "start" of this week (Monday, 0:00)
182 def beginning_of_week
183 days_to_monday
= self.wday
!=0 ? self.wday-1
: 6
184 (self - days_to_monday
.days
).midnight
186 alias :monday :beginning_of_week
187 alias :at_beginning_of_week :beginning_of_week
189 # Returns a new Time representing the end of this week (Sunday, 23:59:59)
191 days_to_sunday
= self.wday
!=0 ? 7-self.wday
: 0
192 (self + days_to_sunday
.days
).end_of_day
194 alias :at_end_of_week :end_of_week
196 # Returns a new Time representing the start of the given day in next week (default is Monday).
197 def next_week(day
= :monday)
198 days_into_week
= { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6}
199 since(1.week
).beginning_of_week
.since(days_into_week
[day
].day
).change(:hour => 0)
202 # Returns a new Time representing the start of the day (0:00)
204 (self - self.seconds_since_midnight
).change(:usec => 0)
206 alias :midnight :beginning_of_day
207 alias :at_midnight :beginning_of_day
208 alias :at_beginning_of_day :beginning_of_day
210 # Returns a new Time representing the end of the day (23:59:59)
212 change(:hour => 23, :min => 59, :sec => 59)
215 # Returns a new Time representing the start of the month (1st of the month, 0:00)
216 def beginning_of_month
217 #self - ((self.mday-1).days + self.seconds_since_midnight)
218 change(:day => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
220 alias :at_beginning_of_month :beginning_of_month
222 # Returns a new Time representing the end of the month (last day of the month, 0:00)
224 #self - ((self.mday-1).days + self.seconds_since_midnight)
225 last_day
= ::Time.days_in_month( self.month
, self.year
)
226 change(:day => last_day
, :hour => 23, :min => 59, :sec => 59, :usec => 0)
228 alias :at_end_of_month :end_of_month
230 # Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
231 def beginning_of_quarter
232 beginning_of_month
.change(:month => [10, 7, 4, 1].detect
{ |m
| m
<= self.month
})
234 alias :at_beginning_of_quarter :beginning_of_quarter
236 # Returns a new Time representing the end of the quarter (last day of march, june, september, december, 23:59:59)
238 beginning_of_month
.change(:month => [3, 6, 9, 12].detect
{ |m
| m
>= self.month
}).end_of_month
240 alias :at_end_of_quarter :end_of_quarter
242 # Returns a new Time representing the start of the year (1st of january, 0:00)
243 def beginning_of_year
244 change(:month => 1,:day => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
246 alias :at_beginning_of_year :beginning_of_year
248 # Returns a new Time representing the end of the year (31st of december, 23:59:59)
250 change(:month => 12,:day => 31,:hour => 23, :min => 59, :sec => 59)
252 alias :at_end_of_year :end_of_year
254 # Convenience method which returns a new Time representing the time 1 day ago
259 # Convenience method which returns a new Time representing the time 1 day since the instance time
264 def plus_with_duration(other
) #:nodoc:
265 if ActiveSupport
::Duration === other
268 plus_without_duration(other
)
272 def minus_with_duration(other
) #:nodoc:
273 if ActiveSupport
::Duration === other
276 minus_without_duration(other
)
280 # Time#- can also be used to determine the number of seconds between two Time instances.
281 # We're layering on additional behavior so that ActiveSupport::TimeWithZone instances
282 # are coerced into values that Time#- will recognize
283 def minus_with_coercion(other
)
284 other
= other
.comparable_time
if other
.respond_to
?(:comparable_time)
285 minus_without_coercion(other
)
288 # Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
289 # can be chronologically compared with a Time
290 def compare_with_coercion(other
)
291 # if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do <=> comparison
292 other
= other
.comparable_time
if other
.respond_to
?(:comparable_time)
293 if other
.acts_like
?(:date)
294 # other is a Date/DateTime, so coerce self #to_datetime and hand off to DateTime#<=>
295 to_datetime
.compare_without_coercion(other
)
297 compare_without_coercion(other
)