runt 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +48 -0
- data/LICENSE.txt +44 -0
- data/README +88 -0
- data/Rakefile +117 -0
- data/TODO +19 -0
- data/doc/tutorial_schedule.rdoc +51 -0
- data/doc/tutorial_te.rdoc +190 -0
- data/lib/runt.rb +93 -0
- data/lib/runt/daterange.rb +74 -0
- data/lib/runt/dprecision.rb +137 -0
- data/lib/runt/pdate.rb +127 -0
- data/lib/runt/schedule.rb +89 -0
- data/lib/runt/temporalexpression.rb +467 -0
- data/setup.rb +1331 -0
- data/site/blue-robot3.css +132 -0
- data/site/dcl-small.gif +0 -0
- data/site/index.html +92 -0
- data/site/logohover.png +0 -0
- data/site/runt-logo.gif +0 -0
- data/site/runt-logo.psd +0 -0
- data/test/alltests.rb +12 -0
- data/test/daterangetest.rb +82 -0
- data/test/dprecisiontest.rb +46 -0
- data/test/pdatetest.rb +106 -0
- data/test/scheduletest.rb +56 -0
- data/test/temporalexpressiontest.rb +282 -0
- metadata +62 -0
data/CHANGES
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= Runt Changelog
|
2
|
+
|
3
|
+
== Version 0.2.0
|
4
|
+
|
5
|
+
* Fixed Schedule class
|
6
|
+
* Renamed Schedule#is_occurring? to Schedule#include?
|
7
|
+
* RAA deployment
|
8
|
+
* GEM deployment
|
9
|
+
* Renamed file dateprecisiontest.rb to dprecisiontest.rb
|
10
|
+
* Renamed several methods on PDate:
|
11
|
+
- second -> sec
|
12
|
+
- minute -> min
|
13
|
+
- hour_of_day -> hour
|
14
|
+
- day_of_month -> day
|
15
|
+
|
16
|
+
== Version 0.1.0
|
17
|
+
|
18
|
+
* Inspired by suggestions[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/95465] on c.l.r., did massive renaming and refactoring to make source more Ruby-esque:
|
19
|
+
|
20
|
+
- TemporalExpression class => TExpr
|
21
|
+
- DatePrecision module => DPrecision (file name changed also)
|
22
|
+
- TimePoint class => PDate (file name changed also)
|
23
|
+
- Added transparent use of bitwise set operators ( & , | ) for building composite expressions
|
24
|
+
- Added transparent use of - operator for diff expressions
|
25
|
+
|
26
|
+
* Updated TE Tutorial to reflect new and improved syntax usage
|
27
|
+
* Skipped several version numbers to celebrate
|
28
|
+
|
29
|
+
== Version 0.0.6
|
30
|
+
|
31
|
+
* TE Tutorial
|
32
|
+
* Website beautification
|
33
|
+
* Credits
|
34
|
+
|
35
|
+
== Version 0.0.4
|
36
|
+
|
37
|
+
* Improved Rake[http://rake.rubyforge.org] support
|
38
|
+
* Better documentation: README, TODO, CHANGES, etc.
|
39
|
+
* More Ruby-like source code layout and code organization inspired by Rake[http://rake.rubyforge.org] distribution
|
40
|
+
* Dropped the obviously superfluous 'alpha' from versioning
|
41
|
+
|
42
|
+
== Version 0.0.1
|
43
|
+
|
44
|
+
* Learned Ruby (or began trying, at any rate...)
|
45
|
+
* RubyForge project setup
|
46
|
+
* Basic implementation of the Java-based chronicJ[http://chronicJ.org] functionality in Ruby
|
47
|
+
|
48
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
The Apache Software License, Version 1.1
|
2
|
+
|
3
|
+
Copyright (c) 2003-2004 Digital Clash LLC. All rights
|
4
|
+
reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
7
|
+
modification, are permitted provided that the following conditions
|
8
|
+
are met:
|
9
|
+
|
10
|
+
1. Redistributions of source code must retain the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer.
|
12
|
+
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright
|
14
|
+
notice, this list of conditions and the following disclaimer in
|
15
|
+
the documentation and/or other materials provided with the
|
16
|
+
distribution.
|
17
|
+
|
18
|
+
3. The end-user documentation included with the redistribution,
|
19
|
+
if any, must include the following acknowledgment:
|
20
|
+
"This product includes software developed by the
|
21
|
+
Runt team (http://runt.rubyforge.org/)."
|
22
|
+
Alternately, this acknowledgment may appear in the software itself,
|
23
|
+
if and wherever such third-party acknowledgments normally appear.
|
24
|
+
|
25
|
+
4. The names "Runt" and "Digital Clash" not be used to endorse or
|
26
|
+
promote products derived from this software without prior written
|
27
|
+
permission. For written permission, please contact
|
28
|
+
info@digitalclash.com.
|
29
|
+
|
30
|
+
5. Products derived from this software may not be called "Runt",
|
31
|
+
"Digital Clash", nor may "Runt" or "Digital Clash" appear in
|
32
|
+
their name, without prior written permission of Digital Clash LLC.
|
33
|
+
|
34
|
+
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
35
|
+
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
36
|
+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
37
|
+
DISCLAIMED. IN NO EVENT SHALL DIGITAL CLASH LLC OR ITS CONTRIBUTORS
|
38
|
+
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
39
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
40
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
41
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
42
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
43
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
44
|
+
THE POSSIBILITY OF SUCH DAMAGE.
|
data/README
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
= RUNT -- Ruby Temporal Expressions
|
2
|
+
|
3
|
+
* Runt is a Ruby[http://www.ruby-lang.org/en/] implementation of select Martin Fowler patterns[http://www.martinfowler.com/articles].
|
4
|
+
|
5
|
+
* <em>TemporalExpression</em>s allow a developer to define patterns of date recurrence using set expressions.
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
= INSTALL
|
10
|
+
|
11
|
+
* Unpack the Runt distribution.
|
12
|
+
|
13
|
+
$ tar -xzvf runt-<version>.tar.gz
|
14
|
+
|
15
|
+
* cd $UNPACK_DIR/runt/
|
16
|
+
|
17
|
+
* execute:
|
18
|
+
|
19
|
+
$ ruby setup.rb config
|
20
|
+
$ ruby setup.rb setup
|
21
|
+
$ ruby setup.rb install (may require root privilege)
|
22
|
+
|
23
|
+
<b>or</b>
|
24
|
+
|
25
|
+
$ ruby setup.rb --help
|
26
|
+
|
27
|
+
for more options.
|
28
|
+
|
29
|
+
---
|
30
|
+
|
31
|
+
= QUICK START
|
32
|
+
|
33
|
+
* require 'runt'
|
34
|
+
|
35
|
+
* See http://runt.rubyforge.org/doc/index.html
|
36
|
+
|
37
|
+
* See $UNPACK_DIR/runt/test/*.rb for example usage.
|
38
|
+
|
39
|
+
* See this mini-TemporalExpression tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_te_rdoc.html]
|
40
|
+
|
41
|
+
* See http://www.chronicj.org/apidocs/index.html for better commented source (JavaDoc)
|
42
|
+
|
43
|
+
Get in touch if you have questions or if Runt causes your computer to burst into flames...
|
44
|
+
|
45
|
+
Matt[mailto:matt@digitalclash.com]
|
46
|
+
|
47
|
+
---
|
48
|
+
|
49
|
+
== Credits
|
50
|
+
|
51
|
+
Rubyforge[http://rubyforge.org] for hosting this project.
|
52
|
+
|
53
|
+
M.Fowler[http://martinfowler.com], Matz[http://ruby-lang.org],
|
54
|
+
T.Funaba (Date/DateTime[http://www.funaba.org/en/ruby.html]),
|
55
|
+
J.Weirich (Rake[http://rake.rubyforge.org]),
|
56
|
+
PragmaticProgrammers[http://pragmaticprogrammer.com], and everyone on
|
57
|
+
ruby-talk[mailto:ruby-talk@ruby-lang.org] for their shameless public display
|
58
|
+
of smartness.
|
59
|
+
|
60
|
+
Hal Fulton, Mauricio Fernandez, and Mark Hubbart for the
|
61
|
+
thread[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/95465] on c.l.r.
|
62
|
+
that inspired a more Ruby-esque syntax for creating TE's. 2X Thanks to Hal, for
|
63
|
+
taking the time to exchange ideas off-line.
|
64
|
+
|
65
|
+
The ruby-nyc user's group for their suggestions and feedback.
|
66
|
+
|
67
|
+
BlueRobot[http://www.bluerobot.com/web/layouts/] for the CSS used to prettify the Runt website.
|
68
|
+
|
69
|
+
The number 12, and the letters E and J.
|
70
|
+
|
71
|
+
---
|
72
|
+
|
73
|
+
= Etc...
|
74
|
+
|
75
|
+
Author:: Matthew Lipper <matt@digitalclash.com>
|
76
|
+
Requires:: Ruby 1.8.0 or later, Date/DateTime classes
|
77
|
+
License:: Copyright 2004 by Digital Clash LLC.
|
78
|
+
Released under the Apache Software license (see LICENSE.txt)
|
79
|
+
included in the distribution.
|
80
|
+
|
81
|
+
= Warranty
|
82
|
+
|
83
|
+
This software is provided "as is" and without any express or
|
84
|
+
implied warranties, including, without limitation, the implied
|
85
|
+
warranties of merchantibility and fitness for a particular
|
86
|
+
purpose.
|
87
|
+
|
88
|
+
link://../dcl-small.gif
|
data/Rakefile
ADDED
@@ -0,0 +1,117 @@
|
|
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/packagetask'
|
14
|
+
|
15
|
+
require 'rake/contrib/sshpublisher'
|
16
|
+
require 'rake/contrib/rubyforgepublisher'
|
17
|
+
require 'fileutils'
|
18
|
+
|
19
|
+
# Build Settings
|
20
|
+
PKG_VERSION = "0.2.0"
|
21
|
+
# 'gem_install_bin.rb',
|
22
|
+
# Files to be included in Runt distribution
|
23
|
+
PKG_FILES = FileList[
|
24
|
+
'setup.rb',
|
25
|
+
'[A-Z]*',
|
26
|
+
'lib/**/*.rb',
|
27
|
+
'test/**/*.rb',
|
28
|
+
'doc/**/*',
|
29
|
+
'site/**/*'
|
30
|
+
].exclude("*.ses")
|
31
|
+
|
32
|
+
# build directory
|
33
|
+
TARGET_DIR = "target"
|
34
|
+
|
35
|
+
# Targets
|
36
|
+
task :default => [:test]
|
37
|
+
task :clobber => [:clobber_build_dir]
|
38
|
+
|
39
|
+
# Make the build directory
|
40
|
+
directory TARGET_DIR
|
41
|
+
|
42
|
+
desc "Clobber the entire build directory."
|
43
|
+
task :clobber_build_dir do |t|
|
44
|
+
puts "It's clobberin' time! (hello from task #{t.name})"
|
45
|
+
CLOBBER.include(TARGET_DIR)
|
46
|
+
end
|
47
|
+
|
48
|
+
Rake::RDocTask.new do |rd|
|
49
|
+
rd.rdoc_dir="#{TARGET_DIR}/doc"
|
50
|
+
rd.options << "-S"
|
51
|
+
rd.rdoc_files.include('lib/**/*.rb', 'doc/**/*.rdoc','[A-Z]*')
|
52
|
+
rd.rdoc_files.exclude('test/*.rb','[A-Z]*.ses','Rakefile')
|
53
|
+
end
|
54
|
+
|
55
|
+
Rake::TestTask.new do |t|
|
56
|
+
t.libs << "test"
|
57
|
+
t.pattern = 'test/alltests.rb'
|
58
|
+
t.verbose = true
|
59
|
+
end
|
60
|
+
|
61
|
+
Rake::PackageTask.new("runt", PKG_VERSION) do |p|
|
62
|
+
p.package_dir="#{TARGET_DIR}/#{p.package_dir}"
|
63
|
+
p.need_tar = true
|
64
|
+
p.need_zip = true
|
65
|
+
p.package_files.include(PKG_FILES)
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Copy html files for the Runt website to the build directory."
|
69
|
+
file "copy_site" => TARGET_DIR
|
70
|
+
file "copy_site" do
|
71
|
+
cp_r Dir["site/*.{html,gif,png,css}"], TARGET_DIR
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Publish the Documentation to RubyForge."
|
75
|
+
task :publish => [:rdoc,:copy_site,:clobber_package] do |t|
|
76
|
+
publisher = Rake::CompositePublisher.new
|
77
|
+
publisher.add Rake::SshDirPublisher.new("mlipper@rubyforge.org", "/var/www/gforge-projects/runt",TARGET_DIR)
|
78
|
+
publisher.upload
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "Publish the Documentation to the build dir."
|
82
|
+
task :test_publish => [:rerdoc,:copy_site,:clobber_package] do |t|
|
83
|
+
puts "YAY! We've tested publish! YAY!"
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
if ! defined?(Gem)
|
88
|
+
puts "Package Target requires RubyGEMs"
|
89
|
+
else
|
90
|
+
spec = Gem::Specification.new do |s|
|
91
|
+
s.platform = Gem::Platform::RUBY
|
92
|
+
s.summary = "Ruby Temporal Expressions."
|
93
|
+
s.name = 'runt'
|
94
|
+
s.version = PKG_VERSION
|
95
|
+
s.requirements << 'none'
|
96
|
+
s.require_path = 'lib'
|
97
|
+
s.autorequire = 'runt'
|
98
|
+
s.files = PKG_FILES.to_a
|
99
|
+
s.author = 'Matthew Lipper'
|
100
|
+
s.email = 'matt@digitalclash.com'
|
101
|
+
s.homepage = 'http://runt.rubyforge.org'
|
102
|
+
s.has_rdoc = true
|
103
|
+
# s.rdoc_files = rd.rdoc_files
|
104
|
+
# s.rdoc_options = rd.option_list
|
105
|
+
s.rubyforge_project = 'runt'
|
106
|
+
s.description = <<EOF
|
107
|
+
Runt is a Ruby version of temporal patterns by
|
108
|
+
Martin Fowler. Runt provides an API for scheduling
|
109
|
+
recurring events using set-like semantics.
|
110
|
+
EOF
|
111
|
+
end
|
112
|
+
|
113
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
114
|
+
pkg.need_zip = true
|
115
|
+
pkg.need_tar = true
|
116
|
+
end
|
117
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,19 @@
|
|
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
|
+
* Fix REWeek so that ordinal day values where the end day < start day does not fail
|
8
|
+
|
9
|
+
* Make Schedule a Module instead of a class
|
10
|
+
|
11
|
+
* Schedule tutorial
|
12
|
+
|
13
|
+
* Better RDoc's which include usage examples
|
14
|
+
|
15
|
+
* Properly implement/test Range functionality in DateRange
|
16
|
+
|
17
|
+
* Address performance issues due to implementation (in particular, PDate#<=>)
|
18
|
+
|
19
|
+
* Use RDoc properly so Runt website and README don't cause DRY violations
|
@@ -0,0 +1,51 @@
|
|
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]
|
@@ -0,0 +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/schedule_te.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]
|