runt 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,120 +1,119 @@
1
- # Rakefile for runt -*- ruby -*-
2
-
3
- begin
4
- require 'rubygems'
5
- require 'rake/gempackagetask'
6
- rescue Exception
7
- nil
8
- end
9
- require 'rake'
10
- require 'rake/clean'
11
- require 'rake/testtask'
12
- require 'rake/rdoctask'
13
- require 'rake/contrib/sshpublisher'
14
- require 'rake/contrib/rubyforgepublisher'
15
- require 'fileutils'
16
-
17
- #####################################################################
18
- # Constants
19
- #####################################################################
20
-
21
- # Build Settings
22
- PKG_VERSION = "0.3.0"
23
-
24
- # Files to be included in Runt distribution
25
- PKG_FILES = FileList[
26
- 'setup.rb',
27
- '[A-Z]*',
28
- 'lib/**/*.rb',
29
- 'test/**/*.rb',
30
- 'doc/**/*',
31
- 'site/**/*'
32
- ].exclude("*.ses")
33
-
34
- if(RUBY_PLATFORM =~ /win32/i)
35
- PKG_EXEC_TAR = false
36
- else
37
- PKG_EXEC_TAR = true
38
- end
39
-
40
- # build directory
41
- TARGET_DIR = "target"
42
-
43
- #####################################################################
44
- # Targets
45
- #####################################################################
46
-
47
- task :default => [:test]
48
- task :clobber => [:clobber_build_dir]
49
-
50
- # Make the build directory
51
- directory TARGET_DIR
52
-
53
- desc "Clobber the entire build directory."
54
- task :clobber_build_dir do |t|
55
- puts "It's clobberin' time! (hello from task #{t.name})"
56
- CLOBBER.include(TARGET_DIR)
57
- end
58
-
59
- Rake::RDocTask.new do |rd|
60
- rd.rdoc_dir="#{TARGET_DIR}/doc"
61
- rd.options << "-S"
62
- rd.rdoc_files.include('lib/**/*.rb', 'doc/**/*.rdoc','[A-Z]*')
63
- rd.rdoc_files.exclude('test/*.rb','[A-Z]*.ses','Rakefile')
64
- end
65
-
66
- Rake::TestTask.new do |t|
67
- t.libs << "test"
68
- t.pattern = 'test/alltests.rb'
69
- t.verbose = false
70
- end
71
-
72
- desc "Copy html files for the Runt website to the build directory."
73
- file "copy_site" => TARGET_DIR
74
- file "copy_site" do
75
- cp_r Dir["site/*.{html,gif,png,css}"], TARGET_DIR
76
- end
77
-
78
- desc "Publish the Documentation to RubyForge."
79
- task :publish => [:rdoc,:copy_site,:clobber_package] do |t|
80
- publisher = Rake::CompositePublisher.new
81
- publisher.add Rake::SshDirPublisher.new("mlipper@rubyforge.org", "/var/www/gforge-projects/runt",TARGET_DIR)
82
- publisher.upload
83
- end
84
-
85
- desc "Publish the Documentation to the build dir."
86
- task :test_publish => [:rdoc,:copy_site,:clobber_package] do |t|
87
- puts "YAY! We've tested publish! YAY!"
88
- end
89
-
90
-
91
- if ! defined?(Gem)
92
- puts "Package Target requires RubyGEMs"
93
- else
94
- spec = Gem::Specification.new do |s|
95
- s.platform = Gem::Platform::RUBY
96
- s.summary = "Ruby Temporal Expressions."
97
- s.name = 'runt'
98
- s.version = PKG_VERSION
99
- s.requirements << 'none'
100
- s.require_path = 'lib'
101
- s.autorequire = 'runt'
102
- s.files = PKG_FILES.to_a
103
- s.author = 'Matthew Lipper'
104
- s.email = 'matt@digitalclash.com'
105
- s.homepage = 'http://runt.rubyforge.org'
106
- s.has_rdoc = true
107
- s.test_files = Dir['test/*test.rb']
108
- s.rubyforge_project = 'runt'
109
- s.description = <<EOF
110
- Runt is a Ruby version of temporal patterns by
111
- Martin Fowler. Runt provides an API for scheduling
112
- recurring events using set-like semantics.
113
- EOF
114
- end
115
-
116
- Rake::GemPackageTask.new(spec) do |pkg|
117
- pkg.need_zip = true
118
- pkg.need_tar = PKG_EXEC_TAR
119
- end
120
- end
1
+ # Rakefile for runt -*- ruby -*-
2
+
3
+ begin
4
+ require 'rubygems'
5
+ require 'rake/gempackagetask'
6
+ rescue Exception
7
+ nil
8
+ end
9
+ require 'rake'
10
+ require 'rake/clean'
11
+ require 'rake/testtask'
12
+ require 'rake/rdoctask'
13
+ require 'rake/contrib/sshpublisher'
14
+ require 'rake/contrib/rubyforgepublisher'
15
+ require 'fileutils'
16
+
17
+ #####################################################################
18
+ # Constants
19
+ #####################################################################
20
+
21
+ # Build Settings
22
+ PKG_VERSION = "0.5.0"
23
+
24
+ # Files to be included in Runt distribution
25
+ PKG_FILES = FileList[
26
+ 'setup.rb',
27
+ '[A-Z]*',
28
+ 'lib/**/*.rb',
29
+ 'test/**/*.rb',
30
+ 'doc/**/*',
31
+ 'site/**/*'
32
+ ].exclude("*.ses")
33
+
34
+ if(RUBY_PLATFORM =~ /win32/i)
35
+ PKG_EXEC_TAR = false
36
+ else
37
+ PKG_EXEC_TAR = true
38
+ end
39
+
40
+ # build directory
41
+ TARGET_DIR = "target"
42
+
43
+ #####################################################################
44
+ # Targets
45
+ #####################################################################
46
+
47
+ task :default => [:test]
48
+ task :clobber => [:clobber_build_dir]
49
+
50
+ # Make the build directory
51
+ directory TARGET_DIR
52
+
53
+ # Calling this task directly doesn't work?
54
+ desc "Clobber the entire build directory."
55
+ task :clobber_build_dir do |t|
56
+ CLOBBER.include(TARGET_DIR)
57
+ end
58
+
59
+ Rake::RDocTask.new do |rd|
60
+ rd.rdoc_dir="#{TARGET_DIR}/doc"
61
+ rd.options << "-S"
62
+ rd.rdoc_files.include('lib/*','doc/*.rdoc','README','CHANGES','TODO','LICENSE.txt')
63
+ end
64
+
65
+ Rake::TestTask.new do |t|
66
+ t.libs << "test"
67
+ t.pattern = 'test/*test.rb'
68
+ t.verbose = false
69
+ end
70
+
71
+ desc "Copy html files for the Runt website to the build directory."
72
+ file "copy_site" => TARGET_DIR
73
+ file "copy_site" do
74
+ cp_r Dir["site/*.{html,gif,png,css}"], TARGET_DIR
75
+ end
76
+
77
+ desc "Publish the Documentation to RubyForge."
78
+ task :publish => [:rdoc,:copy_site,:clobber_package] do |t|
79
+ publisher = Rake::CompositePublisher.new
80
+ publisher.add Rake::SshDirPublisher.new("mlipper@rubyforge.org", "/var/www/gforge-projects/runt",TARGET_DIR)
81
+ publisher.upload
82
+ end
83
+
84
+ desc "Publish the Documentation to the build dir."
85
+ task :test_publish => [:rdoc,:copy_site,:clobber_package] do |t|
86
+ puts "YAY! We've tested publish! YAY!"
87
+ end
88
+
89
+
90
+ if ! defined?(Gem)
91
+ puts "Package Target requires RubyGEMs"
92
+ else
93
+ spec = Gem::Specification.new do |s|
94
+ s.platform = Gem::Platform::RUBY
95
+ s.summary = "Ruby Temporal Expressions."
96
+ s.name = 'runt'
97
+ s.version = PKG_VERSION
98
+ s.requirements << 'none'
99
+ s.require_path = 'lib'
100
+ s.autorequire = 'runt'
101
+ s.files = PKG_FILES.to_a
102
+ s.author = 'Matthew Lipper'
103
+ s.email = 'mlipper@gmail.com'
104
+ s.homepage = 'http://runt.rubyforge.org'
105
+ s.has_rdoc = true
106
+ s.test_files = Dir['test/*test.rb']
107
+ s.rubyforge_project = 'runt'
108
+ s.description = <<EOF
109
+ Runt is a Ruby version of temporal patterns by
110
+ Martin Fowler. Runt provides an API for scheduling
111
+ recurring events using set-like semantics.
112
+ EOF
113
+ end
114
+
115
+ Rake::GemPackageTask.new(spec) do |pkg|
116
+ pkg.need_zip = true
117
+ pkg.need_tar = PKG_EXEC_TAR
118
+ end
119
+ end
data/TODO CHANGED
@@ -1,20 +1,11 @@
1
- = Runt - Ruby Temporal Expressions -- To Do List
2
-
3
- Send suggestions, questions, threats, etc. for this list to Matt[mailto:matt@digitalclash.com]
4
-
5
- === To Do
6
-
7
- * Schedule tutorial
8
-
9
- * Example usage with Rails
10
-
11
- * Persistence strategy:
12
- - Marshall
13
- - database...create storable grammar, expression stack?
14
-
15
- * Fix REWeek so that ordinal day values where the end day < start day does not fail
16
-
17
- * Make Schedule a Module instead of a class
18
-
19
- * Address performance issues due to implementation (in particular, PDate#<=>)
20
-
1
+ = Runt - Ruby Temporal Expressions -- To Do List
2
+
3
+ Send suggestions, questions, threats, etc. for this list to Matt[mailto:mlipper@gmail.com]
4
+
5
+ === To Do
6
+
7
+ * Tutorials
8
+
9
+ * Better examples
10
+
11
+ * Laundry
@@ -1,51 +1,84 @@
1
- = Schedule Tutorial
2
-
3
- If you haven't done so already, took a look the temporal expression
4
- tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_te_rdoc.html].
5
-
6
- So, you've defined some temporal expressions, now what? In his
7
- paper[http://martinfowler.com/apsupp/recurring.pdf] about recurring events,
8
- Martin Fowler also discusses a simple schedule API which is used, surprisingly
9
- enough, to build a schedule.
10
-
11
- We're not going to cover the pattern itself in this tutorial as Fowler already
12
- does a nice job. Luckily, because it is such a simple pattern (once you invent
13
- it!), you'll be able understand it even if you decide not to skim the
14
- aforementioned paper[http://martinfowler.com/apsupp/recurring.pdf].
15
-
16
- In the last tutorial we learned about the exciting world of NYC street cleaning
17
- regulations. For the uptown side of my block, the sign says:
18
-
19
- #############################
20
- # #
21
- # NO PARKING #
22
- # #
23
- # Mon, Wed, Fri 8am-11am #
24
- # #
25
- # T,Th 11:30am-2:00pm #
26
- # #
27
- # Violators will be towed! #
28
- # #
29
- #############################
30
- # #
31
- # #
32
- # #
33
-
34
- We created a temporal expression to match this time period, like so:
35
-
36
- expr1 = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
37
-
38
- expr2 = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
39
-
40
- ticket = expr1 | expr2
41
-
42
-
43
-
44
- =.....FINNISH ME!......
45
-
46
-
47
- <em>See Also:</em>
48
-
49
- * Fowler's recurring event pattern[http://martinfowler.com/apsupp/recurring.pdf]
50
-
51
- * Other temporal patterns[http://martinfowler.com/ap2/timeNarrative.html]
1
+ = Schedule Tutorial
2
+
3
+ If you haven't done so already, took a look the temporal expression
4
+ tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_te_rdoc.html].
5
+
6
+ So, you've defined some temporal expressions, now what? In his
7
+ paper[http://martinfowler.com/apsupp/recurring.pdf] about recurring events,
8
+ Martin Fowler also discusses a simple schedule API which is used, surprisingly
9
+ enough, to build a schedule.
10
+
11
+ We're not going to cover the pattern itself in this tutorial as Fowler already
12
+ does a nice job. Luckily, because it is such a simple pattern (once you invent
13
+ it!), you'll be able understand it even if you decide not to skim the
14
+ aforementioned paper[http://martinfowler.com/apsupp/recurring.pdf].
15
+
16
+ In the last tutorial we learned about the exciting world of NYC street cleaning
17
+ regulations. For the uptown side of my block, the sign says:
18
+
19
+ #############################
20
+ # #
21
+ # NO PARKING #
22
+ # #
23
+ # Mon, Wed, Fri 8am-11am #
24
+ # #
25
+ # T,Th 11:30am-2:00pm #
26
+ # #
27
+ # Violators will be towed! #
28
+ # #
29
+ #############################
30
+ # #
31
+ # #
32
+ # #
33
+
34
+ We created a temporal expression to match this time period, like so:
35
+
36
+ expr1=(DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
37
+
38
+ expr2=(DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
39
+
40
+ ticket=expr1 | expr2
41
+
42
+ What we need at this point is a way to associate this expression with the rest
43
+ of our domain model. For this purpose, we can use a Schedule and an associated
44
+ Event both of which are supplied by Runt.
45
+
46
+ A Schedule holds zero or more Event/TemporalExpression pairs, allowing clients
47
+ to easily query and update TemporalExpressions as well perform certain range
48
+ operations as we will see in a moment.
49
+
50
+ An Event is simply a container for domain data. Although Runt uses Events
51
+ by default, Schedules will happily house any kind of Object. Internally, a
52
+ Schedule is really just a Hash where the keys are whatever it is you are
53
+ scheduling and the values are the TemporalExpressions you create.
54
+
55
+
56
+ def add(obj, expression)
57
+ @elems[obj]=expression
58
+ end
59
+
60
+
61
+ For the remainder of this tutorial I will refer to Events specifically, but
62
+ just remember that anything reasonably Hash-keyable will do.
63
+
64
+
65
+ So, let's pretend that I own a car. Since I don't want to get a ticket, I
66
+ decide to create a little cron-like utility daemon to remind myself whenever
67
+ it's time for me to move it.
68
+
69
+ class Ron
70
+
71
+
72
+ def run
73
+ loop
74
+
75
+ etc...
76
+
77
+ =.....FINISH ME!......
78
+
79
+
80
+ <em>See Also:</em>
81
+
82
+ * Fowler's recurring event pattern[http://martinfowler.com/apsupp/recurring.pdf]
83
+
84
+ * Other temporal patterns[http://martinfowler.com/ap2/timeNarrative.html]
@@ -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 expr.include?(Date.new(2004,3,18)) #Thurs 3/18/04 => true
46
- 14
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 11, 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 expr.include?(Date.new(2004,3,18)) #Thurs 3/18/04 => true
46
+ 14
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 11, 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]