runt 0.2.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/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/lib/runt.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# :title:Runt -- Ruby Temporal Expressions
|
4
|
+
#
|
5
|
+
# == Runt -- Ruby Temporal Expressions
|
6
|
+
#
|
7
|
+
# The usage and design patterns expressed in this library are mostly...*uhm*..
|
8
|
+
# <em>entirely</em>..*cough*...based on a series of
|
9
|
+
# <tt>articles</tt>[http://www.martinfowler.com] by Martin Fowler.
|
10
|
+
#
|
11
|
+
# It highly recommended that anyone using Runt (or writing
|
12
|
+
# object-oriented software :) take a moment to peruse the wealth of useful info
|
13
|
+
# that Fowler has made publicly available:
|
14
|
+
#
|
15
|
+
# * An excellent introductory summation of temporal <tt>patterns</tt>[http://martinfowler.com/ap2/timeNarrative.html]
|
16
|
+
# * Recurring event <tt>pattern</tt>[http://martinfowler.com/apsupp/recurring.pdf]
|
17
|
+
#
|
18
|
+
# Also, for those of you (like me, for example) still chained in your cubicle and forced
|
19
|
+
# to write <tt>Java</tt>[http://java.sun.com] code, check out the original version of
|
20
|
+
# project called <tt>ChronicJ</tt>[http://chronicj.org].
|
21
|
+
#
|
22
|
+
# ---
|
23
|
+
# Author:: Matthew Lipper (mailto:info@digitalclash.com)
|
24
|
+
# Copyright:: Copyright (c) 2004 Digital Clash, LLC
|
25
|
+
# License:: See LICENSE.txt
|
26
|
+
#
|
27
|
+
# = Warranty
|
28
|
+
#
|
29
|
+
# This software is provided "as is" and without any express or
|
30
|
+
# implied warranties, including, without limitation, the implied
|
31
|
+
# warranties of merchantibility and fitness for a particular
|
32
|
+
# purpose.
|
33
|
+
|
34
|
+
require 'date'
|
35
|
+
require 'date/format'
|
36
|
+
require "runt/dprecision"
|
37
|
+
require "runt/pdate"
|
38
|
+
require "runt/temporalexpression"
|
39
|
+
require "runt/schedule"
|
40
|
+
require "runt/daterange"
|
41
|
+
|
42
|
+
#
|
43
|
+
# The Runt module is the main namespace for all Runt modules and classes. Using
|
44
|
+
# require statements, it makes the entire Runt library available.It also
|
45
|
+
# defines some new constants and exposes some already defined in the standard
|
46
|
+
# library classes <tt>Date</tt> and <tt>DateTime</tt>.
|
47
|
+
#
|
48
|
+
# <b>See also</b> date.rb
|
49
|
+
#
|
50
|
+
module Runt
|
51
|
+
|
52
|
+
#Yes it's true, I'm a big idiot!
|
53
|
+
Sunday = Date::DAYNAMES.index("Sunday")
|
54
|
+
Monday = Date::DAYNAMES.index("Monday")
|
55
|
+
Tuesday = Date::DAYNAMES.index("Tuesday")
|
56
|
+
Wednesday = Date::DAYNAMES.index("Wednesday")
|
57
|
+
Thursday = Date::DAYNAMES.index("Thursday")
|
58
|
+
Friday = Date::DAYNAMES.index("Friday")
|
59
|
+
Saturday = Date::DAYNAMES.index("Saturday")
|
60
|
+
Sun = Date::ABBR_DAYNAMES.index("Sun")
|
61
|
+
Mon = Date::ABBR_DAYNAMES.index("Mon")
|
62
|
+
Tue = Date::ABBR_DAYNAMES.index("Tue")
|
63
|
+
Wed = Date::ABBR_DAYNAMES.index("Wed")
|
64
|
+
Thu = Date::ABBR_DAYNAMES.index("Thu")
|
65
|
+
Fri = Date::ABBR_DAYNAMES.index("Fri")
|
66
|
+
Sat = Date::ABBR_DAYNAMES.index("Sat")
|
67
|
+
First = 1
|
68
|
+
Second = 2
|
69
|
+
Third = 3
|
70
|
+
Fourth = 4
|
71
|
+
Fifth = 5
|
72
|
+
Sixth = 6
|
73
|
+
Seventh = 7
|
74
|
+
Eigth = 8
|
75
|
+
Ninth = 9
|
76
|
+
Tenth = 10
|
77
|
+
|
78
|
+
private
|
79
|
+
class ApplyLast #:nodoc:
|
80
|
+
def initialize()
|
81
|
+
@negate=Proc.new{|n| n*-1}
|
82
|
+
end
|
83
|
+
def [](arg)
|
84
|
+
@negate.call(arg)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
public
|
89
|
+
Last = ApplyLast.new
|
90
|
+
Last_of = Last[First]
|
91
|
+
Second_to_last = Last[Second]
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'runt'
|
5
|
+
|
6
|
+
|
7
|
+
module Runt
|
8
|
+
# :title:DateRange
|
9
|
+
# == DateRange
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# Based the <tt>range</tt>[http://martinfowler.com/ap2/range.html] pattern by Martin Fowler.
|
13
|
+
#
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# Author:: Matthew Lipper
|
17
|
+
class DateRange < Range
|
18
|
+
|
19
|
+
include DPrecision
|
20
|
+
|
21
|
+
attr_reader :start_expr, :end_expr
|
22
|
+
|
23
|
+
def initialize(start_expr, end_expr,exclusive=false)
|
24
|
+
super(start_expr, end_expr,exclusive)
|
25
|
+
@start_expr, @end_expr = start_expr, end_expr
|
26
|
+
end
|
27
|
+
|
28
|
+
def include?(obj)
|
29
|
+
return super(obj.min) && super(obj.max) if obj.kind_of? Range
|
30
|
+
return super(obj)
|
31
|
+
end
|
32
|
+
|
33
|
+
def overlap?(obj)
|
34
|
+
return true if( include?(obj.min) || include?(obj.max) )
|
35
|
+
return true if( obj.kind_of?(Range) && obj.include?(self) )
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def empty?
|
40
|
+
return @start_expr>@end_expr
|
41
|
+
end
|
42
|
+
|
43
|
+
def gap(obj)
|
44
|
+
|
45
|
+
return EMPTY if self.overlap? obj
|
46
|
+
|
47
|
+
lower=nil
|
48
|
+
higher=nil
|
49
|
+
|
50
|
+
if((self<=>obj)<0)
|
51
|
+
lower=self
|
52
|
+
higher=obj
|
53
|
+
else
|
54
|
+
lower=obj
|
55
|
+
higher=self
|
56
|
+
end
|
57
|
+
|
58
|
+
return DateRange.new((lower.end_expr+1),(higher.start_expr-1))
|
59
|
+
end
|
60
|
+
|
61
|
+
def <=>(other)
|
62
|
+
return @start_expr <=> other.start_expr if(@start_expr != other.start_expr)
|
63
|
+
return @end_expr <=> other.end_expr
|
64
|
+
end
|
65
|
+
|
66
|
+
def min; @start_expr end
|
67
|
+
def max; @end_expr end
|
68
|
+
def to_s; @start_expr.to_s + " " + @end_expr.to_s end
|
69
|
+
|
70
|
+
|
71
|
+
EMPTY = DateRange.new(PDate.day(2004,2,2),PDate.day(2004,2,1))
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'runt'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
module Runt
|
7
|
+
|
8
|
+
# :title:DPrecision
|
9
|
+
# == DPrecision
|
10
|
+
# Module providing automatic precisioning of Date, DateTime, and PDate classes.
|
11
|
+
#
|
12
|
+
# Inspired by a <tt>pattern</tt>[http://martinfowler.com/ap2/timePoint.html] by Martin Fowler.
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# Author:: Matthew Lipper
|
16
|
+
module DPrecision
|
17
|
+
|
18
|
+
def DPrecision.to_p(date,prec=DEFAULT)
|
19
|
+
|
20
|
+
case prec
|
21
|
+
when MIN then PDate.min(*DPrecision.explode(date,prec))
|
22
|
+
when DAY then PDate.day(*DPrecision.explode(date,prec))
|
23
|
+
when HOUR then PDate.hour(*DPrecision.explode(date,prec))
|
24
|
+
when MONTH then PDate.month(*DPrecision.explode(date,prec))
|
25
|
+
when YEAR then PDate.year(*DPrecision.explode(date,prec))
|
26
|
+
when SEC then PDate.sec(*DPrecision.explode(date,prec))
|
27
|
+
when MILLI then raise "Not implemented."
|
28
|
+
else PDate.default(*DPrecision.explode(date,prec))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def DPrecision.explode(date,prec)
|
33
|
+
result = [date.year,date.month,date.day]
|
34
|
+
if(date.respond_to?("hour"))
|
35
|
+
result << date.hour << date.min << date.sec
|
36
|
+
else
|
37
|
+
result << 0 << 0 << 0
|
38
|
+
end
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
#Simple value class for keeping track of precisioned dates
|
43
|
+
class Precision
|
44
|
+
include Comparable
|
45
|
+
|
46
|
+
attr_reader :precision
|
47
|
+
private_class_method :new
|
48
|
+
|
49
|
+
#Some constants w/arbitrary integer values used internally for comparisions
|
50
|
+
YEAR_PREC = 0
|
51
|
+
MONTH_PREC = 1
|
52
|
+
DAY_PREC = 2
|
53
|
+
HOUR_PREC = 3
|
54
|
+
MIN_PREC = 4
|
55
|
+
SEC_PREC = 5
|
56
|
+
MILLI_PREC = 6
|
57
|
+
|
58
|
+
#String values for display
|
59
|
+
LABEL = { YEAR_PREC => "YEAR",
|
60
|
+
MONTH_PREC => "MONTH",
|
61
|
+
DAY_PREC => "DAY",
|
62
|
+
HOUR_PREC => "HOUR",
|
63
|
+
MIN_PREC => "MIN",
|
64
|
+
SEC_PREC => "SEC",
|
65
|
+
MILLI_PREC => "MILLI"}
|
66
|
+
|
67
|
+
#Minimun values that precisioned fields get set to
|
68
|
+
FIELD_MIN = { YEAR_PREC => 1,
|
69
|
+
MONTH_PREC => 1,
|
70
|
+
DAY_PREC => 1,
|
71
|
+
HOUR_PREC => 0,
|
72
|
+
MIN_PREC => 0,
|
73
|
+
SEC_PREC => 0,
|
74
|
+
MILLI_PREC => 0}
|
75
|
+
|
76
|
+
def Precision.year
|
77
|
+
new(YEAR_PREC)
|
78
|
+
end
|
79
|
+
|
80
|
+
def Precision.month
|
81
|
+
new(MONTH_PREC)
|
82
|
+
end
|
83
|
+
|
84
|
+
def Precision.day
|
85
|
+
new(DAY_PREC)
|
86
|
+
end
|
87
|
+
|
88
|
+
def Precision.hour
|
89
|
+
new(HOUR_PREC)
|
90
|
+
end
|
91
|
+
|
92
|
+
def Precision.min
|
93
|
+
new(MIN_PREC)
|
94
|
+
end
|
95
|
+
|
96
|
+
def Precision.sec
|
97
|
+
new(SEC_PREC)
|
98
|
+
end
|
99
|
+
|
100
|
+
def Precision.millisec
|
101
|
+
new(MILLI_PREC)
|
102
|
+
end
|
103
|
+
|
104
|
+
def min_value()
|
105
|
+
FIELD_MIN[@precision]
|
106
|
+
end
|
107
|
+
|
108
|
+
def initialize(prec)
|
109
|
+
@precision = prec
|
110
|
+
end
|
111
|
+
|
112
|
+
def <=>(other)
|
113
|
+
self.precision <=> other.precision
|
114
|
+
end
|
115
|
+
|
116
|
+
def ===(other)
|
117
|
+
self.precision == other.precision
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_s
|
121
|
+
"DPrecision::#{LABEL[@precision]}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#Pseudo Singletons:
|
126
|
+
YEAR = Precision.year
|
127
|
+
MONTH = Precision.month
|
128
|
+
DAY = Precision.day
|
129
|
+
HOUR = Precision.hour
|
130
|
+
MIN = Precision.min
|
131
|
+
SEC = Precision.sec
|
132
|
+
MILLI = Precision.millisec
|
133
|
+
DEFAULT=MIN
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
data/lib/runt/pdate.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'runt'
|
5
|
+
|
6
|
+
|
7
|
+
module Runt
|
8
|
+
|
9
|
+
# :title:PDate
|
10
|
+
# == PDate
|
11
|
+
# Date and DateTime with explicit precision.
|
12
|
+
#
|
13
|
+
# Based the <tt>pattern</tt>[http://martinfowler.com/ap2/timePoint.html] by Martin Fowler.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# Author:: Matthew Lipper
|
17
|
+
class PDate < DateTime
|
18
|
+
include DPrecision
|
19
|
+
|
20
|
+
attr_accessor :date_precision
|
21
|
+
|
22
|
+
class << self
|
23
|
+
alias_method :old_civil, :civil
|
24
|
+
|
25
|
+
def civil(*args)
|
26
|
+
if(args[0].instance_of?(DPrecision::Precision))
|
27
|
+
precision = args.shift
|
28
|
+
else
|
29
|
+
return PDate::sec(*args)
|
30
|
+
end
|
31
|
+
_civil = old_civil(*args)
|
32
|
+
_civil.date_precision = precision
|
33
|
+
_civil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self; alias_method :new, :civil end
|
38
|
+
|
39
|
+
def + (n)
|
40
|
+
raise TypeError, 'expected numeric' unless n.kind_of?(Numeric)
|
41
|
+
case @date_precision
|
42
|
+
when YEAR then
|
43
|
+
return DPrecision::to_p(PDate::civil(year+n,month,day),@date_precision)
|
44
|
+
when MONTH then
|
45
|
+
current_date = self.class.to_date(self)
|
46
|
+
return DPrecision::to_p((current_date>>n),@date_precision)
|
47
|
+
when DAY then
|
48
|
+
return new_self_plus(n)
|
49
|
+
when HOUR then
|
50
|
+
return new_self_plus(n){ |n| n = (n*(1.to_r/24) ) }
|
51
|
+
when MIN then
|
52
|
+
return new_self_plus(n){ |n| n = (n*(1.to_r/1440) ) }
|
53
|
+
when SEC then
|
54
|
+
return new_self_plus(n){ |n| n = (n*(1.to_r/86400) ) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def - (x)
|
59
|
+
case x
|
60
|
+
when Numeric then
|
61
|
+
return self+(-x)
|
62
|
+
#FIXME!!
|
63
|
+
when Date; return @ajd - x.ajd
|
64
|
+
end
|
65
|
+
raise TypeError, 'expected numeric or date'
|
66
|
+
end
|
67
|
+
|
68
|
+
def <=> (other)
|
69
|
+
result = nil
|
70
|
+
if(other.respond_to?("date_precision") && other.date_precision>@date_precision)
|
71
|
+
result = super(DPrecision::to_p(other,@date_precision))
|
72
|
+
else
|
73
|
+
result = super(other)
|
74
|
+
end
|
75
|
+
puts "#{self.to_s}<=>#{other.to_s} => #{result}" if $DEBUG
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def new_self_plus(n)
|
80
|
+
if(block_given?)
|
81
|
+
n=yield(n)
|
82
|
+
end
|
83
|
+
return DPrecision::to_p(self.class.new0(@ajd + n, @of, @sg),@date_precision)
|
84
|
+
end
|
85
|
+
|
86
|
+
def PDate.to_date(pdate)
|
87
|
+
if( pdate.date_precision > DPrecision::DAY) then
|
88
|
+
DateTime.new(pdate.year,pdate.month,pdate.day,pdate.hour,pdate.min,pdate.sec)
|
89
|
+
end
|
90
|
+
return Date.new(pdate.year,pdate.month,pdate.day)
|
91
|
+
end
|
92
|
+
|
93
|
+
def PDate.year(yr,*ignored)
|
94
|
+
PDate.civil(YEAR, yr, MONTH.min_value, DAY.min_value )
|
95
|
+
end
|
96
|
+
|
97
|
+
def PDate.month( yr,mon,*ignored )
|
98
|
+
PDate.civil(MONTH, yr, mon, DAY.min_value )
|
99
|
+
end
|
100
|
+
|
101
|
+
def PDate.day( yr,mon,day,*ignored )
|
102
|
+
PDate.civil(DAY, yr, mon, day )
|
103
|
+
end
|
104
|
+
|
105
|
+
def PDate.hour( yr,mon,day,hr=HOUR.min_value,*ignored )
|
106
|
+
PDate.civil(HOUR, yr, mon, day,hr,MIN.min_value, SEC.min_value)
|
107
|
+
end
|
108
|
+
|
109
|
+
def PDate.min( yr,mon,day,hr=HOUR.min_value,min=MIN.min_value,*ignored )
|
110
|
+
PDate.civil(MIN, yr, mon, day,hr,min, SEC.min_value)
|
111
|
+
end
|
112
|
+
|
113
|
+
def PDate.sec( yr,mon,day,hr=HOUR.min_value,min=MIN.min_value,sec=SEC.min_value,*ignored )
|
114
|
+
PDate.civil(SEC, yr, mon, day,hr,min, sec)
|
115
|
+
end
|
116
|
+
|
117
|
+
def PDate.millisecond( yr,mon,day,hr,min,sec,ms,*ignored )
|
118
|
+
raise "Not implemented yet."
|
119
|
+
end
|
120
|
+
|
121
|
+
def PDate.default(*args)
|
122
|
+
PDate.civil(DEFAULT, *args)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Runt
|
4
|
+
|
5
|
+
|
6
|
+
# Implementation of a <tt>pattern</tt>[http://martinfowler.com/apsupp/recurring.pdf]
|
7
|
+
# for recurring calendar events created by Martin Fowler.
|
8
|
+
class Schedule
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@elems = Hash.new
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Schedule event to occur using the given expression.
|
16
|
+
def add(event, expression)
|
17
|
+
|
18
|
+
if @elems.include?(event)
|
19
|
+
@elems[event].push(ScheduleElement.new(event, expression))
|
20
|
+
else
|
21
|
+
@elems[event] = [ScheduleElement.new(event, expression)]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
# For the given date range, returns an Array of PDate objects at which
|
27
|
+
# the supplied event is scheduled to occur.
|
28
|
+
def dates(event, date_range)
|
29
|
+
result = Array.new
|
30
|
+
date_range.each do |date|
|
31
|
+
result.push date if include?(event,date)
|
32
|
+
end
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return true or false depend on if the supplied event is scheduled to occur on the
|
37
|
+
# given date.
|
38
|
+
def include?(event, date)
|
39
|
+
return false unless @elems.include?(event)
|
40
|
+
result = Array.new
|
41
|
+
@elems[event].each{|element| result << element.include?(event, date) }
|
42
|
+
result.inject{|x,y| x && y}
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def add_element
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
class ScheduleElement
|
52
|
+
|
53
|
+
def initialize(event, expression)
|
54
|
+
@event = event
|
55
|
+
@expression = expression
|
56
|
+
end
|
57
|
+
|
58
|
+
def include?(event, date)
|
59
|
+
return false unless @event == event
|
60
|
+
@expression.include?(date)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
"event: #{@event} expr: #{@expression}"
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
public
|
70
|
+
class Event
|
71
|
+
|
72
|
+
attr_reader :schedule, :id
|
73
|
+
|
74
|
+
def initialize(id)
|
75
|
+
raise Exception, "id argument cannot be nil" unless !id.nil?
|
76
|
+
@id = id
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def to_s; @id.to_s end
|
81
|
+
|
82
|
+
def == (other)
|
83
|
+
return true if other.kind_of?(Event) && @id == other.id
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|