Class: Period

Inherits:
Object
  • Object
show all
Includes:
Comparable, Enumerable
Defined in:
lib/fat_core/period.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Enumerable

#groups_of

Constructor Details

#initialize(first, last) ⇒ Period

Returns a new instance of Period.

[View source]

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
# File 'lib/fat_core/period.rb', line 8

def initialize(first, last)
  case first
  when String
    begin
      first = Date.parse(first)
    rescue ArgumentError => ex
      if ex.message =~ /invalid date/
        raise ArgumentError, "you gave an invalid date '#{first}'"
      else
        raise
      end
    end
  when Date
    first = first
  else
    raise ArgumentError, "use Date or String to initialize Period"
  end

  case last
  when String
    begin
      last = Date.parse(last)
    rescue ArgumentError => ex
      if ex.message =~ /invalid date/
        raise ArgumentError, "you gave an invalid date '#{last}'"
      else
        raise
      end
    end
  when Date
    last = last
  else
    raise ArgumentError, "use Date or String to initialize Period"
  end

  @first = first
  @last = last
  if @first > @last
    raise ArgumentError, "Period's first date is later than its last date"
  end
end

Instance Attribute Details

#firstObject

Returns the value of attribute first.


6
7
8
# File 'lib/fat_core/period.rb', line 6

def first
  @first
end

#lastObject

Returns the value of attribute last.


6
7
8
# File 'lib/fat_core/period.rb', line 6

def last
  @last
end

Class Method Details

.chunk_sym_to_days(sym) ⇒ Object

[View source]

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/fat_core/period.rb', line 101

def self.chunk_sym_to_days(sym)
  case sym
  when :day
    1
  when :week
    7
  when :biweek
    14
  when :semimonth
    15
  when :month
    30
  when :bimonth
    60
  when :quarter
    90
  when :year
    365
  when :irregular
    30
  else
    raise ArgumentError, "unknown chunk sym '#{sym}'"
  end
end

.chunk_sym_to_max_days(sym) ⇒ Object

The largest number of days possible in each chunk

[View source]

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/fat_core/period.rb', line 127

def self.chunk_sym_to_max_days(sym)
  case sym
  when :semimonth
    16
  when :month
    31
  when :bimonth
    62
  when :quarter
    92
  when :year
    366
  else
    chunk_sym_to_days(sym)
  end
end

.chunk_symsObject

[View source]

96
97
98
99
# File 'lib/fat_core/period.rb', line 96

def self.chunk_syms
  [:day, :week, :biweek, :semimonth, :month, :bimonth,
   :quarter, :year, :irregular]
end

.days_to_chunk_sym(days) ⇒ Object

This is only used for inferring statement frequency based on the number of days between statements, so it will not consider all possible chunks, only :year, :quarter, :month, and :week. And distinguishing between :semimonth and :biweek is impossible anyway. Since statement dates can bounce around quite a bit in my experience, this is really fuzzy. For example, one of my banks does monthly statements “around” the 10th of the month, but the 10th can get pushed off by holidays, weekends, etc., so a “quarter” here is much broader than the calendar definition. Ditto for the others, but since statements are most likely monthly, we default to :month.

[View source]

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/fat_core/period.rb', line 154

def self.days_to_chunk_sym(days)
  case days
  when 356..376
    :year
  when 86..96
    :quarter
  when 26..33
    :month
  when 7
    :week
  when 1
    :day
  else
    :irregular
  end
end

.parse_spec(from = 'today', to = nil) ⇒ Object

Return a period based on two date specs (see Date.parse_spec), a ”‘from’ and a ‘to’ spec. If the to-spec is not given or is nil, the from-spec is used for both the from- and to-spec. If no from-spec is given, return today as the period.

[View source]

54
55
56
57
58
# File 'lib/fat_core/period.rb', line 54

def self.parse_spec(from = 'today', to = nil)
  to ||= from
  from ||= to
  Period.new(Date.parse_spec(from, :from), Date.parse_spec(to, :to))
end

Instance Method Details

#<=>(other) ⇒ Object

[View source]

187
188
189
# File 'lib/fat_core/period.rb', line 187

def <=>(other)
  [first, size] <=> [other.first, other.size]
end

#==(other) ⇒ Object

[View source]

183
184
185
# File 'lib/fat_core/period.rb', line 183

def ==(other)
  first == other.first && last == other.last
end

#chunk_nameObject

Name for a period not necessarily ending on calendar boundaries. For example, in reporting reconciliation, we want the period from Feb 11, 2014, to March 10, 2014, be called the ‘Month ending March 10, 2014,’ event though the period is not a calendar month. Using the stricter Period#chunk_sym, would not allow such looseness.

[View source]

293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/fat_core/period.rb', line 293

def chunk_name
  case Period.days_to_chunk_sym(length)
  when :year
    'Year'
  when :quarter
    'Quarter'
  when :bimonth
    'Bi-month'
  when :month
    'Month'
  when :semimonth
    'Semi-month'
  when :biweek
    'Bi-week'
  when :week
    'Week'
  when :day
    'Day'
  else
    'Period'
  end
end

#chunk_symObject

returns the chunk sym represented by the period

[View source]

259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/fat_core/period.rb', line 259

def chunk_sym
  if first.beginning_of_year? && last.end_of_year? &&
      (365..366) === last - first + 1
    :year
  elsif first.beginning_of_quarter? && last.end_of_quarter? &&
      (90..92) === last - first + 1
    :quarter
  elsif first.beginning_of_bimonth? && last.end_of_bimonth? &&
      (58..62) === last - first + 1
    :bimonth
  elsif first.beginning_of_month? && last.end_of_month? &&
      (28..31) === last - first + 1
    :month
  elsif first.beginning_of_semimonth? && last.end_of_semimonth &&
      (13..16) === last - first + 1
    :semimonth
  elsif first.beginning_of_biweek? && last.end_of_biweek? &&
      last - first + 1 == 14
    :biweek
  elsif first.beginning_of_week? && last.end_of_week? &&
      last - first + 1 == 7
    :week
  elsif first == last
    :day
  else
    :irregular
  end
end

#chunks(size: :month, partial_first: false, partial_last: false, round_up_last: false) ⇒ Object

Return an array of Periods wholly-contained within self in chunks of size, defaulting to monthly chunks. Partial chunks at the beginning and end of self are not included unless partial_first or partial_last, respectively, are set true. The last chunk can be made to extend beyond the end of self to make it a whole chunk if round_up_last is set true, in which case, partial_last is ignored.

[View source]

351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/fat_core/period.rb', line 351

def chunks(size: :month, partial_first: false, partial_last: false, round_up_last: false)
  size = size.to_sym
  result = []
  chunk_start = first.dup
  while chunk_start <= last
    case size
    when :year
      unless partial_first
        until chunk_start.beginning_of_year?
          chunk_start += 1.day
        end
      end
      chunk_end = chunk_start.end_of_year
    when :quarter
      unless partial_first
        until chunk_start.beginning_of_quarter?
          chunk_start += 1.day
        end
      end
      chunk_end = chunk_start.end_of_quarter
    when :bimonth
      unless partial_first
        until chunk_start.beginning_of_bimonth?
          chunk_start += 1.day
        end
      end
      chunk_end = (chunk_start.end_of_month + 1.day).end_of_month
    when :month
      unless partial_first
        until chunk_start.beginning_of_month?
          chunk_start += 1.day
        end
      end
      chunk_end = chunk_start.end_of_month
    when :semimonth
      unless partial_first
        until chunk_start.beginning_of_semimonth?
          chunk_start += 1.day
        end
      end
      chunk_end = chunk_start.end_of_semimonth
    when :biweek
      unless partial_first
        until chunk_start.beginning_of_biweek?
          chunk_start += 1.day
        end
      end
      chunk_end = chunk_start.end_of_biweek
    when :week
      unless partial_first
        until chunk_start.beginning_of_week?
          chunk_start += 1.day
        end
      end
      chunk_end = chunk_start.end_of_week
    when :day
      chunk_end = chunk_start
    else
      chunk_end = last
    end
    if chunk_end <= last
      result << Period.new(chunk_start, chunk_end)
    else
      if round_up_last
        result << Period.new(chunk_start, chunk_end)
      elsif partial_last
        result << Period.new(chunk_start, last)
      else
        break
      end
    end
    chunk_start = result.last.last + 1.day
  end
  result
end

#contains?(date) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

316
317
318
319
320
321
322
323
324
# File 'lib/fat_core/period.rb', line 316

def contains?(date)
  if date.respond_to?(:to_date)
    date = date.to_date
  end
  unless (date.is_a? Date)
    raise ArgumentError, "argument must be a Date"
  end
  self.to_range.cover?(date)
end

#difference(other) ⇒ Object Also known as: -

[View source]

253
254
255
# File 'lib/fat_core/period.rb', line 253

def difference(other)
  self.to_range.difference(other.to_range)
end

#eachObject

TO_DATE = Period.new(Date::BOT, Date.current) FOREVER = Period.new(Date::BOT, Date::EOT)

[View source]

88
89
90
91
92
93
94
# File 'lib/fat_core/period.rb', line 88

def each
  d = first
  while d <= last
    yield d
    d = d + 1.day
  end
end

#gaps(periods) ⇒ Object

[View source]

340
341
342
343
# File 'lib/fat_core/period.rb', line 340

def gaps(periods)
  to_range.gaps(periods.map { |p| p.to_range }).
    map { |r| Period.new(r.first, r.last)}
end

#has_overlaps_within?(periods) ⇒ Boolean

Return whether any of the Periods that are within self overlap one another

Returns:

  • (Boolean)
[View source]

332
333
334
# File 'lib/fat_core/period.rb', line 332

def has_overlaps_within?(periods)
  self.to_range.has_overlaps_within?(periods.map{ |p| p.to_range})
end

#intersection(other) ⇒ Object Also known as: &

[View source]

243
244
245
# File 'lib/fat_core/period.rb', line 243

def intersection(other)
  self.to_range.intersection(other.to_range)
end

#lengthObject

[View source]

175
176
177
# File 'lib/fat_core/period.rb', line 175

def length
  size
end

#overlaps?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

239
240
241
# File 'lib/fat_core/period.rb', line 239

def overlaps?(other)
  self.to_range.overlaps?(other.to_range)
end

#proper_subset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

227
228
229
# File 'lib/fat_core/period.rb', line 227

def proper_subset_of?(other)
  to_range.proper_subset_of?(other.to_range)
end

#proper_superset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

235
236
237
# File 'lib/fat_core/period.rb', line 235

def proper_superset_of?(other)
  to_range.proper_superset_of?(other.to_range)
end

#sizeObject

Days in period

[View source]

215
216
217
# File 'lib/fat_core/period.rb', line 215

def size
  to_range.size
end

#spanned_by?(periods) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

336
337
338
# File 'lib/fat_core/period.rb', line 336

def spanned_by?(periods)
  to_range.spanned_by?(periods.map { |p| p.to_range })
end

#subset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

223
224
225
# File 'lib/fat_core/period.rb', line 223

def subset_of?(other)
  to_range.subset_of?(other.to_range)
end

#superset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

231
232
233
# File 'lib/fat_core/period.rb', line 231

def superset_of?(other)
  to_range.superset_of?(other.to_range)
end

#tex_quoteObject

Allow erb documents can directly interpolate ranges

[View source]

210
211
212
# File 'lib/fat_core/period.rb', line 210

def tex_quote
  "#{first.iso}--#{last.iso}"
end

#to_rangeObject

[View source]

179
180
181
# File 'lib/fat_core/period.rb', line 179

def to_range
  (first..last)
end

#to_sObject

[View source]

191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/fat_core/period.rb', line 191

def to_s
  if first.beginning_of_year? && last.end_of_year? && first.year == last.year
    "#{first.year}"
  elsif first.beginning_of_quarter? &&
      last.end_of_quarter? &&
      first.year == last.year &&
      first.quarter == last.quarter
    "#{first.year}-#{first.quarter}Q"
  elsif first.beginning_of_month? &&
      last.end_of_month? &&
      first.year == last.year &&
      first.month == last.month
    "#{first.year}-%02d" % first.month
  else
    "#{first.iso} to #{last.iso}"
  end
end

#union(other) ⇒ Object Also known as: +

[View source]

248
249
250
# File 'lib/fat_core/period.rb', line 248

def union(other)
  self.to_range.union(other.to_range)
end