Class: Date

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

Overview

Extensions to the standard library Date.

Constant Summary collapse

TIME_START =
Date.new(1970, 1, 1)
SECS_PER_DAY =
24 * 60 * 60

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.bywday(year, mon, wday, n = 1, sg = Date::ITALY) ⇒ Object

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:

  • Date.bywday(2004, nil, 1, 9) => the ninth Sunday of 2004

  • Date.bywday(2004, nil, 1) => the first Sunday of 2004

  • Date.bywday(2004, nil, 1, -2) => the second last Sunday of 2004

  • Date.bywday(2004, 12, 1) => the first sunday in the 12th month of 2004

  • Date.bywday(2004, 2, 2, -1) => last Tuesday in the 2nd month in 2004

  • Date.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.


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
# 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

.str2wday(wdaystr) ⇒ Object

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.

Raises:

  • (ArgumentError)

44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 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

.weekstart(year, mon, day, weekstart = "MO") ⇒ Object

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).


127
128
129
130
131
132
133
134
# 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

Instance Method Details

#vpim_to_timeObject

Converts this object to a Time object, or throws an ArgumentError if conversion is not possible because it is before the start of epoch.

Raises:

  • (ArgumentError)

19
20
21
22
23
24
# 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