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]

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/fat_core/period.rb', line 120

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]

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/fat_core/period.rb', line 146

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

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

[View source]

115
116
117
118
# File 'lib/fat_core/period.rb', line 115

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]

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/fat_core/period.rb', line 173

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]

81
82
83
84
85
# File 'lib/fat_core/period.rb', line 81

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

Comparable does not include this.

[View source]

59
60
61
# File 'lib/fat_core/period.rb', line 59

def !=(other)
  !(self == other)
end

#<=>(other) ⇒ Object

Comparable base: periods are equal only if their first and last dates are equal. Sorting will be by first date, then last, so periods starting on the same date will sort by last date, thus, from smallest to largest in size.

[View source]

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

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

#===(date) ⇒ Object

Case equality checks for inclusion of date in period.

[View source]

73
74
75
# File 'lib/fat_core/period.rb', line 73

def ===(date)
  self.contains?(date)
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]

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/fat_core/period.rb', line 304

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]

270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/fat_core/period.rb', line 270

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]

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
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/fat_core/period.rb', line 362

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]

327
328
329
330
331
332
333
334
335
# File 'lib/fat_core/period.rb', line 327

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]

264
265
266
# File 'lib/fat_core/period.rb', line 264

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

#eachObject

Enumerable base. Yield each day in the period.

[View source]

64
65
66
67
68
69
70
# File 'lib/fat_core/period.rb', line 64

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

#gaps(periods) ⇒ Object

[View source]

351
352
353
354
# File 'lib/fat_core/period.rb', line 351

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]

343
344
345
# File 'lib/fat_core/period.rb', line 343

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]

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

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

#lengthObject

[View source]

194
195
196
# File 'lib/fat_core/period.rb', line 194

def length
  size
end

#overlaps?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

250
251
252
# File 'lib/fat_core/period.rb', line 250

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

#proper_subset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

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

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

#proper_superset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

246
247
248
# File 'lib/fat_core/period.rb', line 246

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

#sizeObject

Days in period

[View source]

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

def size
  to_range.size
end

#spanned_by?(periods) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

347
348
349
# File 'lib/fat_core/period.rb', line 347

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

#subset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

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

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

#superset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

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

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

#tex_quoteObject

Allow erb documents can directly interpolate ranges

[View source]

221
222
223
# File 'lib/fat_core/period.rb', line 221

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

#to_rangeObject

[View source]

198
199
200
# File 'lib/fat_core/period.rb', line 198

def to_range
  (first..last)
end

#to_sObject

[View source]

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/fat_core/period.rb', line 202

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]

259
260
261
# File 'lib/fat_core/period.rb', line 259

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