runt 0.3.0 → 0.5.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/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]