Class: Date

Inherits:
Object
  • Object
show all
Defined in:
lib/fat_core/date.rb

Constant Summary collapse

BOT =

Constants for Begining of Time (BOT) and End of Time (EOT) Both outside the range of what we would find in an accounting app.

Date.parse('1900-01-01')
EOT =
Date.parse('3000-12-31')
FED_DECREED_HOLIDAYS =

Holidays decreed by executive order See www.whitehouse.gov/the-press-office/2012/12/21/

executive-order-closing-executive-departments-and-agencies-federal-gover
[
  Date.parse('2012-12-24')
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.days_in_month(y, m) ⇒ Object

Raises:

  • (ArgumentError)


594
595
596
597
598
599
600
601
602
# File 'lib/fat_core/date.rb', line 594

def self.days_in_month(y, m)
  raise ArgumentError, 'illegal month number' if m < 1 || m > 12
  days = Time::COMMON_YEAR_DAYS_IN_MONTH[m]
  if m == 2
    Date.new(y, m, 1).leap? ? 29 : 28
  else
    days
  end
end

.easter(year) ⇒ Object



648
649
650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/fat_core/date.rb', line 648

def self.easter(year)
  y = year
  a = y % 19
  b, c = y.divmod(100)
  d, e = b.divmod(4)
  f = (b + 8) / 25
  g = (b - f + 1) / 3
  h = (19 * a + b - d - g + 15) % 30
  i, k = c.divmod(4)
  l = (32 + 2 * e + 2 * i - h - k) % 7
  m = (a + 11 * h + 22 * l) / 451
  n, p = (h + l - 7 * m + 114).divmod(31)
  Date.new(y, n, p + 1)
end

.nth_wday_in_year_month(n, wday, year, month) ⇒ Object

Raises:

  • (ArgumentError)


604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
# File 'lib/fat_core/date.rb', line 604

def self.nth_wday_in_year_month(n, wday, year, month)
  # Return the nth weekday in the given month
  # If n is negative, count from last day of month
  wday = wday.to_i
  raise ArgumentError, 'illegal weekday number' if wday < 0 || wday > 6
  month = month.to_i
  raise ArgumentError, 'illegal month number' if month < 1 || month > 12
  n = n.to_i
  if n > 0
    # Set d to the 1st wday in month
    d = Date.new(year, month, 1)
    d += 1 while d.wday != wday
    # Set d to the nth wday in month
    nd = 1
    while nd != n
      d += 7
      nd += 1
    end
    d
  elsif n < 0
    n = -n
    # Set d to the last wday in month
    d = Date.new(year, month, 1).end_of_month
    d -= 1 while d.wday != wday
    # Set d to the nth wday in month
    nd = 1
    while nd != n
      d -= 7
      nd += 1
    end
    d
  else
    raise ArgumentError,
      'Arg 1 to nth_wday_in_month_year cannot be zero'
  end
end

.parse_american(str) ⇒ Date

Convert a string with an American style date into a Date object

An American style date is of the form MM/DD/YYYY, that is it places the month first, then the day of the month, and finally the year. The European convention is to place the day of the month first, DD/MM/YYYY. Because a date found in the wild can be ambiguous, e.g. 3/5/2014, a date string known to be using the American convention can be parsed using this method. Both the month and the day can be a single digit. The year can be either 2 or 4 digits, and if given as 2 digits, it adds 2000 to it to give the year.

Examples:

Date.parse_american('9/11/2001') #=> Date(2011, 9, 11)
Date.parse_american('9/11/01')   #=> Date(2011, 9, 11)
Date.parse_american('9/11/1')    #=> ArgumentError

Parameters:

  • str (#to_s)

    a stringling of the form MM/DD/YYYY

Returns:

  • (Date)

    the date represented by the string paramenter.



25
26
27
28
29
30
31
32
33
34
# File 'lib/fat_core/date.rb', line 25

def self.parse_american(str)
  unless str.to_s =~ %r{\A\s*(\d\d?)\s*/\s*(\d\d?)\s*/\s*(\d?\d?\d\d)\s*\z}
    raise ArgumentError, "date string must be of form 'MM?/DD?/YY(YY)?'"
  end
  year = $3.to_i
  month = $1.to_i
  day = $2.to_i
  year += 2000 if year < 100
  Date.new(year, month, day)
end

.parse_spec(spec, spec_type = :from) ⇒ Date

Convert a ‘date spec’ to a Date. A date spec is a short-hand way of specifying a date, relative to the computer clock. A date spec can interpreted as either a ‘from spec’ or a ‘to spec’. Assuming that Date.current at the time of execution is 2014-07-26 and using the default spec_type of :from. The return values are actually Date objects, but are shown below as textual dates.

A fully specified date returns that date:

Date.parse_spec('2001-09-11')  # =>

Commercial weeks can be specified using, for example W32 or 32W, with the week beginning on Monday, ending on Sunday.

Date.parse_spec('2012-W32')    # =>
Date.parse_spec('2012-W32', :to) # =>
Date.parse_spec('W32') # =>

A spec of the form Q3 or 3Q returns the beginning or end of calendar quarters.

Date.parse_spec('Q3')         # =>

Parameters:

  • spec (#to_s)

    a stringling containing the spec to be interpreted

  • spec_type (:from, :to) (defaults to: :from)

    interpret the spec as a from- or to-spec respectively, defaulting to interpretation as a to-spec.

Returns:

  • (Date)

    a date object equivalent to the date spec



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/fat_core/date.rb', line 61

def self.parse_spec(spec, spec_type = :from)
  spec = spec.to_s.strip
  unless [:from, :to].include?(spec_type)
    raise ArgumentError, "invalid date spec type: '#{spec_type}'"
  end

  today = Date.current
  case spec.clean
  when /\A(\d\d\d\d)[-\/](\d\d?)[-\/](\d\d?)\z/
    # A specified date
    Date.new($1.to_i, $2.to_i, $3.to_i)
  when /\AW(\d\d?)\z/, /\A(\d\d?)W\z/
    week_num = $1.to_i
    if week_num < 1 || week_num > 53
      raise ArgumentError, "invalid week number (1-53): 'W#{week_num}'"
    end
    if spec_type == :from
      Date.commercial(today.year, week_num).beginning_of_week
    else
      Date.commercial(today.year, week_num).end_of_week
    end
  when /\A(\d\d\d\d)[-\/]W(\d\d?)\z/, /\A(\d\d\d\d)[-\/](\d\d?)W\z/
    year = $1.to_i
    week_num = $2.to_i
    if week_num < 1 || week_num > 53
      raise ArgumentError, "invalid week number (1-53): 'W#{week_num}'"
    end
    if spec_type == :from
      Date.commercial(year, week_num).beginning_of_week
    else
      Date.commercial(year, week_num).end_of_week
    end
  when /^(\d\d\d\d)[-\/](\d)[Qq]$/, /^(\d\d\d\d)[-\/][Qq](\d)$/
    # Year-Quarter
    year = $1.to_i
    quarter = $2.to_i
    unless [1, 2, 3, 4].include?(quarter)
      raise ArgumentError, "bad date format: #{spec}"
    end
    month = quarter * 3
    if spec_type == :from
      Date.new(year, month, 1).beginning_of_quarter
    else
      Date.new(year, month, 1).end_of_quarter
    end
  when /^([1234])[qQ]$/, /^[qQ]([1234])$/
    # Quarter only
    this_year = today.year
    quarter = $1.to_i
    date = Date.new(this_year, quarter * 3, 15)
    if spec_type == :from
      date.beginning_of_quarter
    else
      date.end_of_quarter
    end
  when /^(\d\d\d\d)[-\/](\d)[Hh]$/, /^(\d\d\d\d)[-\/][Hh](\d)$/
    # Year-Half
    year = $1.to_i
    half = $2.to_i
    unless [1, 2].include?(half)
      raise ArgumentError, "bad date format: #{spec}"
    end
    month = half * 6
    if spec_type == :from
      Date.new(year, month, 15).beginning_of_half
    else
      Date.new(year, month, 1).end_of_half
    end
  when /^([12])[hH]$/, /^[hH]([12])$/
    # Half only
    this_year = today.year
    half = $1.to_i
    date = Date.new(this_year, half * 6, 15)
    if spec_type == :from
      date.beginning_of_half
    else
      date.end_of_half
    end
  when /^(\d\d\d\d)[-\/](\d\d?)*$/
    # Year-Month only
    if spec_type == :from
      Date.new($1.to_i, $2.to_i, 1)
    else
      Date.new($1.to_i, $2.to_i, 1).end_of_month
    end
  when /^(\d\d?)[-\/](\d\d?)*$/
    # Month-Day only
    if spec_type == :from
      Date.new(today.year, $1.to_i, $2.to_i)
    else
      Date.new(today.year, $1.to_i, $2.to_i).end_of_month
    end
  when /\A(\d\d?)\z/
    # Month only
    if spec_type == :from
      Date.new(today.year, $1.to_i, 1)
    else
      Date.new(today.year, $1.to_i, 1).end_of_month
    end
  when /^(\d\d\d\d)$/
    # Year only
    if spec_type == :from
      Date.new($1.to_i, 1, 1)
    else
      Date.new($1.to_i, 12, 31)
    end
  when /^(to|this_?)?day/
    today
  when /^(yester|last_?)?day/
    today - 1.day
  when /^(this_?)?week/
    spec_type == :from ? today.beginning_of_week : today.end_of_week
  when /last_?week/
    if spec_type == :from
      (today - 1.week).beginning_of_week
    else
      (today - 1.week).end_of_week
    end
  when /^(this_?)?biweek/
    if spec_type == :from
      today.beginning_of_biweek
    else
      today.end_of_biweek
    end
  when /last_?biweek/
    if spec_type == :from
      (today - 2.week).beginning_of_biweek
    else
      (today - 2.week).end_of_biweek
    end
  when /^(this_?)?semimonth/
    spec_type == :from ? today.beginning_of_semimonth : today.end_of_semimonth
  when /^last_?semimonth/
    if spec_type == :from
      (today - 15.days).beginning_of_semimonth
    else
      (today - 15.days).end_of_semimonth
    end
  when /^(this_?)?month/
    if spec_type == :from
      today.beginning_of_month
    else
      today.end_of_month
    end
  when /^last_?month/
    if spec_type == :from
      (today - 1.month).beginning_of_month
    else
      (today - 1.month).end_of_month
    end
  when /^(this_?)?bimonth/
    if spec_type == :from
      today.beginning_of_bimonth
    else
      today.end_of_bimonth
    end
  when /^last_?bimonth/
    if spec_type == :from
      (today - 2.month).beginning_of_bimonth
    else
      (today - 2.month).end_of_bimonth
    end
  when /^(this_?)?quarter/
    if spec_type == :from
      today.beginning_of_quarter
    else
      today.end_of_quarter
    end
  when /^last_?quarter/
    if spec_type == :from
      (today - 3.months).beginning_of_quarter
    else
      (today - 3.months).end_of_quarter
    end
  when /^(this_?)?half/
    if spec_type == :from
      today.beginning_of_half
    else
      today.end_of_half
    end
  when /^last_?half/
    if spec_type == :from
      (today - 6.months).beginning_of_half
    else
      (today - 6.months).end_of_half
    end
  when /^(this_?)?year/
    if spec_type == :from
      today.beginning_of_year
    else
      today.end_of_year
    end
  when /^last_?year/
    if spec_type == :from
      (today - 1.year).beginning_of_year
    else
      (today - 1.year).end_of_year
    end
  when /^forever/
    if spec_type == :from
      Date::BOT
    else
      Date::EOT
    end
  when /^never/
    nil
  else
    raise ArgumentError, "bad date spec: '#{spec}''"
  end # !> previous definition of length was here
end

Instance Method Details

#add_chunk(chunk) ⇒ Object



511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/fat_core/date.rb', line 511

def add_chunk(chunk)
  case chunk
  when :year
    next_year
  when :half
    next_month(6)
  when :quarter
    next_month(3)
  when :bimonth
    next_month(2)
  when :month
    next_month
  when :semimonth
    self + 15.days
  when :biweek
    self + 14.days
  when :week
    self + 7.days
  when :day
    self + 1.days
  else
    raise ArgumentError, "add_chunk unknown chunk: '#{chunk}'"
  end
end

#add_fed_business_days(n) ⇒ Object



975
976
977
978
979
980
981
982
983
984
985
# File 'lib/fat_core/date.rb', line 975

def add_fed_business_days(n)
  d = dup
  return d if n.zero?
  incr = n < 0 ? -1 : 1
  n = n.abs
  while n > 0
    d += incr
    n -= 1 if d.fed_workday?
  end
  d
end

#add_nyse_business_days(n) ⇒ Object Also known as: add_trading_days



995
996
997
998
999
1000
1001
1002
1003
1004
1005
# File 'lib/fat_core/date.rb', line 995

def add_nyse_business_days(n)
  d = dup
  return d if n.zero?
  incr = n < 0 ? -1 : 1
  n = n.abs
  while n > 0
    d += incr
    n -= 1 if d.nyse_workday?
  end
  d
end

#americanObject

Format date in MM/DD/YYYY form, as typical for the short American form.



316
317
318
# File 'lib/fat_core/date.rb', line 316

def american
  strftime '%-m/%-d/%Y'
end

#beginning_of_bimonthObject

The date that is the first day of the bimonth in which self falls. A ‘bimonth’ is a two-month calendar period beginning on the first day of the odd-numbered months. E.g., 2014-01-01 to 2014-02-28 is the first bimonth of 2014.



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

def beginning_of_bimonth
  if month.odd?
    beginning_of_month
  else
    (self - 1.month).beginning_of_month
  end
end

#beginning_of_bimonth?Boolean

Returns:

  • (Boolean)


466
467
468
# File 'lib/fat_core/date.rb', line 466

def beginning_of_bimonth?
  month.odd? && beginning_of_month == self
end

#beginning_of_biweekObject

Note: we use a Monday start of the week in the next two methods because commercial week counting assumes a Monday start.



426
427
428
429
430
431
432
# File 'lib/fat_core/date.rb', line 426

def beginning_of_biweek
  if cweek.odd?
    beginning_of_week(:monday)
  else
    (self - 1.week).beginning_of_week(:monday)
  end
end

#beginning_of_biweek?Boolean

Returns:

  • (Boolean)


490
491
492
# File 'lib/fat_core/date.rb', line 490

def beginning_of_biweek?
  beginning_of_biweek == self
end

#beginning_of_chunk(sym) ⇒ Object



536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'lib/fat_core/date.rb', line 536

def beginning_of_chunk(sym)
  case sym
  when :year
    beginning_of_year
  when :half
    beginning_of_half
  when :quarter
    beginning_of_quarter
  when :bimonth
    beginning_of_bimonth
  when :month
    beginning_of_month
  when :semimonth
    beginning_of_semimonth
  when :biweek
    beginning_of_biweek
  when :week
    beginning_of_week
  when :day
    self
  else
    raise ArgumentError, "unknown chunk sym: '#{sym}'"
  end
end

#beginning_of_halfObject

The date that is the first day of the half-year in which self falls.



355
356
357
358
359
360
361
362
363
# File 'lib/fat_core/date.rb', line 355

def beginning_of_half
  if month > 9
    (beginning_of_quarter - 15).beginning_of_quarter
  elsif month > 6
    beginning_of_quarter
  else
    beginning_of_year
  end
end

#beginning_of_half?Boolean

Returns:

  • (Boolean)


450
451
452
# File 'lib/fat_core/date.rb', line 450

def beginning_of_half?
  beginning_of_half == self
end

#beginning_of_month?Boolean

Returns:

  • (Boolean)


474
475
476
# File 'lib/fat_core/date.rb', line 474

def beginning_of_month?
  beginning_of_month == self
end

#beginning_of_quarter?Boolean

Returns:

  • (Boolean)


458
459
460
# File 'lib/fat_core/date.rb', line 458

def beginning_of_quarter?
  beginning_of_quarter == self
end

#beginning_of_semimonthObject

The date that is the first day of the semimonth in which self falls. A semimonth is a calendar period beginning on the 1st or 16th of each month and ending on the 15th or last day of the month respectively. So each year has exactly 24 semimonths.



404
405
406
407
408
409
410
# File 'lib/fat_core/date.rb', line 404

def beginning_of_semimonth
  if day >= 16
    Date.new(year, month, 16)
  else
    beginning_of_month
  end
end

#beginning_of_semimonth?Boolean

Returns:

  • (Boolean)


482
483
484
# File 'lib/fat_core/date.rb', line 482

def beginning_of_semimonth?
  beginning_of_semimonth == self
end

#beginning_of_week?Boolean

Returns:

  • (Boolean)


498
499
500
# File 'lib/fat_core/date.rb', line 498

def beginning_of_week?
  beginning_of_week == self
end

#beginning_of_year?Boolean

Returns:

  • (Boolean)


442
443
444
# File 'lib/fat_core/date.rb', line 442

def beginning_of_year?
  beginning_of_year == self
end

#easter?Boolean

Returns:

  • (Boolean)


668
669
670
671
# File 'lib/fat_core/date.rb', line 668

def easter?
  # Am I Easter?
  self == easter_this_year
end

#easter_this_yearObject



663
664
665
666
# File 'lib/fat_core/date.rb', line 663

def easter_this_year
  # Return the date of Easter in self's year
  Date.easter(year)
end

#end_of_bimonthObject

The date that is the last day of the bimonth in which self falls. A ‘bimonth’ is a two-month calendar period beginning on the first day of the odd-numbered months. E.g., 2014-01-01 to 2014-02-28 is the first bimonth of 2014.



392
393
394
395
396
397
398
# File 'lib/fat_core/date.rb', line 392

def end_of_bimonth
  if month.odd?
    (self + 1.month).end_of_month
  else
    end_of_month
  end
end

#end_of_bimonth?Boolean

Returns:

  • (Boolean)


470
471
472
# File 'lib/fat_core/date.rb', line 470

def end_of_bimonth?
  month.even? && end_of_month == self
end

#end_of_biweekObject



434
435
436
437
438
439
440
# File 'lib/fat_core/date.rb', line 434

def end_of_biweek
  if cweek.odd?
    (self + 1.week).end_of_week(:monday)
  else
    end_of_week(:monday)
  end
end

#end_of_biweek?Boolean

Returns:

  • (Boolean)


494
495
496
# File 'lib/fat_core/date.rb', line 494

def end_of_biweek?
  end_of_biweek == self
end

#end_of_chunk(sym) ⇒ Object



561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
# File 'lib/fat_core/date.rb', line 561

def end_of_chunk(sym)
  case sym
  when :year
    end_of_year
  when :half
    end_of_half
  when :quarter
    end_of_quarter
  when :bimonth
    end_of_bimonth
  when :month
    end_of_month
  when :semimonth
    end_of_semimonth
  when :biweek
    end_of_biweek
  when :week
    end_of_week
  when :day
    self
  else
    raise ArgumentError, "unknown chunk sym: '#{sym}'"
  end
end

#end_of_halfObject

The date that is the last day of the half-year in which self falls.



366
367
368
369
370
371
372
373
374
# File 'lib/fat_core/date.rb', line 366

def end_of_half
  if month < 4
    (end_of_quarter + 15).end_of_quarter
  elsif month < 7
    end_of_quarter
  else
    end_of_year
  end
end

#end_of_half?Boolean

Returns:

  • (Boolean)


454
455
456
# File 'lib/fat_core/date.rb', line 454

def end_of_half?
  end_of_half == self
end

#end_of_month?Boolean

Returns:

  • (Boolean)


478
479
480
# File 'lib/fat_core/date.rb', line 478

def end_of_month?
  end_of_month == self
end

#end_of_quarter?Boolean

Returns:

  • (Boolean)


462
463
464
# File 'lib/fat_core/date.rb', line 462

def end_of_quarter?
  end_of_quarter == self
end

#end_of_semimonthObject

The date that is the last day of the semimonth in which self falls. A semimonth is a calendar period beginning on the 1st or 16th of each month and ending on the 15th or last day of the month respectively. So each year has exactly 24 semimonths.



416
417
418
419
420
421
422
# File 'lib/fat_core/date.rb', line 416

def end_of_semimonth
  if day <= 15
    Date.new(year, month, 15)
  else
    end_of_month
  end
end

#end_of_semimonth?Boolean

Returns:

  • (Boolean)


486
487
488
# File 'lib/fat_core/date.rb', line 486

def end_of_semimonth?
  end_of_semimonth == self
end

#end_of_week?Boolean

Returns:

  • (Boolean)


502
503
504
# File 'lib/fat_core/date.rb', line 502

def end_of_week?
  end_of_week == self
end

#end_of_year?Boolean

Returns:

  • (Boolean)


446
447
448
# File 'lib/fat_core/date.rb', line 446

def end_of_year?
  end_of_year == self
end

#engObject

Format as an English string



310
311
312
# File 'lib/fat_core/date.rb', line 310

def eng
  strftime('%B %e, %Y')
end

#expand_to_period(sym) ⇒ Object



506
507
508
509
# File 'lib/fat_core/date.rb', line 506

def expand_to_period(sym)
  require 'fat_core/period'
  Period.new(beginning_of_chunk(sym), end_of_chunk(sym))
end

#fed_fixed_holiday?Boolean

Calculations for Federal holidays 5 USC 6103

Returns:

  • (Boolean)


683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
# File 'lib/fat_core/date.rb', line 683

def fed_fixed_holiday?
  # Fixed-date holidays on weekdays
  if mon == 1 && mday == 1
    # New Years (January 1),
    true
  elsif mon == 7 && mday == 4
    # Independence Day (July 4),
    true
  elsif mon == 11 && mday == 11
    # Veterans Day (November 11),
    true
  elsif mon == 12 && mday == 25
    # Christmas (December 25), and
    true
  else
    false
  end
end

#fed_holiday?Boolean

Returns:

  • (Boolean)


731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
# File 'lib/fat_core/date.rb', line 731

def fed_holiday?
  # All Saturdays and Sundays are "holidays"
  return true if weekend?

  # Some days are holidays by executive decree
  return true if FED_DECREED_HOLIDAYS.include?(self)

  # Is self a fixed holiday
  return true if fed_fixed_holiday? || fed_moveable_feast?

  if friday? && month == 12 && day == 26
    # If Christmas falls on a Thursday, apparently, the Friday after is
    # treated as a holiday as well.  See 2003, 2008, for example.
    true
  elsif friday?
    # A Friday is a holiday if a fixed-date holiday
    # would fall on the following Saturday
    (self + 1).fed_fixed_holiday? || (self + 1).fed_moveable_feast?
  elsif monday?
    # A Monday is a holiday if a fixed-date holiday
    # would fall on the preceding Sunday
    (self - 1).fed_fixed_holiday? || (self - 1).fed_moveable_feast?
  else
    false
  end
end

#fed_moveable_feast?Boolean

Returns:

  • (Boolean)


702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
# File 'lib/fat_core/date.rb', line 702

def fed_moveable_feast?
  # See if today is a "movable feast," all of which are
  # rigged to fall on Monday except Thanksgiving

  # No moveable feasts in certain months
  if [3, 4, 6, 7, 8, 12].include?(month)
    false
  elsif monday?
    moveable_mondays = []
    # MLK's Birthday (Third Monday in Jan)
    moveable_mondays << nth_wday_in_month?(3, 1, 1)
    # Washington's Birthday (Third Monday in Feb)
    moveable_mondays << nth_wday_in_month?(3, 1, 2)
    # Memorial Day (Last Monday in May)
    moveable_mondays << nth_wday_in_month?(-1, 1, 5)
    # Labor Day (First Monday in Sep)
    moveable_mondays << nth_wday_in_month?(1, 1, 9)
    # Columbus Day (Second Monday in Oct)
    moveable_mondays << nth_wday_in_month?(2, 1, 10)
    # Other Mondays
    moveable_mondays.any?
  elsif thursday?
    # Thanksgiving Day (Fourth Thur in Nov)
    nth_wday_in_month?(4, 4, 11)
  else
    false
  end
end

#fed_workday?Boolean

Returns:

  • (Boolean)


966
967
968
# File 'lib/fat_core/date.rb', line 966

def fed_workday?
  !fed_holiday?
end

#format_by(fmt = '%Y-%m-%d') ⇒ Object



299
300
301
302
# File 'lib/fat_core/date.rb', line 299

def format_by(fmt = '%Y-%m-%d')
  fmt ||= '%Y-%m-%d'
  strftime(fmt)
end

#halfObject

Self’s calendar half: 1 or 2



331
332
333
334
335
336
337
338
# File 'lib/fat_core/date.rb', line 331

def half
  case month
  when (1..6)
    1
  when (7..12)
    2
  end
end

#isoObject

Format as an ISO string.



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

def iso
  strftime('%Y-%m-%d')
end

#next_fed_workdayObject



987
988
989
# File 'lib/fat_core/date.rb', line 987

def next_fed_workday
  add_fed_business_days(1)
end

#next_nyse_workdayObject Also known as: next_trading_day



1008
1009
1010
# File 'lib/fat_core/date.rb', line 1008

def next_nyse_workday
  add_nyse_business_days(1)
end

#next_until_trading_dayObject

Return self if its a trading day, otherwise skip forward to the first later trading day.



1028
1029
1030
1031
1032
# File 'lib/fat_core/date.rb', line 1028

def next_until_trading_day
  date = self
  date += 1 until date.trading_day?
  date
end

#nth_wday_in_month?(n, wday, month) ⇒ Boolean

Returns:

  • (Boolean)


673
674
675
676
677
# File 'lib/fat_core/date.rb', line 673

def nth_wday_in_month?(n, wday, month)
  # Is self the nth weekday in the given month of its year?
  # If n is negative, count from last day of month
  self == Date.nth_wday_in_year_month(n, wday, year, month)
end

#numObject

Format as an all-numeric string, i.e. ‘YYYYMMDD’



295
296
297
# File 'lib/fat_core/date.rb', line 295

def num
  strftime('%Y%m%d')
end

#nyse_fixed_holiday?Boolean

Rule: if it falls on Saturday, observe on preceding Friday. Rule: if it falls on Sunday, observe on following Monday.

New Year’s Day, January 1. Birthday of Martin Luther King, Jr., the third Monday in January. Washington’s Birthday, the third Monday in February. Good Friday Friday before Easter Sunday. NOTE: not a fed holiday Memorial Day, the last Monday in May. Independence Day, July 4. Labor Day, the first Monday in September. NOTE: Columbus and Veterans days not observed Thanksgiving Day, the fourth Thursday in November. Christmas Day, December 25.

Returns:

  • (Boolean)


777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
# File 'lib/fat_core/date.rb', line 777

def nyse_fixed_holiday?
  # Fixed-date holidays
  if mon == 1 && mday == 1
    # New Years (January 1),
    true
  elsif mon == 7 && mday == 4
    # Independence Day (July 4),
    true
  elsif mon == 12 && mday == 25
    # Christmas (December 25), and
    true
  else
    false
  end
end

#nyse_holiday?Boolean

Returns:

  • (Boolean)


938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
# File 'lib/fat_core/date.rb', line 938

def nyse_holiday?
  # All Saturdays and Sundays are "holidays"
  return true if weekend?

  # Is self a fixed holiday
  return true if nyse_fixed_holiday? || nyse_moveable_feast?

  return true if nyse_special_holiday

  if friday? && (self >= Date.parse('1959-07-03'))
    # A Friday is a holiday if a holiday would fall on the following
    # Saturday.  The rule does not apply if the Friday "ends a monthly or
    # yearly accounting period." Adopted July 3, 1959. E.g, December 31,
    # 2010, fell on a Friday, so New Years was on Saturday, but the NYSE
    # opened because it ended a yearly accounting period.  I believe 12/31
    # is the only date to which the exception can apply since only New
    # Year's can fall on the first of the month.
    !end_of_quarter? &&
      ((self + 1).nyse_fixed_holiday? || (self + 1).nyse_moveable_feast?)
  elsif monday?
    # A Monday is a holiday if a holiday would fall on the
    # preceding Sunday.  This has apparently always been the rule.
    (self - 1).nyse_fixed_holiday? || (self - 1).nyse_moveable_feast?
  else
    false
  end
end

#nyse_moveable_feast?Boolean

Returns:

  • (Boolean)


793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
# File 'lib/fat_core/date.rb', line 793

def nyse_moveable_feast?
  # See if today is a "movable feast," all of which are
  # rigged to fall on Monday except Thanksgiving

  # No moveable feasts in certain months
  return false if [6, 7, 8, 10, 12].include?(month)

  case month
  when 1
    # MLK's Birthday (Third Monday in Jan) since 1998
    year >= 1998 && nth_wday_in_month?(3, 1, 1)
  when 2
    # Lincoln's birthday until 1953
    # Washington's Birthday (Third Monday in Feb)
    (year <= 1953 && month == 2 && day == 12) ||
      (year <= 1970 ? (month == 2 && day == 22)
       : nth_wday_in_month?(3, 1, 2))
  when 3, 4
    # Good Friday
    if !friday?
      false
    elsif [1898, 1906, 1907].include?(year)
      # Good Friday, the Friday before Easter, except certain years
      false
    else
      (self + 2).easter?
    end
  when 5
    # Memorial Day (Last Monday in May)
    year <= 1970 ? (month == 5 && day == 30) : nth_wday_in_month?(-1, 1, 5)
  when 9
    # Labor Day (First Monday in Sep)
    nth_wday_in_month?(1, 1, 9)
  when 10
    # Columbus Day (Oct 12) 1909--1953
    year >= 1909 && year <= 1953 && day == 12
  when 11
    if tuesday?
      # Election Day. Until 1968 all Election Days.  From 1972 to 1980
      # Election Day in presidential years only.  Election Day is the first
      # Tuesday after the first Monday in November.
      first_tuesday = Date.nth_wday_in_year_month(1, 1, year, 11) + 1
      is_election_day = (self == first_tuesday)
      if year <= 1968
        is_election_day
      elsif year <= 1980
        is_election_day && (year % 4).zero?
      else
        false
      end
    elsif thursday?
      # Historically Thanksgiving (NYSE closed all day) had been declared to
      #   be the last Thursday in November until 1938; the next-to-last
      #   Thursday in November from 1939 to 1941 (therefore the 3rd Thursday
      #   in 1940 and 1941); the last Thursday in November in 1942; the fourth
      #   Thursday in November since 1943;
      if year < 1938
        nth_wday_in_month?(-1, 4, 11)
      elsif year <= 1941
        nth_wday_in_month?(3, 4, 11)
      elsif year == 1942
        nth_wday_in_month?(-1, 4, 11)
      else
        nth_wday_in_month?(4, 4, 11)
      end
    elsif day == 11
      # Armistice or Veterans Day.  1918--1921; 1934--1953.
      (year >= 1918 && year <= 1921) || (year >= 1934 && year <= 1953)
    else
      false
    end
  else
    false
  end
end

#nyse_special_holidayObject

They NYSE has closed on several occasions outside its normal holiday rules. This detects those dates beginning in 1960. Closing for part of a day is not counted. See www1.nyse.com/pdfs/closings.pdf



872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
# File 'lib/fat_core/date.rb', line 872

def nyse_special_holiday
  return false unless self > Date.parse('1960-01-01')
  case self
  when Date.parse('1961-05-29')
    # Day before Decoaration Day
    true
  when Date.parse('1963-11-25')
    # President Kennedy's funeral
    true
  when Date.parse('1965-12-24')
    # Christmas eve unscheduled for normal holiday
    true
  when Date.parse('1968-02-12')
    # Lincoln birthday
    true
  when Date.parse('1968-04-09')
    # Mourning MLK
    true
  when Date.parse('1968-07-05')
    # Day after Independence Day
    true
  when (Date.parse('1968-06-12')..Date.parse('1968-12-31'))
    # Paperwork crisis (closed on Wednesdays if no other holiday in week)
    wednesday? && (self - 2).nyse_workday? && (self - 1).nyse_workday? &&
      (self + 1).nyse_workday? && (self + 2).nyse_workday?
  when Date.parse('1969-02-10')
    # Heavy snow
    true
  when Date.parse('1969-05-31')
    # Eisenhower Funeral
    true
  when Date.parse('1969-07-21')
    # Moon landing
    true
  when Date.parse('1972-12-28')
    # Truman Funeral
    true
  when Date.parse('1973-01-25')
    # Johnson Funeral
    true
  when Date.parse('1977-07-14')
    # Electrical blackout NYC
    true
  when Date.parse('1985-09-27')
    # Hurricane Gloria
    true
  when Date.parse('1994-04-27')
    # Nixon Funeral
    true
  when (Date.parse('2001-09-11')..Date.parse('2001-09-14'))
    # 9-11 Attacks
    true
  when (Date.parse('2004-06-11')..Date.parse('2001-09-14'))
    # Reagan Funeral
    true
  when Date.parse('2007-01-02')
    # Observance death of President Ford
    true
  when Date.parse('2012-10-29'), Date.parse('2012-10-30')
    # Hurricane Sandy
    true
  else
    false
  end
end

#nyse_workday?Boolean Also known as: trading_day?

Returns:

  • (Boolean)


970
971
972
# File 'lib/fat_core/date.rb', line 970

def nyse_workday?
  !nyse_holiday?
end

#orgObject

Format as an inactive Org date (see emacs org-mode)



305
306
307
# File 'lib/fat_core/date.rb', line 305

def org
  strftime('[%Y-%m-%d %a]')
end

#predObject

Predecessor of self. Allows Date to work as a countable element of a Range.



274
275
276
# File 'lib/fat_core/date.rb', line 274

def pred
  self - 1.day
end

#prior_fed_workdayObject



991
992
993
# File 'lib/fat_core/date.rb', line 991

def prior_fed_workday
  add_fed_business_days(-1)
end

#prior_nyse_workdayObject Also known as: prior_trading_day



1013
1014
1015
# File 'lib/fat_core/date.rb', line 1013

def prior_nyse_workday
  add_nyse_business_days(-1)
end

#prior_until_trading_dayObject

Return self if its a trading day, otherwise skip back to the first prior trading day.



1020
1021
1022
1023
1024
# File 'lib/fat_core/date.rb', line 1020

def prior_until_trading_day
  date = self
  date -= 1 until date.trading_day?
  date
end

#quarterObject

Self’s calendar quarter: 1, 2, 3, or 4



341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/fat_core/date.rb', line 341

def quarter
  case month
  when (1..3)
    1
  when (4..6)
    2
  when (7..9)
    3
  when (10..12)
    4
  end
end

#succObject

Successor of self. Allows Date to work as a countable element of a Range.



280
281
282
# File 'lib/fat_core/date.rb', line 280

def succ
  self + 1.day
end

#tex_quoteObject

Format date to TeX documents as ISO strings



290
291
292
# File 'lib/fat_core/date.rb', line 290

def tex_quote
  iso
end

#weekday?Boolean

Does self fall on a weekday?

Returns:

  • (Boolean)


326
327
328
# File 'lib/fat_core/date.rb', line 326

def weekday?
  !weekend?
end

#weekend?Boolean

Does self fall on a weekend?

Returns:

  • (Boolean)


321
322
323
# File 'lib/fat_core/date.rb', line 321

def weekend?
  saturday? || sunday?
end

#within_6mos_of?(d) ⇒ Boolean

Returns:

  • (Boolean)


641
642
643
644
645
646
# File 'lib/fat_core/date.rb', line 641

def within_6mos_of?(d)
  # Date 6 calendar months before self
  start_date = self - 6.months + 2.days
  end_date = self + 6.months - 2.days
  (start_date..end_date).cover?(d)
end