Class: Sniff::Timeframe

Inherits:
Object
  • Object
show all
Defined in:
lib/sniff/timeframe.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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

Raises:

  • (ArgumentError)


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/sniff/timeframe.rb', line 27

def initialize(*args)
  options = args.extract_options!

  if month = options[:month]
    month = Date.parse(month).month if month.is_a? String
    year = options[:year] || Time.zone.today.year
    from = Date.new(year, month, 1)
    to   = from.next_month
  elsif year = options[:year]
    from = Date.new(year, 1, 1)
    to   = Date.new(year+1, 1, 1)
  end

  from ||= args.shift.andand.to_date
  to ||= args.shift.andand.to_date

  raise ArgumentError, "Please supply a start and end date, `#{args.map(&:inspect).to_sentence}' is not enough" if from.nil? or to.nil?
  raise ArgumentError, "Start date #{from} should be earlier than end date #{to}" if from > to
  raise ArgumentError, 'Timeframes that cross year boundaries are dangerous' unless options[:skip_year_boundary_crossing_check] or from.year == to.yesterday.year or from == to

  @from, @to = from, to
end

Instance Attribute Details

#fromObject

Returns the value of attribute from.



11
12
13
# File 'lib/sniff/timeframe.rb', line 11

def from
  @from
end

#toObject

Returns the value of attribute to.



11
12
13
# File 'lib/sniff/timeframe.rb', line 11

def to
  @to
end

Class Method Details

.constrained_new(from, to, constraint) ⇒ Object

Raises:

  • (ArgumentError)


223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/sniff/timeframe.rb', line 223

def constrained_new(from, to, constraint)
  raise ArgumentError, 'Need Date, Date, Timeframe as args' unless from.is_a? Date and to.is_a? Date and constraint.is_a? Timeframe
  raise ArgumentError, "Start date #{from} should be earlier than end date #{to}" if from > to
  if to <= constraint.from or from >= constraint.to
    new constraint.from, constraint.from
  elsif from.year == to.yesterday.year
    new(from, to) & constraint
  elsif from.year < constraint.from.year and constraint.from.year < to.yesterday.year
    constraint
  else
    new [constraint.from, from].max, [constraint.to, to].min
  end
end

.mid(number) ⇒ Object

create a multiyear timeframe +/- number of years around today



244
245
246
247
248
# File 'lib/sniff/timeframe.rb', line 244

def mid(number)
  from = Time.zone.today - number.years
  to = Time.zone.today + number.years
  multiyear from, to
end

.multiyear(from, to) ⇒ Object



237
238
239
240
241
# File 'lib/sniff/timeframe.rb', line 237

def multiyear(from, to)
  from = Date.parse(from) if from.is_a?(String)
  to = Date.parse(to) if to.is_a?(String)
  new from, to, :skip_year_boundary_crossing_check => true
end

.this_yearObject



219
220
221
# File 'lib/sniff/timeframe.rb', line 219

def this_year
  new :year => Time.now.year
end

Instance Method Details

#&(other_timeframe) ⇒ Object

Returns a timeframe representing the intersection of the timeframes



163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/sniff/timeframe.rb', line 163

def &(other_timeframe)
  this_timeframe = self
  if other_timeframe == this_timeframe
    this_timeframe
  elsif this_timeframe.from > other_timeframe.from and this_timeframe.to < other_timeframe.to
    this_timeframe
  elsif other_timeframe.from > this_timeframe.from and other_timeframe.to < this_timeframe.to
    other_timeframe
  elsif this_timeframe.from >= other_timeframe.to or this_timeframe.to <= other_timeframe.from
    nil
  else
    Timeframe.new [this_timeframe.from, other_timeframe.from].max, [this_timeframe.to, other_timeframe.to].min, :skip_year_boundary_crossing_check => true
  end
end

#/(other_timeframe) ⇒ Object

Returns a fraction of another Timeframe

Raises:

  • (ArgumentError)


179
180
181
182
# File 'lib/sniff/timeframe.rb', line 179

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



94
95
96
97
98
# File 'lib/sniff/timeframe.rb', line 94

def ==(other)
  # puts "checking to see if #{self} is equal to #{other}" if Emitter::DEBUG
  return false unless other.is_a?(Timeframe)
  from == other.from and to == other.to
end

#covered_by?(*timeframes) ⇒ Boolean

Returns:

  • (Boolean)


210
211
212
# File 'lib/sniff/timeframe.rb', line 210

def covered_by?(*timeframes)
  gaps_left_by(*timeframes).empty?
end

#crop(container) ⇒ Object

Raises:

  • (ArgumentError)


184
185
186
187
# File 'lib/sniff/timeframe.rb', line 184

def crop(container)
  raise ArgumentError, 'You can only crop a timeframe by another timeframe' unless container.is_a? Timeframe
  self.class.new [from, container.from].max, [to, container.to].min
end

#daysObject

The number of days in the timeframe

Timeframe.new(Date.new(2007, 11, 1), Date.new(2007, 12, 1)).days #=> 30
Timeframe.new(:month => 1).days #=> 31
Timeframe.new(:year => 2004).days #=> 366


59
60
61
# File 'lib/sniff/timeframe.rb', line 59

def days
  (to - from).to_i
end

#ending_no_later_than(date) ⇒ Object

multiyear safe



152
153
154
155
156
157
158
159
160
# File 'lib/sniff/timeframe.rb', line 152

def ending_no_later_than(date)
  if to < date
    self
  elsif from >= date
    nil
  else
    Timeframe.multiyear from, date
  end
end

#full_month_subtimeframesObject

multiyear safe



133
134
135
# File 'lib/sniff/timeframe.rb', line 133

def full_month_subtimeframes
  month_subtimeframes.map { |st| Timeframe.new(:year => st.from.year, :month => st.from.month) }
end

#full_year_subtimeframesObject

multiyear safe



145
146
147
148
149
# File 'lib/sniff/timeframe.rb', line 145

def full_year_subtimeframes
  (from.year..to.yesterday.year).map do |year|
    Timeframe.new :year => year
  end
end

#gaps_left_by(*timeframes) ⇒ Object



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/sniff/timeframe.rb', line 189

def gaps_left_by(*timeframes)
  # remove extraneous timeframes
  timeframes.reject! { |t| t.to <= from }
  timeframes.reject! { |t| t.from >= to }
  
  # 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.from <=> y.from }
  
  timeframes.collect(&:to).unshift(from).ykk(timeframes.collect(&:from).push(to)) do |gap|
    Timeframe.new(*gap) if gap[1] > gap[0]
  end.compact
end

#hashObject Also known as: to_param

Calculates a hash value for the Timeframe, used for equality checking and Hash lookups. This needs to be an integer or else it won’t use #eql?



103
104
105
# File 'lib/sniff/timeframe.rb', line 103

def hash
  from.hash + to.hash
end

#include?(obj) ⇒ Boolean

Returns true when the date is included in this Timeframe

Returns:

  • (Boolean)


75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/sniff/timeframe.rb', line 75

def include?(obj)
  # puts "checking to see if #{date} is between #{from} and #{to}" if Emitter::DEBUG
  case obj
  when Date
    (from...to).include?(obj)
  when Time
    # (from...to).include?(obj.to_date)
    raise "this wasn't previously supported, but it could be"
  when Timeframe
    from <= obj.from and to >= obj.to
  end
end

#inspectObject



50
51
52
# File 'lib/sniff/timeframe.rb', line 50

def inspect
  "<Timeframe(#{object_id}) #{days} days starting #{from} ending #{to}>"
end

#last_yearObject



214
215
216
# File 'lib/sniff/timeframe.rb', line 214

def last_year
  self.class.new((from - 1.year), (to - 1.year))
end

#month_subtimeframesObject

multiyear safe



124
125
126
127
128
129
130
# File 'lib/sniff/timeframe.rb', line 124

def month_subtimeframes
  (from.year..to.yesterday.year).map do |year|
    (1..12).map do |month|
      Timeframe.new(:year => year, :month => month) & self
    end
  end.flatten.compact
end

#monthsObject

Returns an array of month-long subtimeframes TODO: rename to month_subtimeframes

Raises:

  • (ArgumentError)


110
111
112
113
114
115
# File 'lib/sniff/timeframe.rb', line 110

def months
  raise ArgumentError, "Please only provide whole-month timeframes to Timeframe#months" unless from.day == 1 and to.day == 1
  raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#months' unless from.year == to.yesterday.year
  year = from.year # therefore this only works in the from year
  (from.month..to.yesterday.month).map { |m| Timeframe.new :month => m, :year => year }
end

#proper_include?(other_timeframe) ⇒ Boolean

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


88
89
90
91
# File 'lib/sniff/timeframe.rb', line 88

def proper_include?(other_timeframe)
  raise ArgumentError, 'Proper inclusion only makes sense when testing other Timeframes' unless other_timeframe.is_a? Timeframe
  (from < other_timeframe.from) and (to > other_timeframe.to)
end

#to_sObject

Returns a string representation of the timeframe



64
65
66
67
68
69
70
71
72
# File 'lib/sniff/timeframe.rb', line 64

def to_s
  if [from.day, from.month, to.day, to.month].uniq == [1]
    from.year.to_s
  elsif from.day == 1 and to.day == 1 and to.month - from.month == 1
    "#{Date::MONTHNAMES[from.month]} #{from.year}"
  else
    "the period from #{from.strftime('%d %B')} to #{to.yesterday.strftime('%d %B %Y')}"
  end
end

#yearObject

Returns the relevant year as a Timeframe

Raises:

  • (ArgumentError)


118
119
120
121
# File 'lib/sniff/timeframe.rb', line 118

def year
  raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#year' unless from.year == to.yesterday.year
  Timeframe.new :year => from.year
end

#year_subtimeframesObject

multiyear safe



138
139
140
141
142
# File 'lib/sniff/timeframe.rb', line 138

def year_subtimeframes
  (from.year..to.yesterday.year).map do |year|
    Timeframe.new(:year => year) & self
  end
end