Class: Timeframe
- Inherits:
-
Object
- Object
- Timeframe
- Defined in:
- lib/timeframe.rb,
lib/timeframe/version.rb,
lib/timeframe/iso_8601.rb
Overview
Encapsulates a timeframe between two dates. The dates provided to the class are always until the last date. That means that the last date is excluded.
# from 2007-10-01 00:00:00.000 to 2007-10-31 23:59:59.999
Timeframe.new(Date(2007,10,1), Date(2007,11,1))
# and holds 31 days
Timeframe.new(Date(2007,10,1), Date(2007,11,1)).days #=> 31
Defined Under Namespace
Modules: Iso8601
Constant Summary collapse
- VERSION =
'1.0.0'
Instance Attribute Summary collapse
-
#end_date ⇒ Object
readonly
Returns the value of attribute end_date.
-
#start_date ⇒ Object
readonly
Returns the value of attribute start_date.
Class Method Summary collapse
-
.constrained_new(start_date, end_date, constraint) ⇒ Object
Construct a new Timeframe, but constrain it by another.
-
.from_hash(hsh) ⇒ Object
Construct a new Timeframe from a hash with keys startDate and endDate.
-
.from_iso8601(str) ⇒ Object
Construct a new Timeframe by parsing an ISO 8601 time interval string en.wikipedia.org/wiki/ISO_8601#Time_intervals.
-
.from_year(year) ⇒ Object
Construct a new Timeframe from a year.
-
.mid(number) ⇒ Object
Create a timeframe +/- number of years around today.
-
.multiyear(*args) ⇒ Object
Deprecated.
-
.parse(input) ⇒ Object
(also: interval, from_json)
Automagically parse a Timeframe from either a String or a Hash.
-
.this_year ⇒ Object
Shortcut method to return the Timeframe representing the current year (as defined by Time.now).
- .to_date(v) ⇒ Object
Instance Method Summary collapse
-
#&(other_timeframe) ⇒ Object
Returns a timeframe representing the intersection of the given timeframes.
-
#/(other_timeframe) ⇒ Object
Returns the fraction (as a Float) of another Timeframe that this Timeframe represents.
-
#==(other) ⇒ Object
(also: #eql?)
Returns true when this timeframe is equal to the other timeframe.
- #as_json ⇒ Object
-
#covered_by?(*timeframes) ⇒ Boolean
Returns true if the union of the given Timeframes includes the Timeframe.
-
#crop(container) ⇒ Object
Crop a Timeframe by another Timeframe.
- #dates ⇒ Object
-
#days ⇒ Object
The number of days in the timeframe.
-
#ending_no_later_than(date) ⇒ Object
Crop a Timeframe to end no later than the provided date.
- #first_days_of_months ⇒ Object
-
#from ⇒ Object
Deprecated.
-
#gaps_left_by(*timeframes) ⇒ Object
Returns an array of Timeframes representing the gaps left in the Timeframe after removing all given Timeframes.
-
#hash ⇒ Object
Calculates a hash value for the Timeframe, used for equality checking and Hash lookups.
-
#include?(obj) ⇒ Boolean
Returns true when a Date or other Timeframe is included in this Timeframe.
-
#initialize(*args) ⇒ Timeframe
constructor
Creates a new instance of Timeframe.
-
#inspect ⇒ Object
:nodoc:.
-
#iso8601 ⇒ Object
(also: #to_s, #to_param)
An ISO 8601 “time interval” like YYYY-MM-DD/YYYY-MM-DD.
-
#last_year ⇒ Object
Returns the same Timeframe, only a year earlier.
-
#months ⇒ Object
Returns an Array of month-long Timeframes.
-
#proper_include?(other_timeframe) ⇒ Boolean
Returns true when the parameter Timeframe is properly included in the Timeframe.
-
#to ⇒ Object
Deprecated.
-
#year ⇒ Object
Returns the relevant year as a Timeframe.
Constructor Details
#initialize(*args) ⇒ Timeframe
Creates a new instance of Timeframe. You can either pass a start and end Date or a Hash with named arguments, with the following options:
<tt>:month</tt>: Start date becomes the first day of this month, and the end date becomes the first day of
the next month. If no <tt>:year</tt> is specified, the current year is used.
<tt>:year</tt>: Start date becomes the first day of this year, and the end date becomes the first day of the
next year.
Examples:
Timeframe.new Date.new(2007, 2, 1), Date.new(2007, 4, 1) # February and March
Timeframe.new :year => 2004 # The year 2004
Timeframe.new :month => 4 # April
Timeframe.new :year => 2004, :month => 2 # Feburary 2004
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/timeframe.rb', line 134 def initialize(*args) = args. if month = [:month] month = Date.parse(month).month if month.is_a? String year = [:year] || Date.today.year start_date = Date.new(year, month, 1) end_date = start_date.next_month elsif year = [:year] start_date = Date.new(year, 1, 1) end_date = Date.new(year+1, 1, 1) end start_date ||= Timeframe.to_date(args[0]) end_date ||= Timeframe.to_date(args[1]) raise ArgumentError, "Please supply a start and end date, `#{args.map(&:inspect).to_sentence}' is not enough" if start_date.nil? or end_date.nil? raise ArgumentError, "Start date #{start_date} should be earlier than end date #{end_date}" if start_date > end_date @start_date, @end_date = start_date, end_date end |
Instance Attribute Details
#end_date ⇒ Object (readonly)
Returns the value of attribute end_date.
118 119 120 |
# File 'lib/timeframe.rb', line 118 def end_date @end_date end |
#start_date ⇒ Object (readonly)
Returns the value of attribute start_date.
117 118 119 |
# File 'lib/timeframe.rb', line 117 def start_date @start_date end |
Class Method Details
.constrained_new(start_date, end_date, constraint) ⇒ Object
Construct a new Timeframe, but constrain it by another
23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/timeframe.rb', line 23 def constrained_new(start_date, end_date, constraint) start_date, end_date = make_dates start_date, end_date raise ArgumentError, 'Constraint must be a Timeframe' unless constraint.is_a? Timeframe raise ArgumentError, "Start date #{start_date} should be earlier than end date #{end_date}" if start_date > end_date if end_date <= constraint.start_date or start_date >= constraint.end_date new constraint.start_date, constraint.start_date elsif start_date.year == end_date.yesterday.year new(start_date, end_date) & constraint elsif start_date.year < constraint.start_date.year and constraint.start_date.year < end_date.yesterday.year constraint else new [constraint.start_date, start_date].max, [constraint.end_date, end_date].min end end |
.from_hash(hsh) ⇒ Object
Construct a new Timeframe from a hash with keys startDate and endDate
59 60 61 62 |
# File 'lib/timeframe.rb', line 59 def from_hash(hsh) hsh = hsh.symbolize_keys new hsh[:startDate], hsh[:endDate] end |
.from_iso8601(str) ⇒ Object
Construct a new Timeframe by parsing an ISO 8601 time interval string en.wikipedia.org/wiki/ISO_8601#Time_intervals
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/timeframe.rb', line 47 def from_iso8601(str) delimiter = str.include?('/') ? '/' : '--' a_raw, b_raw = str.split delimiter if a_raw.blank? or b_raw.blank? raise ArgumentError, "Interval must be specified according to ISO 8601 <start>/<end>, <start>/<duration>, or <duration>/<end>." end a = Iso8601::A.new a_raw b = Iso8601::B.new b_raw new a.to_time(b), b.to_time(a) end |
.from_year(year) ⇒ Object
Construct a new Timeframe from a year.
65 66 67 |
# File 'lib/timeframe.rb', line 65 def from_year(year) new :year => year.to_i end |
.mid(number) ⇒ Object
Create a timeframe +/- number of years around today
39 40 41 42 43 |
# File 'lib/timeframe.rb', line 39 def mid(number) start_date = Time.now.today - number.years end_date = Time.now.today + number.years new start_date, end_date end |
.multiyear(*args) ⇒ Object
Deprecated
93 94 95 |
# File 'lib/timeframe.rb', line 93 def multiyear(*args) # :nodoc: new *args end |
.parse(input) ⇒ Object Also known as: interval, from_json
Automagically parse a Timeframe from either a String or a Hash
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/timeframe.rb', line 70 def parse(input) case input when ::Integer from_year input when ::Hash from_hash input when ::String str = input.strip if str.start_with?('{') from_hash MultiJson.load(str) elsif input =~ /\A\d\d\d\d\z/ from_year input else from_iso8601 str end else raise ArgumentError, "Must be String or Hash" end end |
.this_year ⇒ Object
Shortcut method to return the Timeframe representing the current year (as defined by Time.now)
18 19 20 |
# File 'lib/timeframe.rb', line 18 def this_year new :year => Time.now.year end |
.to_date(v) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/timeframe.rb', line 97 def to_date(v) case v when NilClass nil when Date v when Time v.to_date else Date.parse v end end |
Instance Method Details
#&(other_timeframe) ⇒ Object
Returns a timeframe representing the intersection of the given timeframes
232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/timeframe.rb', line 232 def &(other_timeframe) this_timeframe = self if other_timeframe == this_timeframe this_timeframe elsif this_timeframe.start_date > other_timeframe.start_date and this_timeframe.end_date < other_timeframe.end_date this_timeframe elsif other_timeframe.start_date > this_timeframe.start_date and other_timeframe.end_date < this_timeframe.end_date other_timeframe elsif this_timeframe.start_date >= other_timeframe.end_date or this_timeframe.end_date <= other_timeframe.start_date nil else Timeframe.new [this_timeframe.start_date, other_timeframe.start_date].max, [this_timeframe.end_date, other_timeframe.end_date].min end end |
#/(other_timeframe) ⇒ Object
Returns the fraction (as a Float) of another Timeframe that this Timeframe represents
248 249 250 251 |
# File 'lib/timeframe.rb', line 248 def /(other_timeframe) raise ArgumentError, 'You can only divide a Timeframe by another Timeframe' unless other_timeframe.is_a? Timeframe self.days.to_f / other_timeframe.days.to_f end |
#==(other) ⇒ Object Also known as: eql?
Returns true when this timeframe is equal to the other timeframe
190 191 192 193 194 |
# File 'lib/timeframe.rb', line 190 def ==(other) # puts "checking to see if #{self} is equal to #{other}" if Emitter::DEBUG return false unless other.is_a?(Timeframe) start_date == other.start_date and end_date == other.end_date end |
#as_json ⇒ Object
297 298 299 |
# File 'lib/timeframe.rb', line 297 def as_json(*) iso8601 end |
#covered_by?(*timeframes) ⇒ Boolean
Returns true if the union of the given Timeframes includes the Timeframe
285 286 287 |
# File 'lib/timeframe.rb', line 285 def covered_by?(*timeframes) gaps_left_by(*timeframes).empty? end |
#crop(container) ⇒ Object
Crop a Timeframe by another Timeframe
254 255 256 257 |
# File 'lib/timeframe.rb', line 254 def crop(container) raise ArgumentError, 'You can only crop a timeframe by another timeframe' unless container.is_a? Timeframe self.class.new [start_date, container.start_date].max, [end_date, container.end_date].min end |
#dates ⇒ Object
308 309 310 311 312 313 314 315 316 |
# File 'lib/timeframe.rb', line 308 def dates dates = [] cursor = start_date while cursor < end_date dates << cursor cursor = cursor.succ end dates end |
#days ⇒ Object
165 166 167 |
# File 'lib/timeframe.rb', line 165 def days (end_date - start_date).to_i end |
#ending_no_later_than(date) ⇒ Object
Crop a Timeframe to end no later than the provided date.
221 222 223 224 225 226 227 228 229 |
# File 'lib/timeframe.rb', line 221 def ending_no_later_than(date) if end_date < date self elsif start_date >= date nil else Timeframe.new start_date, date end end |
#first_days_of_months ⇒ Object
318 319 320 321 322 323 324 325 326 |
# File 'lib/timeframe.rb', line 318 def first_days_of_months dates = [] cursor = start_date.beginning_of_month while cursor < end_date dates << cursor cursor = cursor >> 1 end dates end |
#from ⇒ Object
Deprecated
329 330 331 |
# File 'lib/timeframe.rb', line 329 def from # :nodoc: @start_date end |
#gaps_left_by(*timeframes) ⇒ Object
Returns an array of Timeframes representing the gaps left in the Timeframe after removing all given Timeframes
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/timeframe.rb', line 260 def gaps_left_by(*timeframes) # remove extraneous timeframes timeframes.reject! { |t| t.end_date <= start_date } timeframes.reject! { |t| t.start_date >= end_date } # crop timeframes timeframes.map! { |t| t.crop self } # remove proper subtimeframes timeframes.reject! { |t| timeframes.detect { |u| u.proper_include? t } } # escape return [self] if timeframes.empty? timeframes.sort! { |x, y| x.start_date <=> y.start_date } a = [ start_date ] + timeframes.collect(&:end_date) b = timeframes.collect(&:start_date) + [ end_date ] a.zip(b).map do |gap| Timeframe.new(*gap) if gap[1] > gap[0] end.compact end |
#hash ⇒ Object
Calculates a hash value for the Timeframe, used for equality checking and Hash lookups.
198 199 200 |
# File 'lib/timeframe.rb', line 198 def hash start_date.hash + end_date.hash end |
#include?(obj) ⇒ Boolean
Returns true when a Date or other Timeframe is included in this Timeframe
170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/timeframe.rb', line 170 def include?(obj) # puts "checking to see if #{date} is between #{start_date} and #{end_date}" if Emitter::DEBUG case obj when Date (start_date...end_date).include?(obj) when Time # (start_date...end_date).include?(Date.parse(obj)) raise "this wasn't previously supported, but it could be" when Timeframe start_date <= obj.start_date and end_date >= obj.end_date end end |
#inspect ⇒ Object
:nodoc:
156 157 158 |
# File 'lib/timeframe.rb', line 156 def inspect # :nodoc: "<Timeframe(#{object_id}) #{days} days starting #{start_date} ending #{end_date}>" end |
#iso8601 ⇒ Object Also known as: to_s, to_param
An ISO 8601 “time interval” like YYYY-MM-DD/YYYY-MM-DD
302 303 304 |
# File 'lib/timeframe.rb', line 302 def iso8601 "#{start_date.iso8601}/#{end_date.iso8601}" end |
#last_year ⇒ Object
Returns the same Timeframe, only a year earlier
290 291 292 293 294 295 |
# File 'lib/timeframe.rb', line 290 def last_year self.class.new( Date.new(start_date.year - 1, start_date.month, start_date.day), Date.new(end_date.year - 1, end_date.month, end_date.day) ) end |
#months ⇒ Object
Returns an Array of month-long Timeframes. Partial months are not included by default. stackoverflow.com/questions/1724639/iterate-every-month-with-date-objects
210 211 212 213 214 215 216 217 218 |
# File 'lib/timeframe.rb', line 210 def months memo = [] ptr = start_date while ptr <= end_date do memo.push(Timeframe.new(:year => ptr.year, :month => ptr.month) & self) ptr = ptr >> 1 end memo.flatten.compact end |
#proper_include?(other_timeframe) ⇒ Boolean
Returns true when the parameter Timeframe is properly included in the Timeframe
184 185 186 187 |
# File 'lib/timeframe.rb', line 184 def proper_include?(other_timeframe) raise ArgumentError, 'Proper inclusion only makes sense when testing other Timeframes' unless other_timeframe.is_a? Timeframe (start_date < other_timeframe.start_date) and (end_date > other_timeframe.end_date) end |
#to ⇒ Object
Deprecated
334 335 336 |
# File 'lib/timeframe.rb', line 334 def to # :nodoc: @end_date end |
#year ⇒ Object
Returns the relevant year as a Timeframe
203 204 205 206 |
# File 'lib/timeframe.rb', line 203 def year raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#year' unless start_date.year == end_date.yesterday.year Timeframe.new :year => start_date.year end |