Module: FatCore::Date::ClassMethods

Included in:
Date
Defined in:
lib/fat_core/date.rb

Utilities collapse

COMMON_YEAR_DAYS_IN_MONTH =

An Array of the number of days in each month indexed by month number, starting with January = 1, etc.

[31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31,
30, 31].freeze

Parsing collapse

Utilities collapse

Instance Method Details

#days_in_month(y, m) ⇒ Object

Raises:

  • (ArgumentError)
[View source]

1583
1584
1585
1586
1587
1588
1589
1590
1591
# File 'lib/fat_core/date.rb', line 1583

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

#easter(year) ⇒ ::Date

Return the date of Easter for the Western Church in the given year.

Parameters:

  • year (Integer)

    the year of interest

Returns:

  • (::Date)

    the date of Easter for year

[View source]

1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
# File 'lib/fat_core/date.rb', line 1638

def 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

#ensure_date(dat) ⇒ Date

Ensure that date is of class Date based either on a string or Date object.

Parameters:

  • dat (String|Date|Time)

    the object to be converted to Date

Returns:

[View source]

1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
# File 'lib/fat_core/date.rb', line 1658

def ensure_date(dat)
  case dat
  when String
    ::Date.parse(dat)
  when Date, DateTime
    dat
  when Time
    dat.to_date
  else
    raise ArgumentError, 'Date.ensure_date needs String, Date, or Time'
  end
end

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

Return the nth weekday in the given month. If n is negative, count from last day of month.

Parameters:

  • n (Integer)

    the ordinal number for the weekday

  • wday (Integer)

    the weekday of interest with Monday 0 to Sunday 6

  • year (Integer)

    the year of interest

  • month (Integer)

    the month of interest with January 1 to December 12

Raises:

  • (ArgumentError)
[View source]

1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
# File 'lib/fat_core/date.rb', line 1600

def nth_wday_in_year_month(n, wday, year, 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.positive?
    # 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.negative?
    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, 'Argument n cannot be zero'
  end
end

#parse_american(str) ⇒ ::Date

Convert a string +str+ 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 typically to place the day of the month first, DD/MM/YYYY. A date found in the wild can be ambiguous, e.g. 3/5/2014, but 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 (String, #to_s)

    a stringling of the form MM/DD/YYYY

Returns:

  • (::Date)

    the date represented by the str paramenter.

[View source]

1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
# File 'lib/fat_core/date.rb', line 1283

def 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 'period spec' spec to a ::Date. A date spec is a short-hand way of specifying a calendar period either absolutely or relative to the computer clock. This method returns the first date of that period, when spec_type is set to :from, the default, and returns the last date of the period when spec_type is :to.

There are a number of forms the spec can take. In each case, ::Date.parse_spec returns the first date in the period if spec_type is :from and the last date in the period if spec_type is :to:

  • YYYY is the whole year YYYY,
  • YYYY-1H or YYYY-H1 is the first calendar half in year YYYY,
  • H2 or 2H is the second calendar half of the current year,
  • YYYY-3Q or YYYY-Q3 is the third calendar quarter of year YYYY,
  • Q3 or 3Q is the third calendar quarter in the current year,
  • YYYY-04 or YYYY-4 is April, the fourth month of year YYYY,
  • 4-12 or 04-12 is the 12th of April in the current year,
  • 4 or 04 is April in the current year,
  • YYYY-W32 or YYYY-32W is the 32nd week in year YYYY,
  • W32 or 32W is the 32nd week in the current year,
  • YYYY-MM-DD a particular date, so :from and :to return the same date,
  • this_<chunk> where <chunk> is one of year, half, quarter, bimonth, month, semimonth, biweek, week, or day, the corresponding calendar period in which the current date falls,
  • last_<chunk> where <chunk> is one of year, half, quarter, bimonth, month, semimonth, biweek, week, or day, the corresponding calendar period immediately before the one in which the current date falls,
  • today is the same as this_day,
  • yesterday is the same as last_day,
  • forever is the period from ::Date::BOT to ::Date::EOT, essentially all dates of commercial interest, and
  • never causes the method to return nil.

In all of the above example specs, letter used for calendar chunks, W, Q, and H can be written in lower case as well. Also, you can use / to separate date components instead of -.

Examples:

::Date.parse_spec('2012-W32').iso      # => "2012-08-06"
::Date.parse_spec('2012-W32', :to).iso # => "2012-08-12"
::Date.parse_spec('W32').iso           # => "2012-08-06" if executed in 2012
::Date.parse_spec('W32').iso           # => "2012-08-04" if executed in 2014

Parameters:

  • spec (String, #to_s)

    the spec to be interpreted as a calendar period

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

    return the first (:from) or last (:to) date in the spec's period respectively

Returns:

  • (::Date)

    date that is the first (:from) or last (:to) in the period designated by spec

[View source]

1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
# File 'lib/fat_core/date.rb', line 1346

def 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): '#{spec}'"
    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): '#{spec}'"
    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, "invalid quarter number (1-4): '#{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
    unless [1, 2, 3, 4].include?(quarter)
      raise ArgumentError, "invalid quarter number (1-4): '#{spec}'"
    end
    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, "invalid half number: '#{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
    unless [1, 2].include?(half)
      raise ArgumentError, "invalid half number: '#{spec}'"
    end
    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
    year = $1.to_i
    month = $2.to_i
    unless [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].include?(month)
      raise ArgumentError, "invalid month number (1-12): '#{spec}'"
    end
    if spec_type == :from
      ::Date.new(year, month, 1)
    else
      ::Date.new(year, month, 1).end_of_month
    end
  when /^(\d\d?)[-\/](\d\d?)*$/
    # Month-Day only
    month = $1.to_i
    day = $2.to_i
    unless [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].include?(month)
      raise ArgumentError, "invalid month number (1-12): '#{spec}'"
    end
    if spec_type == :from
      ::Date.new(today.year, month, day)
    else
      ::Date.new(today.year, month, day).end_of_month
    end
  when /\A(\d\d?)\z/
    # Month only
    month = $1.to_i
    unless [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].include?(month)
      raise ArgumentError, "invalid month number (1-12): '#{spec}'"
    end
    if spec_type == :from
      ::Date.new(today.year, month, 1)
    else
      ::Date.new(today.year, month, 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
end