runt 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/.travis.yml +5 -0
- data/{CHANGES → CHANGES.txt} +24 -8
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -44
- data/README.md +79 -0
- data/Rakefile +6 -119
- data/doc/tutorial_schedule.md +365 -0
- data/doc/tutorial_sugar.md +170 -0
- data/doc/tutorial_te.md +155 -0
- data/lib/runt.rb +36 -21
- data/lib/runt/dprecision.rb +4 -2
- data/lib/runt/pdate.rb +101 -95
- data/lib/runt/schedule.rb +18 -0
- data/lib/runt/sugar.rb +41 -9
- data/lib/runt/temporalexpression.rb +246 -30
- data/lib/runt/version.rb +3 -0
- data/runt.gemspec +24 -0
- data/site/.cvsignore +1 -0
- data/site/dcl-small.gif +0 -0
- data/site/index-rubforge-www.html +72 -0
- data/site/index.html +75 -60
- data/site/runt-logo.gif +0 -0
- data/site/runt-logo.psd +0 -0
- data/test/baseexpressiontest.rb +10 -8
- data/test/combinedexpressionstest.rb +166 -158
- data/test/daterangetest.rb +4 -6
- data/test/diweektest.rb +32 -32
- data/test/dprecisiontest.rb +2 -4
- data/test/everytetest.rb +6 -0
- data/test/expressionbuildertest.rb +2 -3
- data/test/icalendartest.rb +3 -6
- data/test/minitest_helper.rb +7 -0
- data/test/pdatetest.rb +21 -6
- data/test/redaytest.rb +3 -0
- data/test/reyeartest.rb +1 -1
- data/test/runttest.rb +5 -8
- data/test/scheduletest.rb +13 -14
- data/test/sugartest.rb +28 -6
- data/test/{spectest.rb → temporaldatetest.rb} +14 -4
- data/test/{rspectest.rb → temporalrangetest.rb} +4 -4
- data/test/test_runt.rb +11 -0
- data/test/weekintervaltest.rb +106 -0
- metadata +161 -116
- data/README +0 -106
- data/doc/tutorial_schedule.rdoc +0 -393
- data/doc/tutorial_sugar.rdoc +0 -143
- data/doc/tutorial_te.rdoc +0 -190
- data/setup.rb +0 -1331
data/lib/runt/pdate.rb
CHANGED
@@ -16,27 +16,35 @@ module Runt
|
|
16
16
|
#
|
17
17
|
# Author:: Matthew Lipper
|
18
18
|
class PDate < DateTime
|
19
|
+
include Comparable
|
19
20
|
include DPrecision
|
20
21
|
|
21
22
|
attr_accessor :date_precision
|
22
|
-
|
23
|
+
|
23
24
|
class << self
|
24
|
-
alias_method :old_civil, :civil
|
25
25
|
|
26
26
|
def civil(*args)
|
27
|
-
|
27
|
+
precision=nil
|
28
28
|
if(args[0].instance_of?(DPrecision::Precision))
|
29
29
|
precision = args.shift
|
30
30
|
else
|
31
31
|
return PDate::sec(*args)
|
32
32
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
pdate = super(*args)
|
34
|
+
pdate.date_precision = precision
|
35
|
+
pdate
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse(*args)
|
39
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
40
|
+
pdate = super(*args)
|
41
|
+
pdate.date_precision = opts[:precision] || opts[:date_precision]
|
42
|
+
pdate
|
36
43
|
end
|
37
|
-
end
|
38
44
|
|
39
|
-
|
45
|
+
alias_method :new, :civil
|
46
|
+
|
47
|
+
end
|
40
48
|
|
41
49
|
def include?(expr)
|
42
50
|
eql?(expr)
|
@@ -44,122 +52,120 @@ module Runt
|
|
44
52
|
|
45
53
|
def + (n)
|
46
54
|
raise TypeError, 'expected numeric' unless n.kind_of?(Numeric)
|
55
|
+
ndays = n
|
47
56
|
case @date_precision
|
48
57
|
when YEAR then
|
49
|
-
|
58
|
+
return DPrecision::to_p(PDate::civil(year+n,month,day),@date_precision)
|
50
59
|
when MONTH then
|
51
|
-
|
52
|
-
return DPrecision::to_p((current_date>>n),@date_precision)
|
60
|
+
return DPrecision::to_p((self.to_date>>n),@date_precision)
|
53
61
|
when WEEK then
|
54
|
-
|
62
|
+
ndays = n*7
|
55
63
|
when DAY then
|
56
|
-
|
64
|
+
ndays = n
|
57
65
|
when HOUR then
|
58
|
-
|
66
|
+
ndays = n*(1.to_r/24)
|
59
67
|
when MIN then
|
60
|
-
|
68
|
+
ndays = n*(1.to_r/1440)
|
61
69
|
when SEC then
|
62
|
-
|
70
|
+
ndays = n*(1.to_r/86400)
|
63
71
|
when MILLI then
|
64
|
-
|
72
|
+
ndays = n*(1.to_r/86400000)
|
73
|
+
end
|
74
|
+
DPrecision::to_p((self.to_date + ndays),@date_precision)
|
65
75
|
end
|
66
|
-
end
|
67
76
|
|
68
|
-
|
69
|
-
|
77
|
+
def - (x)
|
78
|
+
case x
|
70
79
|
when Numeric then
|
71
|
-
|
72
|
-
|
73
|
-
|
80
|
+
return self+(-x)
|
81
|
+
when Date then
|
82
|
+
return super(DPrecision::to_p(x,@date_precision))
|
83
|
+
end
|
84
|
+
raise TypeError, 'expected numeric or date'
|
74
85
|
end
|
75
|
-
raise TypeError, 'expected numeric or date'
|
76
|
-
end
|
77
86
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
87
|
+
def <=> (other)
|
88
|
+
result = nil
|
89
|
+
raise "I'm broken #{self.to_s}" if @date_precision.nil?
|
90
|
+
if(!other.nil? && other.respond_to?("date_precision") && other.date_precision>@date_precision)
|
91
|
+
result = super(DPrecision::to_p(other,@date_precision))
|
92
|
+
else
|
93
|
+
result = super(other)
|
94
|
+
end
|
95
|
+
puts "self<#{self.to_s}><=>other<#{other.to_s}> => #{result}" if $DEBUG
|
96
|
+
result
|
84
97
|
end
|
85
|
-
#puts "#{self.to_s}<=>#{other.to_s} => #{result}" if $DEBUG
|
86
|
-
result
|
87
|
-
end
|
88
98
|
|
89
|
-
|
90
|
-
|
91
|
-
|
99
|
+
def succ
|
100
|
+
result = self + 1
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_date
|
104
|
+
(self.date_precision > DAY) ? DateTime.new(self.year,self.month,self.day,self.hour,self.min,self.sec) : Date.new(self.year, self.month, self.day)
|
92
105
|
end
|
93
|
-
return DPrecision::to_p(self.class.new!(@ajd + n, @of, @sg),@date_precision)
|
94
|
-
end
|
95
106
|
|
96
|
-
|
97
|
-
|
98
|
-
DateTime.new(pdate.year,pdate.month,pdate.day,pdate.hour,pdate.min,pdate.sec)
|
107
|
+
def PDate.year(yr,*ignored)
|
108
|
+
PDate.civil(YEAR, yr, MONTH.min_value, DAY.min_value )
|
99
109
|
end
|
100
|
-
return Date.new(pdate.year,pdate.month,pdate.day)
|
101
|
-
end
|
102
110
|
|
103
|
-
|
104
|
-
|
105
|
-
|
111
|
+
def PDate.month( yr,mon,*ignored )
|
112
|
+
PDate.civil(MONTH, yr, mon, DAY.min_value )
|
113
|
+
end
|
106
114
|
|
107
|
-
|
108
|
-
|
109
|
-
|
115
|
+
def PDate.week( yr,mon,day,*ignored )
|
116
|
+
#LJK: need to calculate which week this day implies,
|
117
|
+
#and then move the day back to the *first* day in that week;
|
118
|
+
#note that since rfc2445 defaults to weekstart=monday, I'm
|
119
|
+
#going to use commercial day-of-week
|
120
|
+
raw = PDate.day(yr, mon, day)
|
121
|
+
cooked = PDate.commercial(raw.cwyear, raw.cweek, 1)
|
122
|
+
PDate.civil(WEEK, cooked.year, cooked.month, cooked.day)
|
123
|
+
end
|
110
124
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
#note that since rfc2445 defaults to weekstart=monday, I'm
|
115
|
-
#going to use commercial day-of-week
|
116
|
-
raw = PDate.day(yr, mon, day)
|
117
|
-
cooked = PDate.commercial(raw.cwyear, raw.cweek, 1)
|
118
|
-
PDate.civil(WEEK, cooked.year, cooked.month, cooked.day)
|
119
|
-
end
|
125
|
+
def PDate.day( yr,mon,day,*ignored )
|
126
|
+
PDate.civil(DAY, yr, mon, day )
|
127
|
+
end
|
120
128
|
|
121
|
-
|
122
|
-
|
123
|
-
|
129
|
+
def PDate.hour( yr,mon,day,hr=HOUR.min_value,*ignored )
|
130
|
+
PDate.civil(HOUR, yr, mon, day,hr,MIN.min_value, SEC.min_value)
|
131
|
+
end
|
124
132
|
|
125
|
-
|
126
|
-
|
127
|
-
|
133
|
+
def PDate.min( yr,mon,day,hr=HOUR.min_value,min=MIN.min_value,*ignored )
|
134
|
+
PDate.civil(MIN, yr, mon, day,hr,min, SEC.min_value)
|
135
|
+
end
|
128
136
|
|
129
|
-
|
130
|
-
|
131
|
-
|
137
|
+
def PDate.sec( yr,mon,day,hr=HOUR.min_value,min=MIN.min_value,sec=SEC.min_value,*ignored )
|
138
|
+
PDate.civil(SEC, yr, mon, day,hr,min, sec)
|
139
|
+
end
|
132
140
|
|
133
|
-
|
134
|
-
|
135
|
-
|
141
|
+
def PDate.millisecond( yr,mon,day,hr,min,sec,ms,*ignored )
|
142
|
+
PDate.civil(SEC, yr, mon, day,hr,min, sec, ms, *ignored)
|
143
|
+
#raise "Not implemented yet."
|
144
|
+
end
|
136
145
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
end
|
146
|
+
def PDate.default(*args)
|
147
|
+
PDate.civil(DEFAULT, *args)
|
148
|
+
end
|
141
149
|
|
142
|
-
|
143
|
-
|
144
|
-
|
150
|
+
#FIXME: marshall broken in 1.9
|
151
|
+
#
|
152
|
+
# Custom dump which preserves DatePrecision
|
153
|
+
#
|
154
|
+
# Author:: Jodi Showers
|
155
|
+
#
|
156
|
+
def marshal_dump
|
157
|
+
[date_precision, ajd, start, offset]
|
158
|
+
end
|
145
159
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
160
|
+
#FIXME: marshall broken in 1.9
|
161
|
+
#
|
162
|
+
# Custom load which preserves DatePrecision
|
163
|
+
#
|
164
|
+
# Author:: Jodi Showers
|
165
|
+
#
|
166
|
+
def marshal_load(dumped_obj)
|
167
|
+
@date_precision, @ajd, @sg, @of=dumped_obj
|
168
|
+
end
|
154
169
|
|
155
|
-
#
|
156
|
-
# Custom load which preserves DatePrecision
|
157
|
-
#
|
158
|
-
# Author:: Jodi Showers
|
159
|
-
#
|
160
|
-
def marshal_load(dumped_obj)
|
161
|
-
@date_precision, @ajd, @sg, @of=dumped_obj
|
162
170
|
end
|
163
|
-
|
164
|
-
end
|
165
171
|
end
|
data/lib/runt/schedule.rb
CHANGED
@@ -30,6 +30,10 @@ module Runt
|
|
30
30
|
end
|
31
31
|
result
|
32
32
|
end
|
33
|
+
|
34
|
+
def scheduled_dates(date_range)
|
35
|
+
@elems.values.collect{|expr| expr.dates(date_range)}.flatten.sort.uniq
|
36
|
+
end
|
33
37
|
|
34
38
|
# Return true or false depend on if the supplied event is scheduled to occur on the
|
35
39
|
# given date.
|
@@ -66,7 +70,21 @@ module Runt
|
|
66
70
|
block.call(@elems[event])
|
67
71
|
end
|
68
72
|
|
73
|
+
def date_to_event_hash(event_attribute=:id)
|
74
|
+
start_date = end_date = nil
|
75
|
+
@elems.keys.each do |event|
|
76
|
+
start_date = event.start_date if start_date.nil? || start_date > event.start_date
|
77
|
+
end_date = event.end_date if end_date.nil? || end_date < event.end_date
|
78
|
+
end
|
79
|
+
|
80
|
+
scheduled_dates(DateRange.new(start_date, end_date)).inject({}) do |h, date|
|
81
|
+
h[date] = events(date).collect{|e| e.send(event_attribute)}
|
82
|
+
h
|
83
|
+
end
|
84
|
+
end
|
69
85
|
end
|
86
|
+
|
87
|
+
# TODO: Extend event to take other attributes
|
70
88
|
|
71
89
|
class Event
|
72
90
|
|
data/lib/runt/sugar.rb
CHANGED
@@ -108,8 +108,35 @@
|
|
108
108
|
#
|
109
109
|
# DIMonth.new(First, Saturday)
|
110
110
|
# DIMonth.new(Last, Tuesday)
|
111
|
-
#
|
112
|
-
|
111
|
+
#
|
112
|
+
# === AfterTE
|
113
|
+
#
|
114
|
+
# self.after(date [, inclusive])
|
115
|
+
#
|
116
|
+
# Example:
|
117
|
+
#
|
118
|
+
# self.after(date)
|
119
|
+
# self.after(date, true)
|
120
|
+
#
|
121
|
+
# is equivilant to
|
122
|
+
#
|
123
|
+
# AfterTE.new(date)
|
124
|
+
# AfterTE.new(date, true)
|
125
|
+
#
|
126
|
+
# === BeforeTE
|
127
|
+
#
|
128
|
+
# self.before(date [, inclusive])
|
129
|
+
#
|
130
|
+
# Example:
|
131
|
+
#
|
132
|
+
# self.before(date)
|
133
|
+
# self.before(date, true)
|
134
|
+
#
|
135
|
+
# is equivilant to
|
136
|
+
#
|
137
|
+
# BeforeTE.new(date)
|
138
|
+
# BeforeTE.new(date, true)
|
139
|
+
#
|
113
140
|
require 'runt'
|
114
141
|
|
115
142
|
module Runt
|
@@ -125,12 +152,7 @@ module Runt
|
|
125
152
|
end
|
126
153
|
|
127
154
|
def method_missing(name, *args, &block)
|
128
|
-
|
129
|
-
return result unless result.nil?
|
130
|
-
super
|
131
|
-
end
|
132
|
-
|
133
|
-
def build(name, *args, &block)
|
155
|
+
#puts "method_missing(#{name},#{args},#{block}) => #{result}"
|
134
156
|
case name.to_s
|
135
157
|
when /^daily_(\d{1,2})_(\d{2})([ap]m)_to_(\d{1,2})_(\d{2})([ap]m)$/
|
136
158
|
# REDay
|
@@ -160,10 +182,20 @@ module Runt
|
|
160
182
|
return DIMonth.new(Runt.const(ordinal), Runt.const(day))
|
161
183
|
else
|
162
184
|
# You're hosed
|
163
|
-
|
185
|
+
super
|
164
186
|
end
|
165
187
|
end
|
166
188
|
|
189
|
+
# Shortcut for AfterTE(date, ...).new
|
190
|
+
def after(date, inclusive=false)
|
191
|
+
AfterTE.new(date, inclusive)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Shortcut for BeforeTE(date, ...).new
|
195
|
+
def before(date, inclusive=false)
|
196
|
+
BeforeTE.new(date, inclusive)
|
197
|
+
end
|
198
|
+
|
167
199
|
def parse_time(hour, minute, ampm)
|
168
200
|
hour = hour.to_i + 12 if ampm =~ /pm/
|
169
201
|
[hour.to_i, minute.to_i]
|
@@ -76,7 +76,7 @@ module TExpr
|
|
76
76
|
end
|
77
77
|
result
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
end
|
81
81
|
|
82
82
|
# Base class for TExpr classes that can be composed of other
|
@@ -87,15 +87,29 @@ class Collection
|
|
87
87
|
|
88
88
|
attr_reader :expressions
|
89
89
|
|
90
|
-
def initialize
|
91
|
-
@expressions =
|
90
|
+
def initialize(expressions = [])
|
91
|
+
@expressions = expressions
|
92
|
+
end
|
93
|
+
|
94
|
+
def ==(other)
|
95
|
+
if other.is_a?(Collection)
|
96
|
+
o_exprs = other.expressions.dup
|
97
|
+
expressions.each do |e|
|
98
|
+
return false unless i = o_exprs.index(e)
|
99
|
+
o_exprs.delete_at(i)
|
100
|
+
end
|
101
|
+
o_exprs.each {|e| return false unless i == expressions.index(e)}
|
102
|
+
return true
|
103
|
+
else
|
104
|
+
super(other)
|
105
|
+
end
|
92
106
|
end
|
93
107
|
|
94
108
|
def add(anExpression)
|
95
109
|
@expressions.push anExpression
|
96
110
|
self
|
97
111
|
end
|
98
|
-
|
112
|
+
|
99
113
|
# Will return true if the supplied object overlaps with the range used to
|
100
114
|
# create this instance
|
101
115
|
def overlap?(date_expr)
|
@@ -110,11 +124,11 @@ class Collection
|
|
110
124
|
first_expr, next_exprs = yield
|
111
125
|
result = ''
|
112
126
|
@expressions.map do |expr|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
127
|
+
if @expressions.first===expr
|
128
|
+
result = first_expr + expr.to_s
|
129
|
+
else
|
130
|
+
result = result + next_exprs + expr.to_s
|
131
|
+
end
|
118
132
|
end
|
119
133
|
result
|
120
134
|
else
|
@@ -142,7 +156,7 @@ class Union < Collection
|
|
142
156
|
end
|
143
157
|
false
|
144
158
|
end
|
145
|
-
|
159
|
+
|
146
160
|
def to_s
|
147
161
|
super {['every ',' or ']}
|
148
162
|
end
|
@@ -159,7 +173,7 @@ class Intersect < Collection
|
|
159
173
|
end
|
160
174
|
result
|
161
175
|
end
|
162
|
-
|
176
|
+
|
163
177
|
def to_s
|
164
178
|
super {['every ', ' and ']}
|
165
179
|
end
|
@@ -178,6 +192,10 @@ class Diff
|
|
178
192
|
@expr2 = expr2
|
179
193
|
end
|
180
194
|
|
195
|
+
def ==(o)
|
196
|
+
o.is_a?(Diff) ? expr1 == o.expr1 && expr2 == o.expr2 : super(o)
|
197
|
+
end
|
198
|
+
|
181
199
|
def include?(aDate)
|
182
200
|
return false unless (@expr1.include?(aDate) && !@expr2.include?(aDate))
|
183
201
|
true
|
@@ -189,7 +207,7 @@ class Diff
|
|
189
207
|
end
|
190
208
|
|
191
209
|
# TExpr that provides for inclusion of an arbitrary date.
|
192
|
-
class
|
210
|
+
class TemporalDate
|
193
211
|
|
194
212
|
include TExpr
|
195
213
|
|
@@ -199,6 +217,10 @@ class Spec
|
|
199
217
|
@date_expr = date_expr
|
200
218
|
end
|
201
219
|
|
220
|
+
def ==(o)
|
221
|
+
o.is_a?(TemporalDate) ? date_expr == o.date_expr : super(o)
|
222
|
+
end
|
223
|
+
|
202
224
|
# Will return true if the supplied object is == to that which was used to
|
203
225
|
# create this instance
|
204
226
|
def include?(date_expr)
|
@@ -217,7 +239,7 @@ end
|
|
217
239
|
# facilitating inclusion of an arbitrary range in a temporal expression.
|
218
240
|
#
|
219
241
|
# See also: Range
|
220
|
-
class
|
242
|
+
class TemporalRange < TemporalDate
|
221
243
|
|
222
244
|
## Will return true if the supplied object is included in the range used to
|
223
245
|
## create this instance
|
@@ -225,6 +247,10 @@ class RSpec < Spec
|
|
225
247
|
return @date_expr.include?(date_expr)
|
226
248
|
end
|
227
249
|
|
250
|
+
def ==(o)
|
251
|
+
o.is_a?(TemporalRange) ? date_expr == o.date_expr : super(o)
|
252
|
+
end
|
253
|
+
|
228
254
|
# Will return true if the supplied object overlaps with the range used to
|
229
255
|
# create this instance
|
230
256
|
def overlap?(date_expr)
|
@@ -315,11 +341,17 @@ class DIMonth
|
|
315
341
|
include TExpr
|
316
342
|
include TExprUtils
|
317
343
|
|
344
|
+
attr_reader :day_index, :week_of_month_index
|
345
|
+
|
318
346
|
def initialize(week_of_month_index,day_index)
|
319
347
|
@day_index = day_index
|
320
348
|
@week_of_month_index = week_of_month_index
|
321
349
|
end
|
322
350
|
|
351
|
+
def ==(o)
|
352
|
+
o.is_a?(DIMonth) ? day_index == o.day_index && week_of_month_index == o.week_of_month_index : super(o)
|
353
|
+
end
|
354
|
+
|
323
355
|
def include?(date)
|
324
356
|
( day_matches?(date) ) && ( week_matches?(@week_of_month_index,date) )
|
325
357
|
end
|
@@ -354,6 +386,8 @@ class DIWeek
|
|
354
386
|
include TExpr
|
355
387
|
|
356
388
|
VALID_RANGE = 0..6
|
389
|
+
|
390
|
+
attr_reader :ordinal_weekday
|
357
391
|
|
358
392
|
def initialize(ordinal_weekday)
|
359
393
|
unless VALID_RANGE.include?(ordinal_weekday)
|
@@ -361,6 +395,10 @@ class DIWeek
|
|
361
395
|
end
|
362
396
|
@ordinal_weekday = ordinal_weekday
|
363
397
|
end
|
398
|
+
|
399
|
+
def ==(o)
|
400
|
+
o.is_a?(DIWeek) ? ordinal_weekday == o.ordinal_weekday : super(o)
|
401
|
+
end
|
364
402
|
|
365
403
|
def include?(date)
|
366
404
|
@ordinal_weekday == date.wday
|
@@ -384,6 +422,8 @@ class REWeek
|
|
384
422
|
|
385
423
|
VALID_RANGE = 0..6
|
386
424
|
|
425
|
+
attr_reader :start_day, :end_day
|
426
|
+
|
387
427
|
# Creates a REWeek using the supplied start
|
388
428
|
# day(range = 0..6, where 0=>Sunday) and an optional end
|
389
429
|
# day. If an end day is not supplied, the maximum value
|
@@ -393,6 +433,10 @@ class REWeek
|
|
393
433
|
@start_day = start_day
|
394
434
|
@end_day = end_day
|
395
435
|
end
|
436
|
+
|
437
|
+
def ==(o)
|
438
|
+
o.is_a?(REWeek) ? start_day == o.start_day && end_day == o.end_day : super(o)
|
439
|
+
end
|
396
440
|
|
397
441
|
def include?(date)
|
398
442
|
return true if all_week?
|
@@ -494,24 +538,28 @@ class REYear
|
|
494
538
|
else
|
495
539
|
case args.size
|
496
540
|
when 1
|
497
|
-
|
498
|
-
|
499
|
-
|
541
|
+
@end_month = args[0]
|
542
|
+
@start_day = NO_DAY
|
543
|
+
@end_day = NO_DAY
|
500
544
|
when 2
|
501
|
-
|
502
|
-
|
503
|
-
|
545
|
+
@start_day = args[0]
|
546
|
+
@end_month = args[1]
|
547
|
+
@end_day = NO_DAY
|
504
548
|
when 3
|
505
|
-
|
506
|
-
|
507
|
-
|
549
|
+
@start_day = args[0]
|
550
|
+
@end_month = args[1]
|
551
|
+
@end_day = args[2]
|
508
552
|
else
|
509
|
-
|
553
|
+
raise "Invalid number of var args: 1 or 3 expected, #{args.size} given"
|
510
554
|
end
|
511
555
|
end
|
512
556
|
@same_month_dates_provided = (@start_month == @end_month) && (@start_day!=NO_DAY && @end_day != NO_DAY)
|
513
557
|
end
|
514
558
|
|
559
|
+
def ==(o)
|
560
|
+
o.is_a?(REYear) ? start_day == o.start_day && end_day == o.end_day && start_month == o.start_month && end_month == o.end_month : super(o)
|
561
|
+
end
|
562
|
+
|
515
563
|
def include?(date)
|
516
564
|
|
517
565
|
return same_start_month_include_day?(date) \
|
@@ -555,9 +603,13 @@ end
|
|
555
603
|
# NOTE: By default, this class will match any date expression whose
|
556
604
|
# precision is less than or equal to DPrecision::DAY. To override
|
557
605
|
# this behavior, pass the optional fifth constructor argument the
|
558
|
-
# value: false.
|
606
|
+
# value: false.
|
607
|
+
#
|
608
|
+
# When the less_precise_match argument is true, the
|
609
|
+
# date-like object passed to :include? will be "promoted" to
|
610
|
+
# DPrecision::MINUTE if it has a precision of DPrecision::DAY or
|
611
|
+
# less.
|
559
612
|
#
|
560
|
-
# See also: Date
|
561
613
|
class REDay
|
562
614
|
|
563
615
|
include TExpr
|
@@ -565,6 +617,8 @@ class REDay
|
|
565
617
|
CURRENT=28
|
566
618
|
NEXT=29
|
567
619
|
ANY_DATE=PDate.day(2002,8,CURRENT)
|
620
|
+
|
621
|
+
attr_reader :range, :spans_midnight
|
568
622
|
|
569
623
|
def initialize(start_hour, start_minute, end_hour, end_minute, less_precise_match=true)
|
570
624
|
|
@@ -580,19 +634,26 @@ class REDay
|
|
580
634
|
@range = start_time..end_time
|
581
635
|
@less_precise_match = less_precise_match
|
582
636
|
end
|
583
|
-
|
637
|
+
|
638
|
+
def ==(o)
|
639
|
+
o.is_a?(REDay) ? spans_midnight == o.spans_midnight && range == o.range : super(o)
|
640
|
+
end
|
641
|
+
|
584
642
|
def include?(date)
|
585
643
|
#
|
586
644
|
# If @less_precise_match == true and the precision of the argument
|
587
645
|
# is day or greater, then the result is always true
|
588
|
-
return true if @less_precise_match && date
|
589
|
-
|
646
|
+
return true if @less_precise_match && less_precise?(date)
|
647
|
+
|
648
|
+
date_to_use = ensure_precision(date)
|
649
|
+
|
650
|
+
if(@spans_midnight&&date_to_use.hour<12) then
|
590
651
|
#Assume next day
|
591
|
-
return @range.include?(get_next(
|
652
|
+
return @range.include?(get_next(date_to_use.hour,date_to_use.min))
|
592
653
|
end
|
593
654
|
|
594
655
|
#Same day
|
595
|
-
return @range.include?(get_current(
|
656
|
+
return @range.include?(get_current(date_to_use.hour,date_to_use.min))
|
596
657
|
end
|
597
658
|
|
598
659
|
def to_s
|
@@ -600,6 +661,16 @@ class REDay
|
|
600
661
|
end
|
601
662
|
|
602
663
|
private
|
664
|
+
|
665
|
+
def less_precise?(date)
|
666
|
+
date.date_precision <= DPrecision::DAY
|
667
|
+
end
|
668
|
+
|
669
|
+
def ensure_precision(date)
|
670
|
+
return date unless less_precise?(date)
|
671
|
+
DPrecision.to_p(date,DPrecision::MIN)
|
672
|
+
end
|
673
|
+
|
603
674
|
def spans_midnight?(start_hour, end_hour)
|
604
675
|
return end_hour < start_hour
|
605
676
|
end
|
@@ -627,12 +698,18 @@ class WIMonth
|
|
627
698
|
|
628
699
|
VALID_RANGE = -2..5
|
629
700
|
|
701
|
+
attr_reader :ordinal
|
702
|
+
|
630
703
|
def initialize(ordinal)
|
631
704
|
unless VALID_RANGE.include?(ordinal)
|
632
705
|
raise ArgumentError, 'invalid ordinal week of month'
|
633
706
|
end
|
634
707
|
@ordinal = ordinal
|
635
708
|
end
|
709
|
+
|
710
|
+
def ==(o)
|
711
|
+
o.is_a?(WIMonth) ? ordinal == o.ordinal : super(o)
|
712
|
+
end
|
636
713
|
|
637
714
|
def include?(date)
|
638
715
|
week_matches?(@ordinal,date)
|
@@ -656,11 +733,17 @@ class REMonth
|
|
656
733
|
|
657
734
|
include TExpr
|
658
735
|
|
736
|
+
attr_reader :range
|
737
|
+
|
659
738
|
def initialize(start_day, end_day=0)
|
660
739
|
end_day=start_day if end_day==0
|
661
740
|
@range = start_day..end_day
|
662
741
|
end
|
663
742
|
|
743
|
+
def ==(o)
|
744
|
+
o.is_a?(REMonth) ? range == o.range : super(o)
|
745
|
+
end
|
746
|
+
|
664
747
|
def include?(date)
|
665
748
|
@range.include? date.mday
|
666
749
|
end
|
@@ -678,6 +761,8 @@ end
|
|
678
761
|
class EveryTE
|
679
762
|
|
680
763
|
include TExpr
|
764
|
+
|
765
|
+
attr_reader :start, :interval, :precision
|
681
766
|
|
682
767
|
def initialize(start,n,precision=nil)
|
683
768
|
@start=start
|
@@ -686,6 +771,10 @@ class EveryTE
|
|
686
771
|
@precision=precision || @start.date_precision
|
687
772
|
end
|
688
773
|
|
774
|
+
def ==(o)
|
775
|
+
o.is_a?(EveryTE) ? start == o.start && precision == o.precision && interval == o.interval : super(o)
|
776
|
+
end
|
777
|
+
|
689
778
|
def include?(date)
|
690
779
|
i=DPrecision.to_p(@start,@precision)
|
691
780
|
d=DPrecision.to_p(date,@precision)
|
@@ -710,10 +799,16 @@ class DayIntervalTE
|
|
710
799
|
|
711
800
|
include TExpr
|
712
801
|
|
802
|
+
attr_reader :interval, :base_date
|
803
|
+
|
713
804
|
def initialize(base_date,n)
|
714
805
|
@base_date = DPrecision.to_p(base_date,DPrecision::DAY)
|
715
806
|
@interval = n
|
716
807
|
end
|
808
|
+
|
809
|
+
def ==(o)
|
810
|
+
o.is_a?(DayIntervalTE) ? base_date == o.base_date && interval == o.interval : super(o)
|
811
|
+
end
|
717
812
|
|
718
813
|
def include?(date)
|
719
814
|
return ((DPrecision.to_p(date,DPrecision::DAY) - @base_date).to_i % @interval == 0)
|
@@ -725,6 +820,107 @@ class DayIntervalTE
|
|
725
820
|
|
726
821
|
end
|
727
822
|
|
823
|
+
#
|
824
|
+
# This class creates an expression which matches dates occuring during the weeks
|
825
|
+
# alternating at the given interval begining on the week containing the date
|
826
|
+
# used to create the instance.
|
827
|
+
#
|
828
|
+
# WeekInterval.new(starting_date, interval)
|
829
|
+
#
|
830
|
+
# Weeks are defined as Sunday to Saturday, as opposed to the commercial week
|
831
|
+
# which starts on a Monday. For example,
|
832
|
+
#
|
833
|
+
# every_other_week = WeekInterval.new(Date.new(2013,04,24), 2)
|
834
|
+
#
|
835
|
+
# will match any date that occurs during every other week begining with the
|
836
|
+
# week of 2013-04-21 (2013-04-24 is a Wednesday and 2013-04-21 is the Sunday
|
837
|
+
# that begins the containing week).
|
838
|
+
#
|
839
|
+
# # Sunday of starting week
|
840
|
+
# every_other_week.include?(Date.new(2013,04,21)) #==> true
|
841
|
+
# # Saturday of starting week
|
842
|
+
# every_other_week.include?(Date.new(2013,04,27)) #==> true
|
843
|
+
# # First week _after_ start week
|
844
|
+
# every_other_week.include?(Date.new(2013,05,01)) #==> false
|
845
|
+
# # Second week _after_ start week
|
846
|
+
# every_other_week.include?(Date.new(2013,05,06)) #==> true
|
847
|
+
#
|
848
|
+
# NOTE: The idea and tests for this class were originally contributed as the
|
849
|
+
# REWeekWithIntervalTE class by Jeff Whitmire. The behavior of the original class
|
850
|
+
# provided both the matching of every n weeks and the specification of specific
|
851
|
+
# days of that week in a single class. This class only provides the matching
|
852
|
+
# of every n weeks. The exact functionality of the original class is easy to create
|
853
|
+
# using the Runt set operators and the DIWeek class:
|
854
|
+
#
|
855
|
+
# # Old way
|
856
|
+
# tu_thurs_every_third_week = REWeekWithIntervalTE.new(Date.new(2013,04,24),2,[2,4])
|
857
|
+
#
|
858
|
+
# # New way
|
859
|
+
# tu_thurs_every_third_week =
|
860
|
+
# WeekInterval.new(Date.new(2013,04,24),2) & (DIWeek.new(Tuesday) | DIWeek.new(Thursday))
|
861
|
+
#
|
862
|
+
# Notice that the compound expression (in parens after the "&") can be replaced
|
863
|
+
# or combined with any other appropriate temporal expression to provide different
|
864
|
+
# functionality (REWeek to provide a range of days, REDay to provide certain times, etc...).
|
865
|
+
#
|
866
|
+
# Contributed by Jeff Whitmire
|
867
|
+
class WeekInterval
|
868
|
+
include TExpr
|
869
|
+
def initialize(start_date,interval=2)
|
870
|
+
@start_date = DPrecision.to_p(start_date,DPrecision::DAY)
|
871
|
+
# convert base_date to the start of the week
|
872
|
+
@base_date = @start_date - @start_date.wday
|
873
|
+
@interval = interval
|
874
|
+
end
|
875
|
+
|
876
|
+
def include?(date)
|
877
|
+
return false if @base_date > date
|
878
|
+
((adjust_for_year(date) - week_num(@base_date)) % @interval) == 0
|
879
|
+
end
|
880
|
+
|
881
|
+
def to_s
|
882
|
+
"every #{Runt.ordinalize(@interval)} week starting with the week containing #{Runt.format_date(@start_date)}"
|
883
|
+
end
|
884
|
+
|
885
|
+
private
|
886
|
+
def week_num(date)
|
887
|
+
# %U - Week number of the year. The week starts with Sunday. (00..53)
|
888
|
+
date.strftime("%U").to_i
|
889
|
+
end
|
890
|
+
def max_week_num(year)
|
891
|
+
d = Date.new(year,12,31)
|
892
|
+
max = week_num(d)
|
893
|
+
while max < 52
|
894
|
+
d = d - 1
|
895
|
+
max = week_num(d)
|
896
|
+
end
|
897
|
+
max
|
898
|
+
end
|
899
|
+
def adjust_for_year(date)
|
900
|
+
# Exclusive range: if date.year == @base_date.year, this will be empty
|
901
|
+
range_of_years = @base_date.year...date.year
|
902
|
+
in_same_year = range_of_years.to_a.empty?
|
903
|
+
# Week number of the given date argument
|
904
|
+
week_number = week_num(date)
|
905
|
+
# Default (most common case) date argument is in same year as @base_date
|
906
|
+
# and the week number is also part of the same year. This starting value
|
907
|
+
# is also necessary for the case where they're not in the same year.
|
908
|
+
adjustment = week_number
|
909
|
+
if in_same_year && (week_number < week_num(@base_date)) then
|
910
|
+
# The given date occurs within the same year
|
911
|
+
# but is actually week number 1 of the next year
|
912
|
+
adjustment = adjustment + max_week_num(date.year)
|
913
|
+
elsif !in_same_year then
|
914
|
+
# Date occurs in different year
|
915
|
+
range_of_years.each do |year|
|
916
|
+
# Max week number taking into account we are not using commercial week
|
917
|
+
adjustment = adjustment + max_week_num(year)
|
918
|
+
end
|
919
|
+
end
|
920
|
+
adjustment
|
921
|
+
end
|
922
|
+
end
|
923
|
+
|
728
924
|
# Simple expression which returns true if the supplied arguments
|
729
925
|
# occur within the given year.
|
730
926
|
#
|
@@ -732,10 +928,16 @@ class YearTE
|
|
732
928
|
|
733
929
|
include TExpr
|
734
930
|
|
931
|
+
attr_reader :year
|
932
|
+
|
735
933
|
def initialize(year)
|
736
934
|
@year = year
|
737
935
|
end
|
738
936
|
|
937
|
+
def ==(o)
|
938
|
+
o.is_a?(YearTE) ? year == o.year : super(o)
|
939
|
+
end
|
940
|
+
|
739
941
|
def include?(date)
|
740
942
|
return date.year == @year
|
741
943
|
end
|
@@ -750,13 +952,20 @@ end
|
|
750
952
|
class BeforeTE
|
751
953
|
|
752
954
|
include TExpr
|
955
|
+
|
956
|
+
attr_reader :date, :inclusive
|
753
957
|
|
754
958
|
def initialize(date, inclusive=false)
|
755
959
|
@date = date
|
756
960
|
@inclusive = inclusive
|
757
961
|
end
|
962
|
+
|
963
|
+
def ==(o)
|
964
|
+
o.is_a?(BeforeTE) ? date == o.date && inclusive == o.inclusive : super(o)
|
965
|
+
end
|
758
966
|
|
759
967
|
def include?(date)
|
968
|
+
return false unless date
|
760
969
|
return (date < @date) || (@inclusive && @date == date)
|
761
970
|
end
|
762
971
|
|
@@ -771,11 +980,18 @@ class AfterTE
|
|
771
980
|
|
772
981
|
include TExpr
|
773
982
|
|
983
|
+
attr_reader :date, :inclusive
|
984
|
+
|
774
985
|
def initialize(date, inclusive=false)
|
775
986
|
@date = date
|
776
987
|
@inclusive = inclusive
|
777
988
|
end
|
778
989
|
|
990
|
+
def ==(o)
|
991
|
+
o.is_a?(AfterTE) ? date == o.date && inclusive == o.inclusive : super(o)
|
992
|
+
end
|
993
|
+
|
994
|
+
|
779
995
|
def include?(date)
|
780
996
|
return (date > @date) || (@inclusive && @date == date)
|
781
997
|
end
|