module FatCore::Date::ClassMethods
Constants
- COMMON_YEAR_DAYS_IN_MONTH
An
Array
of the number of days in each month indexed by month number, starting with January = 1, etc.
Public Instance Methods
# File lib/fat_core/date.rb, line 1680 def days_in_month(year, month) raise ArgumentError, 'illegal month number' if month < 1 || month > 12 days = COMMON_YEAR_DAYS_IN_MONTH[month] if month == 2 ::Date.new(year, month, 1).leap? ? 29 : 28 else days end end
Return the date of Easter for the Western Church in the given year.
@param year [Integer] the year of interest @return [::Date] the date of Easter for year
# File lib/fat_core/date.rb, line 1775 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 that date is of class Date
based either on a string or Date
object.
@param dat [String, Date
, Time] the object to be converted to Date
@return [Date, DateTime]
# File lib/fat_core/date.rb, line 1795 def ensure_date(dat) case dat when String ::Date.parse(dat) when Date, DateTime dat when Time dat.to_date else raise ArgumentError, 'requires String, Date, DateTime, or Time' end end
Return the 1-indexed integer that corresponds to a month name.
@param name [String] a name of a month
@return [Integer] the integer integer that corresponds to a month
name, or nil of no month recognized.
# File lib/fat_core/date.rb, line 1697 def mo_name_to_num(name) case name.clean when /\Ajan/i 1 when /\Afeb/i 2 when /\Amar/i 3 when /\Aapr/i 4 when /\Amay/i 5 when /\Ajun/i 6 when /\Ajul/i 7 when /\Aaug/i 8 when /\Asep/i 9 when /\Aoct/i 10 when /\Anov/i 11 when /\Adec/i 12 else nil end end
Return the nth weekday in the given month. If n is negative, count from last day of month.
@param nth [Integer] the ordinal number for the weekday @param wday [Integer] the weekday of interest with Monday 0 to Sunday 6 @param year [Integer] the year of interest @param month [Integer] the month of interest with January 1 to December 12
# File lib/fat_core/date.rb, line 1735 def nth_wday_in_year_month(nth, wday, year, month) wday = wday.to_i raise ArgumentError, 'illegal weekday number' if wday.negative? || wday > 6 month = month.to_i raise ArgumentError, 'illegal month number' if month < 1 || month > 12 nth = nth.to_i if nth.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 != nth d += 7 nd += 1 end d elsif nth.negative? nth = -nth # 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 != nth d -= 7 nd += 1 end d else raise ArgumentError, 'Argument nth cannot be zero' end end
@group Parsing
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.
@example
::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
@param str [String, to_s] a stringling of the form MM/DD/YYYY @return [::Date] the date represented by the str paramenter.
# File lib/fat_core/date.rb, line 1369 def parse_american(str) re = %r{\A\s*(\d\d?)\s*[-/]\s*(\d\d?)\s*[-/]\s*((\d\d)?\d\d)\s*\z} unless str.to_s =~ re 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
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 `-`.
@example
::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
@param spec [String, to_s] the spec to be interpreted as a calendar period
@param spec_type [Symbol, :from, :to] return the first (:from) or last (:to)
date in the spec's period respectively
@return [::Date] date that is the first (:from) or last (:to) in the period
designated by spec
# File lib/fat_core/date.rb, line 1434 def parse_spec(spec, spec_type = :from) spec = spec.to_s.strip unless %i[from to].include?(spec_type) raise ArgumentError, "invalid date spec type: '#{spec_type}'" end today = ::Date.current case spec.clean when %r{\A(?<yr>\d\d\d\d)[-/](?<mo>\d\d?)[-/](?<dy>\d\d?)\z} # A specified date ::Date.new(Regexp.last_match[:yr].to_i, Regexp.last_match[:mo].to_i, Regexp.last_match[:dy].to_i) when /\AW(?<wk>\d\d?)\z/, /\A(?<wk>\d\d?)W\z/ week_num = Regexp.last_match[:wk].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 %r{\A(?<yr>\d\d\d\d)[-/]W(?<wk>\d\d?)\z}, %r{\A(?<yr>\d\d\d\d)[-/](?<wk>\d\d?)W\z} year = Regexp.last_match[:yr].to_i week_num = Regexp.last_match[:wk].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 %r{^(?<yr>\d\d\d\d)[-/](?<qt>\d)[Qq]$}, %r{^(?<yr>\d\d\d\d)[-/][Qq](?<qt>\d)$} # Year-Quarter year = Regexp.last_match[:yr].to_i quarter = Regexp.last_match[:qt].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 /^(?<qt>[1234])[qQ]$/, /^[qQ](?<qt>[1234])$/ # Quarter only this_year = today.year quarter = Regexp.last_match[:qt].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 %r{^(?<yr>\d\d\d\d)[-/](?<hf>\d)[Hh]$}, %r{^(?<yr>\d\d\d\d)[-/][Hh](?<hf>\d)$} # Year-Half year = Regexp.last_match[:yr].to_i half = Regexp.last_match[:hf].to_i msg = "invalid half number: '#{spec}'" raise ArgumentError, msg unless [1, 2].include?(half) 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 /^(?<hf>[12])[hH]$/, /^[hH](?<hf>[12])$/ # Half only this_year = today.year half = Regexp.last_match[:hf].to_i msg = "invalid half number: '#{spec}'" raise ArgumentError, msg unless [1, 2].include?(half) date = ::Date.new(this_year, half * 6, 15) if spec_type == :from date.beginning_of_half else date.end_of_half end when %r{^(?<yr>\d\d\d\d)[-/](?<mo>\d\d?)*$} # Year-Month only year = Regexp.last_match[:yr].to_i month = Regexp.last_match[:mo].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 %r{^(?<mo>\d\d?)[-/](?<dy>\d\d?)*$} # Month-Day only month = Regexp.last_match[:mo].to_i day = Regexp.last_match[:dy].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(?<mo>\d\d?)\z/ # Month only month = Regexp.last_match[:mo].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 /^(?<yr>\d\d\d\d)$/ # Year only year = Regexp.last_match[:yr].to_i if spec_type == :from ::Date.new(year, 1, 1) else ::Date.new(year, 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