runt 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +153 -125
- data/LICENSE.txt +43 -43
- data/README +106 -100
- data/Rakefile +122 -122
- data/TODO +13 -13
- data/doc/tutorial_schedule.rdoc +393 -393
- data/doc/tutorial_sugar.rdoc +143 -0
- data/doc/tutorial_te.rdoc +190 -190
- data/examples/payment_report.rb +59 -0
- data/examples/payment_reporttest.rb +49 -0
- data/examples/reminder.rb +63 -63
- data/lib/runt.rb +237 -219
- data/lib/runt/daterange.rb +74 -74
- data/lib/runt/dprecision.rb +150 -141
- data/lib/runt/expressionbuilder.rb +65 -0
- data/lib/runt/pdate.rb +165 -153
- data/lib/runt/schedule.rb +88 -88
- data/lib/runt/sugar.rb +171 -0
- data/lib/runt/temporalexpression.rb +789 -777
- data/setup.rb +1331 -1331
- data/site/blue-robot3.css +131 -131
- data/site/dcl-small.gif +0 -0
- data/site/index.html +72 -94
- data/site/runt-logo.gif +0 -0
- data/site/runt-logo.psd +0 -0
- data/test/aftertetest.rb +31 -0
- data/test/beforetetest.rb +31 -0
- data/test/daterangetest.rb +89 -89
- data/test/dprecisiontest.rb +58 -55
- data/test/expressionbuildertest.rb +64 -0
- data/test/icalendartest.rb +621 -41
- data/test/pdatetest.rb +147 -117
- data/test/redaytest.rb +10 -0
- data/test/reyeartest.rb +99 -98
- data/test/runttest.rb +98 -101
- data/test/scheduletest.rb +148 -148
- data/test/sugartest.rb +104 -0
- data/test/temporalexpressiontest.rb +76 -76
- metadata +112 -95
@@ -0,0 +1,143 @@
|
|
1
|
+
= Sugar Tutorial
|
2
|
+
|
3
|
+
<em> This tutorial assumes you are familiar with use of the Runt API to
|
4
|
+
create temporal expressions. If you're unfamiliar with how and why to write
|
5
|
+
temporal expressions, take a look at the temporal expression
|
6
|
+
tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_te_rdoc.html].</em>
|
7
|
+
|
8
|
+
Starting with version 0.7.0, Runt provides some syntactic sugar for creating
|
9
|
+
temporal expressions. Runt also provides a builder class for which can be
|
10
|
+
used to create expressions in a more readable way than simply using :new.
|
11
|
+
|
12
|
+
First, let's look at some of the new shorcuts for creating individual
|
13
|
+
expressions. If you look at the lib/runt/sugar.rb file you find that the
|
14
|
+
Runt module has been re-opened and some nutty stuff happens when
|
15
|
+
:method_missing is called.
|
16
|
+
|
17
|
+
For example, if you've included the Runt module, you can now create a
|
18
|
+
DIWeek expression by calling a method whose name matches the following
|
19
|
+
pattern:
|
20
|
+
|
21
|
+
/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/
|
22
|
+
|
23
|
+
So
|
24
|
+
|
25
|
+
tuesday
|
26
|
+
|
27
|
+
is equivalent to
|
28
|
+
|
29
|
+
DIWeek.new(Tuesday)
|
30
|
+
|
31
|
+
Here's a quick summary of patterns and the expressions they create.
|
32
|
+
|
33
|
+
=== REDay
|
34
|
+
|
35
|
+
<b>regex</b>:: /^daily_(\d{1,2})_(\d{2})([ap]m)_to_(\d{1,2})_(\d{2})([ap]m)$/
|
36
|
+
|
37
|
+
<b>example</b>:: daily_8_30am_to_10_00pm
|
38
|
+
|
39
|
+
<b>action</b>:: REDay.new(8,30,22,00)
|
40
|
+
|
41
|
+
=== REWeek
|
42
|
+
|
43
|
+
<b>regex</b>:: /^weekly_(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\_to\_(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/
|
44
|
+
|
45
|
+
<b>example</b>:: weekly_wednesday_to_friday
|
46
|
+
|
47
|
+
<b>action</b>:: REWeek.new(Wednesday, Friday)
|
48
|
+
|
49
|
+
=== REMonth
|
50
|
+
|
51
|
+
<b>regex</b>:: /^monthly_(\d{1,2})(?:st|nd|rd|th)\_to\_(\d{1,2})(?:st|nd|rd|th)$/
|
52
|
+
|
53
|
+
<b>example</b>:: monthly_2nd_to_24th
|
54
|
+
|
55
|
+
<b>action</b>:: REMonth.new(2,24)
|
56
|
+
|
57
|
+
=== REYear
|
58
|
+
|
59
|
+
<b>regex</b>:: /^yearly_(january|february|march|april|may|june|july|august|september|october|november|december)_(\d{1,2})\_to\_(january|february|march|april|may|june|july|august|september|october|november|december)_(\d{1,2})
|
60
|
+
|
61
|
+
<b>example</b>:: yearly_may_31_to_september_1
|
62
|
+
|
63
|
+
<b>action</b>:: REYear.new(May,31,September,1)
|
64
|
+
|
65
|
+
=== DIWeek
|
66
|
+
|
67
|
+
<b>regex</b>:: /^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/
|
68
|
+
|
69
|
+
<b>example</b>:: friday
|
70
|
+
|
71
|
+
<b>action</b>:: DIWeek.new(Friday)
|
72
|
+
|
73
|
+
=== DIMonth
|
74
|
+
|
75
|
+
<b>regex</b>:: /^(first|second|third|fourth|last|second_to_last)_(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/
|
76
|
+
|
77
|
+
<b>example</b>:: last_friday
|
78
|
+
|
79
|
+
<b>action</b>:: DIMonth.new(Last,Friday)
|
80
|
+
|
81
|
+
Now let's look at the new ExpressionBuilder class. This class uses some simple methods and instance_eval to allow one to create composite tempooral expressions in a more fluid style than :new and friends. The idea is that you define a block where method calls add to a composite expression using either "and", "or", or "not".
|
82
|
+
|
83
|
+
# Create a new builder
|
84
|
+
b = ExpressionBuilder.new
|
85
|
+
|
86
|
+
# Call define with a block
|
87
|
+
expression = d.define do
|
88
|
+
on REDay.new(8,45,9,30)
|
89
|
+
on DIWeek.new(Friday) # "And"
|
90
|
+
possibly DIWeek.new(Saturday) # "Or"
|
91
|
+
except DIMonth.new(Last, Friday) # "Not"
|
92
|
+
end
|
93
|
+
|
94
|
+
# expression = "Daily 8:45am to 9:30 and Fridays or Saturday except not the last Friday of the month"
|
95
|
+
|
96
|
+
Hmmm, this is not really an improvement over
|
97
|
+
|
98
|
+
|
99
|
+
REDay.new(8,45,9,30) & DIWeek.new(Friday) | DIWeek.new(Saturday) - DIMonth.new(Last, Friday)
|
100
|
+
|
101
|
+
|
102
|
+
I know, let's try the new constructor aliases defined above!
|
103
|
+
|
104
|
+
|
105
|
+
expression = d.define do
|
106
|
+
on daily_8_45am_to_9_30am
|
107
|
+
on friday
|
108
|
+
possibly saturday
|
109
|
+
except last_friday
|
110
|
+
end
|
111
|
+
|
112
|
+
Much better, except "on daily..." seems a little awkward. We can use :occurs which is aliased to :on for just such a scenario.
|
113
|
+
|
114
|
+
|
115
|
+
expression = d.define do
|
116
|
+
occurs daily_8_45am_to_9_30am
|
117
|
+
on friday
|
118
|
+
possibly saturday
|
119
|
+
except last_friday
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
ExpressionBuilder creates expressions by evaluating a block passed to the
|
124
|
+
:define method. From inside the block, methods :occurs, :on, :every, :possibly,
|
125
|
+
and :maybe can be called with a temporal expression which will be added to
|
126
|
+
a composite expression as follows:
|
127
|
+
|
128
|
+
* <b>:on</b>:: creates an "and" (&)
|
129
|
+
* <b>:possibly</b>:: creates an "or" (|)
|
130
|
+
* <b>:except</b>:: creates a "not" (-)
|
131
|
+
* <b>:every</b>:: alias for :on method
|
132
|
+
* <b>:occurs</b>:: alias for :on method
|
133
|
+
* <b>:maybe</b>:: alias for :possibly method
|
134
|
+
|
135
|
+
Of course it's easy to open the builder class and add you own aliases if the ones provided don't work for you:
|
136
|
+
|
137
|
+
class ExpressionBuilder
|
138
|
+
alias_method :potentially, :possibly
|
139
|
+
etc....
|
140
|
+
end
|
141
|
+
|
142
|
+
If there are shortcuts or macros that you think others would find useful, send in a feature request or patch.
|
143
|
+
|
data/doc/tutorial_te.rdoc
CHANGED
@@ -1,190 +1,190 @@
|
|
1
|
-
= Temporal Expressions Tutorial
|
2
|
-
|
3
|
-
Based on a pattern[http://martinfowler.com/apsupp/recurring.pdf]
|
4
|
-
created by Martin Fowler, temporal expressions define points or ranges
|
5
|
-
in time using <em>set expressions</em>. This means, an application
|
6
|
-
developer can precisely describe recurring events without resorting to
|
7
|
-
hacking out a big-ol' nasty enumerated list of dates.
|
8
|
-
|
9
|
-
For example, say you wanted to schedule an event that occurred
|
10
|
-
annually on the last Thursday of every August. You might start out by
|
11
|
-
doing something like this:
|
12
|
-
|
13
|
-
require 'date'
|
14
|
-
|
15
|
-
some_dates = [Date.new(2002,8,29),Date.new(2003,8,28),Date.new(2004,8,26)]
|
16
|
-
|
17
|
-
...etc.
|
18
|
-
|
19
|
-
This is fine for two or three years, but what about for thirty years?
|
20
|
-
What if you want to say every Monday, Tuesday and Friday, between 3
|
21
|
-
and 5pm for the next fifty years? *Ouch*.
|
22
|
-
|
23
|
-
As Fowler notes in his paper, TemporalExpressions(<tt>TE</tt>s for
|
24
|
-
short) provide a simple pattern language for defining a given set of
|
25
|
-
dates and/or times. They can be 'mixed-and- matched' as necessary,
|
26
|
-
providing an incremental, modular and expanding expressive power.
|
27
|
-
|
28
|
-
Alrighty, then...less talkin', more tutorin'!
|
29
|
-
|
30
|
-
=== Example 1
|
31
|
-
<b>Define An Expression That Says: 'the last Thursday in August'</b>
|
32
|
-
|
33
|
-
1 require 'runt'
|
34
|
-
2 require 'date'
|
35
|
-
3
|
36
|
-
4 last_thursday = DIMonth.new(Last_of,Thursday)
|
37
|
-
5
|
38
|
-
6 august = REYear.new(8)
|
39
|
-
7
|
40
|
-
8 expr = last_thursday & august
|
41
|
-
9
|
42
|
-
10 expr.include?(Date.new(2002,8,29)) #Thurs 8/29/02 => true
|
43
|
-
11 expr.include?(Date.new(2003,8,28)) #Thurs 8/28/03 => true
|
44
|
-
12 expr.include?(Date.new(2004,8,26)) #Thurs 8/26/04 => true
|
45
|
-
13
|
46
|
-
14 expr.include?(Date.new(2004,3,18)) #Thurs 3/18/04 => false
|
47
|
-
15 expr.include?(Date.new(2004,8,27)) #Fri 8/27/04 => false
|
48
|
-
|
49
|
-
A couple things are worth noting before we move on to more complicated
|
50
|
-
expressions.
|
51
|
-
|
52
|
-
Clients use temporal expressions by creating specific instances
|
53
|
-
(DIMonth == day in month, REYear == range each year) and then,
|
54
|
-
optionally, combining them using various familiar operators
|
55
|
-
<tt>( & , | , - )</tt>.
|
56
|
-
|
57
|
-
Semantically, the '&' operator on line 8 behaves much like the
|
58
|
-
standard Ruby short-circuit operator '&&'. However, instead of
|
59
|
-
returning a boolean value, a new composite <tt>TE</tt> is instead
|
60
|
-
created and returned. This new expression is the logical
|
61
|
-
intersection of everything matched by <b>both</b> arguments '&'.
|
62
|
-
|
63
|
-
In the example above, line 4:
|
64
|
-
|
65
|
-
|
66
|
-
last_thursday = DIMonth.new(Last_of,Thursday)
|
67
|
-
|
68
|
-
|
69
|
-
will match the last Thursday of <b>any</b> month and line 6:
|
70
|
-
|
71
|
-
|
72
|
-
august = REYear.new(8)
|
73
|
-
|
74
|
-
|
75
|
-
will match <b>any</b> date or date range occurring within the month of
|
76
|
-
August. Thus, combining them, you have 'the last Thursday' <b>AND</b>
|
77
|
-
'the month of August'.
|
78
|
-
|
79
|
-
By contrast:
|
80
|
-
|
81
|
-
|
82
|
-
expr = DIMonth.new(Last_of,Thursday) | REYear.new(8)
|
83
|
-
|
84
|
-
|
85
|
-
will all match dates and ranges occurring within 'the last Thursday'
|
86
|
-
<b>OR</b> 'the month of August'.
|
87
|
-
|
88
|
-
|
89
|
-
Now what? Beginning on line 10, you can see that calling the
|
90
|
-
<tt>#include?</tt> method will let you know whether the expression you've
|
91
|
-
defined includes a given date (or, in some cases, a range, or another
|
92
|
-
TE). This is much like the way you use the standard <tt>Range#include?</tt>.
|
93
|
-
|
94
|
-
=== Example 2
|
95
|
-
<b>Define: 'Street Cleaning Rules/Alternate Side Parking in NYC'</b>
|
96
|
-
|
97
|
-
In his paper[http://martinfowler.com/apsupp/recurring.pdf], Fowler
|
98
|
-
uses Boston parking regulations to illustrate some examples. Since I'm
|
99
|
-
from New York City, and Boston-related examples might cause an
|
100
|
-
allergic reaction, I'll use NYC's street cleaning and parking
|
101
|
-
calendar[http://www.nyc.gov/html/dot/html/motorist/scrintro.html#street]
|
102
|
-
instead. Since I'm not <em>completely</em> insane, I'll only use a
|
103
|
-
small subset of the City's actual rules.
|
104
|
-
|
105
|
-
On my block, parking is prohibited on the north side of the street
|
106
|
-
Monday, Wednesday, and Friday between the hours of 8am to 11am, and on
|
107
|
-
Tuesday and Thursday from 11:30am to 2pm
|
108
|
-
|
109
|
-
Hmmm...let's start by selecting days in the week.
|
110
|
-
|
111
|
-
Monday <b>OR</b> Wednesday <b>OR</b> Friday:
|
112
|
-
|
113
|
-
mon_wed_fri = DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)
|
114
|
-
|
115
|
-
#Sanity check
|
116
|
-
mon_wed_fri.include?( DateTime.new(2004,3,10,19,15) ) # Wed => true
|
117
|
-
mon_wed_fri.include?( DateTime.new(2004,3,14,9,00) ) # Sun => false
|
118
|
-
|
119
|
-
8am to 11am:
|
120
|
-
|
121
|
-
eight_to_eleven = REDay.new(8,00,11,00)
|
122
|
-
|
123
|
-
combine the two:
|
124
|
-
|
125
|
-
expr1 = mon_wed_fri & eight_to_eleven
|
126
|
-
|
127
|
-
and, logically speaking, we now have '(Mon <b>OR</b> Wed <b>OR</b> Fri)
|
128
|
-
<b>AND</b> (8am to 11am)'. We're halfway there.
|
129
|
-
|
130
|
-
Tuesdays and Thursdays:
|
131
|
-
|
132
|
-
tues_thurs = DIWeek.new(Tue) | DIWeek.new(Thu)
|
133
|
-
|
134
|
-
11:30am to 2pm:
|
135
|
-
eleven_thirty_to_two = REDay.new(11,30,14,00)
|
136
|
-
|
137
|
-
#Sanity check
|
138
|
-
eleven_thirty_to_two.include?( DateTime.new(2004,3,8,12,00) ) # Noon => true
|
139
|
-
eleven_thirty_to_two.include?( DateTime.new(2004,3,11,00,00) ) # Midnite => false
|
140
|
-
|
141
|
-
expr2 = tues_thurs & eleven_thirty_to_two
|
142
|
-
|
143
|
-
<tt>expr2</tt> says '(Tues <b>OR</b> Thurs) <b>AND</b> (11:30am to 2pm)'.
|
144
|
-
|
145
|
-
and finally:
|
146
|
-
|
147
|
-
ticket = expr1 | expr2
|
148
|
-
|
149
|
-
|
150
|
-
Or, logically, ((Mon <b>OR</b> Wed <b>OR</b> Fri) <b>AND</b> (8am to
|
151
|
-
11am)) <b>OR</b> ((Tues OR Thurs) <b>AND</b> (11:30am to 2pm))
|
152
|
-
|
153
|
-
|
154
|
-
Let's re-write this without all the noise:
|
155
|
-
|
156
|
-
|
157
|
-
expr1 = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
|
158
|
-
|
159
|
-
expr2 = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
|
160
|
-
|
161
|
-
ticket = expr1 | expr2
|
162
|
-
|
163
|
-
|
164
|
-
ticket.include?( DateTime.new(2004,3,11,12,15) ) # => true
|
165
|
-
|
166
|
-
ticket.include?( DateTime.new(2004,3,10,9,15) ) # => true
|
167
|
-
|
168
|
-
ticket.include?( DateTime.new(2004,3,10,8,00) ) # => true
|
169
|
-
|
170
|
-
ticket.include?( DateTime.new(2004,3,11,1,15) ) # => false
|
171
|
-
|
172
|
-
|
173
|
-
Sigh...now if I can only get my dad to remember this...
|
174
|
-
|
175
|
-
|
176
|
-
These are simple examples, but they demonstrate how temporal
|
177
|
-
expressions can be used instead of an enumerated list of date values
|
178
|
-
to define patterns of recurrence. There are many other temporal
|
179
|
-
expressions, and, more importantly, once you get the hang of it, it's
|
180
|
-
easy to write your own.
|
181
|
-
|
182
|
-
Fowler's paper[http://martinfowler.com/apsupp/recurring.pdf] also goes
|
183
|
-
on to describe another element of this pattern: the <tt>Schedule</tt>.
|
184
|
-
See the schedule tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_schedule_rdoc.html] for details.
|
185
|
-
|
186
|
-
<em>See Also:</em>
|
187
|
-
|
188
|
-
* Fowler's recurring event pattern[http://martinfowler.com/apsupp/recurring.pdf]
|
189
|
-
|
190
|
-
* Other temporal patterns[http://martinfowler.com/ap2/timeNarrative.html]
|
1
|
+
= Temporal Expressions Tutorial
|
2
|
+
|
3
|
+
Based on a pattern[http://martinfowler.com/apsupp/recurring.pdf]
|
4
|
+
created by Martin Fowler, temporal expressions define points or ranges
|
5
|
+
in time using <em>set expressions</em>. This means, an application
|
6
|
+
developer can precisely describe recurring events without resorting to
|
7
|
+
hacking out a big-ol' nasty enumerated list of dates.
|
8
|
+
|
9
|
+
For example, say you wanted to schedule an event that occurred
|
10
|
+
annually on the last Thursday of every August. You might start out by
|
11
|
+
doing something like this:
|
12
|
+
|
13
|
+
require 'date'
|
14
|
+
|
15
|
+
some_dates = [Date.new(2002,8,29),Date.new(2003,8,28),Date.new(2004,8,26)]
|
16
|
+
|
17
|
+
...etc.
|
18
|
+
|
19
|
+
This is fine for two or three years, but what about for thirty years?
|
20
|
+
What if you want to say every Monday, Tuesday and Friday, between 3
|
21
|
+
and 5pm for the next fifty years? *Ouch*.
|
22
|
+
|
23
|
+
As Fowler notes in his paper, TemporalExpressions(<tt>TE</tt>s for
|
24
|
+
short) provide a simple pattern language for defining a given set of
|
25
|
+
dates and/or times. They can be 'mixed-and- matched' as necessary,
|
26
|
+
providing an incremental, modular and expanding expressive power.
|
27
|
+
|
28
|
+
Alrighty, then...less talkin', more tutorin'!
|
29
|
+
|
30
|
+
=== Example 1
|
31
|
+
<b>Define An Expression That Says: 'the last Thursday in August'</b>
|
32
|
+
|
33
|
+
1 require 'runt'
|
34
|
+
2 require 'date'
|
35
|
+
3
|
36
|
+
4 last_thursday = DIMonth.new(Last_of,Thursday)
|
37
|
+
5
|
38
|
+
6 august = REYear.new(8)
|
39
|
+
7
|
40
|
+
8 expr = last_thursday & august
|
41
|
+
9
|
42
|
+
10 expr.include?(Date.new(2002,8,29)) #Thurs 8/29/02 => true
|
43
|
+
11 expr.include?(Date.new(2003,8,28)) #Thurs 8/28/03 => true
|
44
|
+
12 expr.include?(Date.new(2004,8,26)) #Thurs 8/26/04 => true
|
45
|
+
13
|
46
|
+
14 expr.include?(Date.new(2004,3,18)) #Thurs 3/18/04 => false
|
47
|
+
15 expr.include?(Date.new(2004,8,27)) #Fri 8/27/04 => false
|
48
|
+
|
49
|
+
A couple things are worth noting before we move on to more complicated
|
50
|
+
expressions.
|
51
|
+
|
52
|
+
Clients use temporal expressions by creating specific instances
|
53
|
+
(DIMonth == day in month, REYear == range each year) and then,
|
54
|
+
optionally, combining them using various familiar operators
|
55
|
+
<tt>( & , | , - )</tt>.
|
56
|
+
|
57
|
+
Semantically, the '&' operator on line 8 behaves much like the
|
58
|
+
standard Ruby short-circuit operator '&&'. However, instead of
|
59
|
+
returning a boolean value, a new composite <tt>TE</tt> is instead
|
60
|
+
created and returned. This new expression is the logical
|
61
|
+
intersection of everything matched by <b>both</b> arguments '&'.
|
62
|
+
|
63
|
+
In the example above, line 4:
|
64
|
+
|
65
|
+
|
66
|
+
last_thursday = DIMonth.new(Last_of,Thursday)
|
67
|
+
|
68
|
+
|
69
|
+
will match the last Thursday of <b>any</b> month and line 6:
|
70
|
+
|
71
|
+
|
72
|
+
august = REYear.new(8)
|
73
|
+
|
74
|
+
|
75
|
+
will match <b>any</b> date or date range occurring within the month of
|
76
|
+
August. Thus, combining them, you have 'the last Thursday' <b>AND</b>
|
77
|
+
'the month of August'.
|
78
|
+
|
79
|
+
By contrast:
|
80
|
+
|
81
|
+
|
82
|
+
expr = DIMonth.new(Last_of,Thursday) | REYear.new(8)
|
83
|
+
|
84
|
+
|
85
|
+
will all match dates and ranges occurring within 'the last Thursday'
|
86
|
+
<b>OR</b> 'the month of August'.
|
87
|
+
|
88
|
+
|
89
|
+
Now what? Beginning on line 10, you can see that calling the
|
90
|
+
<tt>#include?</tt> method will let you know whether the expression you've
|
91
|
+
defined includes a given date (or, in some cases, a range, or another
|
92
|
+
TE). This is much like the way you use the standard <tt>Range#include?</tt>.
|
93
|
+
|
94
|
+
=== Example 2
|
95
|
+
<b>Define: 'Street Cleaning Rules/Alternate Side Parking in NYC'</b>
|
96
|
+
|
97
|
+
In his paper[http://martinfowler.com/apsupp/recurring.pdf], Fowler
|
98
|
+
uses Boston parking regulations to illustrate some examples. Since I'm
|
99
|
+
from New York City, and Boston-related examples might cause an
|
100
|
+
allergic reaction, I'll use NYC's street cleaning and parking
|
101
|
+
calendar[http://www.nyc.gov/html/dot/html/motorist/scrintro.html#street]
|
102
|
+
instead. Since I'm not <em>completely</em> insane, I'll only use a
|
103
|
+
small subset of the City's actual rules.
|
104
|
+
|
105
|
+
On my block, parking is prohibited on the north side of the street
|
106
|
+
Monday, Wednesday, and Friday between the hours of 8am to 11am, and on
|
107
|
+
Tuesday and Thursday from 11:30am to 2pm
|
108
|
+
|
109
|
+
Hmmm...let's start by selecting days in the week.
|
110
|
+
|
111
|
+
Monday <b>OR</b> Wednesday <b>OR</b> Friday:
|
112
|
+
|
113
|
+
mon_wed_fri = DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)
|
114
|
+
|
115
|
+
#Sanity check
|
116
|
+
mon_wed_fri.include?( DateTime.new(2004,3,10,19,15) ) # Wed => true
|
117
|
+
mon_wed_fri.include?( DateTime.new(2004,3,14,9,00) ) # Sun => false
|
118
|
+
|
119
|
+
8am to 11am:
|
120
|
+
|
121
|
+
eight_to_eleven = REDay.new(8,00,11,00)
|
122
|
+
|
123
|
+
combine the two:
|
124
|
+
|
125
|
+
expr1 = mon_wed_fri & eight_to_eleven
|
126
|
+
|
127
|
+
and, logically speaking, we now have '(Mon <b>OR</b> Wed <b>OR</b> Fri)
|
128
|
+
<b>AND</b> (8am to 11am)'. We're halfway there.
|
129
|
+
|
130
|
+
Tuesdays and Thursdays:
|
131
|
+
|
132
|
+
tues_thurs = DIWeek.new(Tue) | DIWeek.new(Thu)
|
133
|
+
|
134
|
+
11:30am to 2pm:
|
135
|
+
eleven_thirty_to_two = REDay.new(11,30,14,00)
|
136
|
+
|
137
|
+
#Sanity check
|
138
|
+
eleven_thirty_to_two.include?( DateTime.new(2004,3,8,12,00) ) # Noon => true
|
139
|
+
eleven_thirty_to_two.include?( DateTime.new(2004,3,11,00,00) ) # Midnite => false
|
140
|
+
|
141
|
+
expr2 = tues_thurs & eleven_thirty_to_two
|
142
|
+
|
143
|
+
<tt>expr2</tt> says '(Tues <b>OR</b> Thurs) <b>AND</b> (11:30am to 2pm)'.
|
144
|
+
|
145
|
+
and finally:
|
146
|
+
|
147
|
+
ticket = expr1 | expr2
|
148
|
+
|
149
|
+
|
150
|
+
Or, logically, ((Mon <b>OR</b> Wed <b>OR</b> Fri) <b>AND</b> (8am to
|
151
|
+
11am)) <b>OR</b> ((Tues OR Thurs) <b>AND</b> (11:30am to 2pm))
|
152
|
+
|
153
|
+
|
154
|
+
Let's re-write this without all the noise:
|
155
|
+
|
156
|
+
|
157
|
+
expr1 = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
|
158
|
+
|
159
|
+
expr2 = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
|
160
|
+
|
161
|
+
ticket = expr1 | expr2
|
162
|
+
|
163
|
+
|
164
|
+
ticket.include?( DateTime.new(2004,3,11,12,15) ) # => true
|
165
|
+
|
166
|
+
ticket.include?( DateTime.new(2004,3,10,9,15) ) # => true
|
167
|
+
|
168
|
+
ticket.include?( DateTime.new(2004,3,10,8,00) ) # => true
|
169
|
+
|
170
|
+
ticket.include?( DateTime.new(2004,3,11,1,15) ) # => false
|
171
|
+
|
172
|
+
|
173
|
+
Sigh...now if I can only get my dad to remember this...
|
174
|
+
|
175
|
+
|
176
|
+
These are simple examples, but they demonstrate how temporal
|
177
|
+
expressions can be used instead of an enumerated list of date values
|
178
|
+
to define patterns of recurrence. There are many other temporal
|
179
|
+
expressions, and, more importantly, once you get the hang of it, it's
|
180
|
+
easy to write your own.
|
181
|
+
|
182
|
+
Fowler's paper[http://martinfowler.com/apsupp/recurring.pdf] also goes
|
183
|
+
on to describe another element of this pattern: the <tt>Schedule</tt>.
|
184
|
+
See the schedule tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_schedule_rdoc.html] for details.
|
185
|
+
|
186
|
+
<em>See Also:</em>
|
187
|
+
|
188
|
+
* Fowler's recurring event pattern[http://martinfowler.com/apsupp/recurring.pdf]
|
189
|
+
|
190
|
+
* Other temporal patterns[http://martinfowler.com/ap2/timeNarrative.html]
|