runt 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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