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 =
'0.2.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).
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.
- #to_json ⇒ Object
-
#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
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/timeframe.rb', line 121 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 = args.shift.to_date if start_date.nil? and args.any? end_date = args.shift.to_date if end_date.nil? and args.any? 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.
105 106 107 |
# File 'lib/timeframe.rb', line 105 def end_date @end_date end |
#start_date ⇒ Object (readonly)
Returns the value of attribute start_date.
104 105 106 |
# File 'lib/timeframe.rb', line 104 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 |
Instance Method Details
#&(other_timeframe) ⇒ Object
Returns a timeframe representing the intersection of the given timeframes
219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/timeframe.rb', line 219 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
235 236 237 238 |
# File 'lib/timeframe.rb', line 235 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
177 178 179 180 181 |
# File 'lib/timeframe.rb', line 177 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
285 286 287 |
# File 'lib/timeframe.rb', line 285 def as_json(*) iso8601 end |
#covered_by?(*timeframes) ⇒ Boolean
Returns true if the union of the given Timeframes includes the Timeframe
272 273 274 |
# File 'lib/timeframe.rb', line 272 def covered_by?(*timeframes) gaps_left_by(*timeframes).empty? end |
#crop(container) ⇒ Object
Crop a Timeframe by another Timeframe
241 242 243 244 |
# File 'lib/timeframe.rb', line 241 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
296 297 298 299 300 301 302 303 304 |
# File 'lib/timeframe.rb', line 296 def dates dates = [] cursor = start_date while cursor < end_date dates << cursor cursor = cursor.succ end dates end |
#days ⇒ Object
152 153 154 |
# File 'lib/timeframe.rb', line 152 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.
208 209 210 211 212 213 214 215 216 |
# File 'lib/timeframe.rb', line 208 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
306 307 308 309 310 311 312 313 314 |
# File 'lib/timeframe.rb', line 306 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
317 318 319 |
# File 'lib/timeframe.rb', line 317 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
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/timeframe.rb', line 247 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.
185 186 187 |
# File 'lib/timeframe.rb', line 185 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
157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/timeframe.rb', line 157 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?(obj.to_date) 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:
143 144 145 |
# File 'lib/timeframe.rb', line 143 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
290 291 292 |
# File 'lib/timeframe.rb', line 290 def iso8601 "#{start_date.iso8601}/#{end_date.iso8601}" end |
#last_year ⇒ Object
Returns the same Timeframe, only a year earlier
277 278 279 |
# File 'lib/timeframe.rb', line 277 def last_year self.class.new((start_date - 1.year), (end_date - 1.year)) 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
197 198 199 200 201 202 203 204 205 |
# File 'lib/timeframe.rb', line 197 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
171 172 173 174 |
# File 'lib/timeframe.rb', line 171 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
322 323 324 |
# File 'lib/timeframe.rb', line 322 def to # :nodoc: @end_date end |
#to_json ⇒ Object
281 282 283 |
# File 'lib/timeframe.rb', line 281 def to_json(*) iso8601 end |
#year ⇒ Object
Returns the relevant year as a Timeframe
190 191 192 193 |
# File 'lib/timeframe.rb', line 190 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 |