class Date
Extensions to the standard library Date.
Constants
- SECS_PER_DAY
- TIME_START
Public Class Methods
Create a new Date object for the date specified by
year year
, month mon
, and day-of-the-week
wday
.
The nth, n
, occurrence of wday
within the period
will be generated (n
defaults to 1). If n
is
positive, the nth occurrence from the beginning of the period will be
returned, if negative, the nth occurrence from the end of the period will
be returned.
The period is a year, unless month
is non-nil, in which case
it is just that month.
Examples:
-
::bywday(2004, nil, 1, 9) => the ninth Sunday of 2004
-
::bywday(2004, nil, 1) => the first Sunday of 2004
-
::bywday(2004, nil, 1, -2) => the second last Sunday of 2004
-
::bywday(2004, 12, 1) => the first sunday in the 12th month of 2004
-
::bywday(2004, 2, 2, -1) => last Tuesday in the 2nd month in 2004
-
::bywday(2004, -2, 3, -2) => second last Wednesday in the second last month of 2004
Compare this to Date.new, which allows a Date to be created by day-of-the-month, mday, to Date.ordinal, which allows a Date to be created by day-of-the-year, yday, and to Date.commercial, which allows a Date to be created by day-of-the-week, but within a specific week.
# File lib/vpim/date.rb, line 83 def Date.bywday(year, mon, wday, n = 1, sg=Date::ITALY) # Normalize mon to 1-12. if mon if mon > 12 || mon == 0 || mon < -12 raise ArgumentError, "mon #{mon} must be 1-12 or negative 1-12" end if mon < 0 mon = 13 + mon end end if wday < 0 || wday > 6 raise ArgumentError, 'wday must be in range 0-6, or a weekday name' end # Determine direction of indexing. inc = n <=> 0 if inc == 0 raise ArgumentError, 'n must be greater or less than zero' end # if !mon, n is index into year, but direction of search is determined by # sign of n d = Date.new(year, mon ? mon : inc, inc, sg) while d.wday != wday d += inc end # Now we have found the first/last day with the correct wday, search # for nth occurrence, by jumping by n.abs-1 weeks forward or backward. d += 7 * (n.abs - 1) * inc if d.year != year raise ArgumentError, 'n is out of bounds of year' end if mon && d.mon != mon raise ArgumentError, 'n is out of bounds of month' end d end
If wday responds to to_str, convert it to the wday number by searching for a wday that matches, using as many characters as are in wday to do the comparison. wday must be 2 or more characters long in order to be a unique match, other than that, “mo”, “Mon”, and “MonDay” are all valid strings for wday 1.
This method can be called on a valid wday, and it will return it. Perhaps it should be called by default inside the Date#new*() methods so that non-integer wday arguments can be used? Perhaps a similar method should exist for months? But with months, we all know January is 1, who can remember where Date chooses to start its wday count!
Examples:
Date.bywday(2004, 2, Date.str2wday('TU')) => the first Tuesday in February Date.bywday(2004, 2, Date.str2wday(2)) => the same day, but notice that a valid wday integer can be passed right through.
# File lib/vpim/date.rb, line 44 def Date.str2wday(wdaystr) return wdaystr unless wdaystr.respond_to? :to_str str = wdaystr.to_str.upcase if str.length < 2 raise ArgumentError, 'wday #{wday} is not long enough to be a unique weekday name' end wday = Date::DAYNAMES.map { |n| n.slice(0, str.length).upcase }.index(str) return wday if wday raise ArgumentError, 'wday #{wdaystr} was not a recognizable weekday name' end
Return the first day of the week for the specified date. Commercial weeks start on Monday, but the weekstart can be specified (as 0-6, where 0 is sunday, or in formate of Date.str2day).
# File lib/vpim/date.rb, line 127 def Date.weekstart(year, mon, day, weekstart="MO") wkst = Date.str2wday(weekstart) d = Date.new(year, mon, day) until d.wday == wkst d = d - 1 end d end
Public Instance Methods
Converts this object to a Time object, or throws an ArgumentError if conversion is not possible because it is before the start of epoch.
# File lib/vpim/date.rb, line 19 def vpim_to_time raise ArgumentError, 'date is before the start of system time' if self < TIME_START days = self - TIME_START Time.at((days * SECS_PER_DAY).to_i) end