Class: Period

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

Constant Summary collapse

TO_DATE =

These need to come after initialize is defined

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

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]

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

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.


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

def first
  @first
end

#lastObject

Returns the value of attribute last.


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

def last
  @last
end

Class Method Details

.chunk_sym_to_days(sym) ⇒ Object

[View source]

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/fat_core/period.rb', line 166

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]

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

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
  when :irregular
    raise ArgumentError, "no maximum period for :irregular chunk"
  else
    chunk_sym_to_days(sym)
  end
end

.chunk_symsObject

Return an array of periods that represent the concatenation of all adjacent periods in the given periods. def self.meld_periods(*periods)

melded_periods = []
while (this_period = periods.pop)
  melded_periods.each do |mp|
    if mp.overlaps?(this_period)
      melded_periods.delete(mp)
      melded_periods << mp.union(this_period)
      break
    elsif mp.contiguous?(this_period)
      melded_periods.delete(mp)
      melded_periods << mp.join(this_period)
      break
    end
  end
end
melded_periods

end

[View source]

161
162
163
164
# File 'lib/fat_core/period.rb', line 161

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 in some cases since a :semimonth can be 14 days just like a :biweek. This ignores that possiblity and requires a :semimonth to be at least 15 days.

[View source]

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/fat_core/period.rb', line 218

def self.days_to_chunk_sym(days)
  case days
  when 356..376
    :year
  when 86..96
    :quarter
  when 59..62
    :bimonth
  when 26..33
    :month
  when 15..16
    :semimonth
  when 14
    :biweek
  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]

130
131
132
133
134
# File 'lib/fat_core/period.rb', line 130

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]

85
86
87
# File 'lib/fat_core/period.rb', line 85

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]

80
81
82
# File 'lib/fat_core/period.rb', line 80

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

#===(date) ⇒ Object

Case equality checks for inclusion of date in period.

[View source]

99
100
101
# File 'lib/fat_core/period.rb', line 99

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]

355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/fat_core/period.rb', line 355

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]

321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/fat_core/period.rb', line 321

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]

413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/fat_core/period.rb', line 413

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]

378
379
380
381
382
383
384
385
386
# File 'lib/fat_core/period.rb', line 378

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

#daysObject

Return the number of days in the period

[View source]

104
105
106
# File 'lib/fat_core/period.rb', line 104

def days
  last - first + 1
end

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

[View source]

314
315
316
317
# File 'lib/fat_core/period.rb', line 314

def difference(other)
  ranges = self.to_range.difference(other.to_range)
  ranges.each.map{ |r| Period.new(r.first, r.last) }
end

#eachObject

Enumerable base. Yield each day in the period.

[View source]

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

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

#gaps(periods) ⇒ Object

[View source]

402
403
404
405
# File 'lib/fat_core/period.rb', line 402

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]

394
395
396
# File 'lib/fat_core/period.rb', line 394

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

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

[View source]

297
298
299
300
301
302
303
304
# File 'lib/fat_core/period.rb', line 297

def intersection(other)
  result = self.to_range.intersection(other.to_range)
  if result.nil?
    nil
  else
    Period.new(result.first, result.last)
  end
end

#lengthObject

[View source]

273
274
275
# File 'lib/fat_core/period.rb', line 273

def length
  size
end

#months(days_in_month = 30.436875) ⇒ Object

Return the fractional number of months in the period. By default, use the average number of days in a month, but allow the user to override the assumption with a parameter.

[View source]

111
112
113
# File 'lib/fat_core/period.rb', line 111

def months(days_in_month = 30.436875)
  (days / days_in_month).to_f
end

#overlaps?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

293
294
295
# File 'lib/fat_core/period.rb', line 293

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

#proper_subset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

281
282
283
# File 'lib/fat_core/period.rb', line 281

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

#proper_superset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

289
290
291
# File 'lib/fat_core/period.rb', line 289

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

#sizeObject

Days in period

[View source]

269
270
271
# File 'lib/fat_core/period.rb', line 269

def size
  (last - first + 1).to_i
end

#spanned_by?(periods) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

398
399
400
# File 'lib/fat_core/period.rb', line 398

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

#subset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

277
278
279
# File 'lib/fat_core/period.rb', line 277

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

#superset_of?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

285
286
287
# File 'lib/fat_core/period.rb', line 285

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

#tex_quoteObject

Allow erb documents can directly interpolate ranges

[View source]

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

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

#to_rangeObject

[View source]

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

def to_range
  (first..last)
end

#to_sObject

[View source]

245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/fat_core/period.rb', line 245

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

#trading_daysObject

[View source]

122
123
124
# File 'lib/fat_core/period.rb', line 122

def trading_days
  select(&:nyse_workday?)
end

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

[View source]

308
309
310
311
# File 'lib/fat_core/period.rb', line 308

def union(other)
  result = self.to_range.union(other.to_range)
  Period.new(result.first, result.last)
end

#years(days_in_year = 365.2425) ⇒ Object

Return the fractional number of years in the period. By default, use the average number of days in a year, but allow the user to override the assumption with a parameter.

[View source]

118
119
120
# File 'lib/fat_core/period.rb', line 118

def years(days_in_year = 365.2425)
  (days / days_in_year).to_f
end