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)


587
588
589
590
591
592
593
594
595
# File 'lib/fat_core/date.rb', line 587

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



641
642
643
644
645
646
647
648
649
650
651
652
653
654
# File 'lib/fat_core/date.rb', line 641

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)


597
598
599
600
601
602
603
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
# File 'lib/fat_core/date.rb', line 597

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
# 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
  when /^(\d\d\d\d)-(\d\d?)-(\d\d?)*$/
    # 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 /\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



504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/fat_core/date.rb', line 504

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



968
969
970
971
972
973
974
975
976
977
978
# File 'lib/fat_core/date.rb', line 968

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



988
989
990
991
992
993
994
995
996
997
998
# File 'lib/fat_core/date.rb', line 988

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.



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

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.



373
374
375
376
377
378
379
# File 'lib/fat_core/date.rb', line 373

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)


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

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.



419
420
421
422
423
424
425
# File 'lib/fat_core/date.rb', line 419

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)


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

def beginning_of_biweek?
  beginning_of_biweek == self
end

#beginning_of_chunk(sym) ⇒ Object



529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/fat_core/date.rb', line 529

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.



348
349
350
351
352
353
354
355
356
# File 'lib/fat_core/date.rb', line 348

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)


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

def beginning_of_half?
  beginning_of_half == self
end

#beginning_of_month?Boolean

Returns:

  • (Boolean)


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

def beginning_of_month?
  beginning_of_month == self
end

#beginning_of_quarter?Boolean

Returns:

  • (Boolean)


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

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.



397
398
399
400
401
402
403
# File 'lib/fat_core/date.rb', line 397

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

#beginning_of_semimonth?Boolean

Returns:

  • (Boolean)


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

def beginning_of_semimonth?
  beginning_of_semimonth == self
end

#beginning_of_week?Boolean

Returns:

  • (Boolean)


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

def beginning_of_week?
  beginning_of_week == self
end

#beginning_of_year?Boolean

Returns:

  • (Boolean)


435
436
437
# File 'lib/fat_core/date.rb', line 435

def beginning_of_year?
  beginning_of_year == self
end

#easter?Boolean

Returns:

  • (Boolean)


661
662
663
664
# File 'lib/fat_core/date.rb', line 661

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

#easter_this_yearObject



656
657
658
659
# File 'lib/fat_core/date.rb', line 656

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.



385
386
387
388
389
390
391
# File 'lib/fat_core/date.rb', line 385

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)


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

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

#end_of_biweekObject



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

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)


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

def end_of_biweek?
  end_of_biweek == self
end

#end_of_chunk(sym) ⇒ Object



554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/fat_core/date.rb', line 554

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.



359
360
361
362
363
364
365
366
367
# File 'lib/fat_core/date.rb', line 359

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)


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

def end_of_half?
  end_of_half == self
end

#end_of_month?Boolean

Returns:

  • (Boolean)


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

def end_of_month?
  end_of_month == self
end

#end_of_quarter?Boolean

Returns:

  • (Boolean)


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

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.



409
410
411
412
413
414
415
# File 'lib/fat_core/date.rb', line 409

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

#end_of_semimonth?Boolean

Returns:

  • (Boolean)


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

def end_of_semimonth?
  end_of_semimonth == self
end

#end_of_week?Boolean

Returns:

  • (Boolean)


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

def end_of_week?
  end_of_week == self
end

#end_of_year?Boolean

Returns:

  • (Boolean)


439
440
441
# File 'lib/fat_core/date.rb', line 439

def end_of_year?
  end_of_year == self
end

#engObject

Format as an English string



303
304
305
# File 'lib/fat_core/date.rb', line 303

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

#expand_to_period(sym) ⇒ Object



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

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)


676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
# File 'lib/fat_core/date.rb', line 676

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)


724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
# File 'lib/fat_core/date.rb', line 724

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)


695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
# File 'lib/fat_core/date.rb', line 695

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)


959
960
961
# File 'lib/fat_core/date.rb', line 959

def fed_workday?
  !fed_holiday?
end

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



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

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

#halfObject

Self’s calendar half: 1 or 2



324
325
326
327
328
329
330
331
# File 'lib/fat_core/date.rb', line 324

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

#isoObject

Format as an ISO string.



278
279
280
# File 'lib/fat_core/date.rb', line 278

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

#next_fed_workdayObject



980
981
982
# File 'lib/fat_core/date.rb', line 980

def next_fed_workday
  add_fed_business_days(1)
end

#next_nyse_workdayObject Also known as: next_trading_day



1001
1002
1003
# File 'lib/fat_core/date.rb', line 1001

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.



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

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)


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

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’



288
289
290
# File 'lib/fat_core/date.rb', line 288

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)


770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
# File 'lib/fat_core/date.rb', line 770

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)


931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
# File 'lib/fat_core/date.rb', line 931

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)


786
787
788
789
790
791
792
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
# File 'lib/fat_core/date.rb', line 786

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



865
866
867
868
869
870
871
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
# File 'lib/fat_core/date.rb', line 865

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)


963
964
965
# File 'lib/fat_core/date.rb', line 963

def nyse_workday?
  !nyse_holiday?
end

#orgObject

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



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

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

#predObject

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



267
268
269
# File 'lib/fat_core/date.rb', line 267

def pred
  self - 1.day
end

#prior_fed_workdayObject



984
985
986
# File 'lib/fat_core/date.rb', line 984

def prior_fed_workday
  add_fed_business_days(-1)
end

#prior_nyse_workdayObject Also known as: prior_trading_day



1006
1007
1008
# File 'lib/fat_core/date.rb', line 1006

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.



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

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



334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/fat_core/date.rb', line 334

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.



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

def succ
  self + 1.day
end

#tex_quoteObject

Format date to TeX documents as ISO strings



283
284
285
# File 'lib/fat_core/date.rb', line 283

def tex_quote
  iso
end

#weekday?Boolean

Does self fall on a weekday?

Returns:

  • (Boolean)


319
320
321
# File 'lib/fat_core/date.rb', line 319

def weekday?
  !weekend?
end

#weekend?Boolean

Does self fall on a weekend?

Returns:

  • (Boolean)


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

def weekend?
  saturday? || sunday?
end

#within_6mos_of?(d) ⇒ Boolean

Returns:

  • (Boolean)


634
635
636
637
638
639
# File 'lib/fat_core/date.rb', line 634

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