dcparker-time_point 1.0.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/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2009-08-21
2
+
3
+ * 1 major enhancement
4
+
5
+ * Initial release
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,6 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/time_point.rb
6
+ spec/time_point_spec.rb
data/README.txt ADDED
@@ -0,0 +1,65 @@
1
+ = time_point
2
+
3
+ * http://github.com/dcparker/time_point
4
+
5
+ == DESCRIPTION:
6
+
7
+ An attempt at a Ruby implementation of Martin Fowler's TimePoint pattern (http://martinfowler.com/ap2/timePoint.html). It is most certainly lacking some areas (ex. timezone support), but its algorithm for parsing the natural language for recurring time-points is quite powerful.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Does not yet understand words that reference from today's date, like "Today", "Tomorrow", "Next Week".
12
+ * Does not yet assume an end-time for a time-range when an end-time is not given. This is necessary so that '2pm Fridays' really means '2-3pm Fridays', and '2:30pm Fridays' really means '2:30-2:31pm Fridays'.
13
+
14
+ == SYNOPSIS:
15
+
16
+ TimePoint works to follow the true spirit of the TimePoint pattern -- the idea that every expression of time is in fact a block of time, and we mean these expressions to be to differing precisions. For example, if I say "March 5, 2000", I simply mean any time within that day -- all day. However, if I say "2:05pm March 5, 2000", I mean any second within that very specific minute. But if I say "2:00pm Fridays" I really mean every Friday, and that expression is precise to the day-of-week and to the hour and minute, but the second, week, month, or year don't matter.
17
+
18
+ The main usage of TimePoint, as it currently has been built for, is TimePoint.parse and TimePoint#include?. Here are several examples:
19
+
20
+ Single-point expressions:
21
+ t1 = TimePoint.parse('Tuesday') # All day Tuesday, every week
22
+ t2 = TimePoint.parse("February 24th") # Every Feb 24, all day long
23
+ t3 = TimePoint.parse("24 February 2001") # Feb 24 in 2001, all day long
24
+ t4 = TimePoint.parse("9 January 2009 and Thursday 2009") # every Thursday in 2009, and the 9th of January 2009 too
25
+ t5 = TimePoint.parse("1st Thursdays at 4-5pm and 1st - 4th of March at 2-3:30pm") # first thursday of every month, forever, from 4 to 5 pm; also 2 to 3:30 pm on the 1st, 2nd, 3rd, and 4th of March (every year!)
26
+ t6 = TimePoint.parse("2pm Fridays in January 2009 and Thursdays in 2009") # default duration of ONE of the most specific piece mentioned: 2-3pm every friday in January of '09, and also all day every thursday all year in 2009
27
+
28
+ From that most complex one above, see how TimePoint#include? works:
29
+ t6.include?(Time.parse('2009-02-05 19:00:00')) => true
30
+ t6.include?(Time.parse('2009-01-09 2:31pm')) => true
31
+ t6.include?(Time.parse('February 5, 2010')) => false
32
+ t6.include?(Time.parse('January 16, 2007')) => false
33
+
34
+ == REQUIREMENTS:
35
+
36
+ * Just Ruby!
37
+
38
+ == INSTALL:
39
+
40
+ * [sudo] gem install time_point
41
+
42
+ == LICENSE:
43
+
44
+ (The MIT License)
45
+
46
+ Copyright (c) 2009 BehindLogic <gems@behindlogic.com>
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of this software and associated documentation files (the
50
+ 'Software'), to deal in the Software without restriction, including
51
+ without limitation the rights to use, copy, modify, merge, publish,
52
+ distribute, sublicense, and/or sell copies of the Software, and to
53
+ permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be
57
+ included in all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
60
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
61
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
62
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
63
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
64
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
65
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/time_point'
4
+
5
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
6
+
7
+ Hoe.new('time_point', TimePoint::VERSION) do |p|
8
+ p.author = 'Daniel Parker'
9
+ p.email = 'gems@behindlogic.com'
10
+ p.summary = "A parser for definitions of recurring events in natural English."
11
+ p.description = "A parser for definitions of recurring events in natural English."
12
+ p.url = 'http://github.com/dcparker/time_point'
13
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ end
15
+
16
+ desc "Generate gemspec"
17
+ task :gemspec do |x|
18
+ # Check the manifest before generating the gemspec
19
+ manifest = %x[rake check_manifest]
20
+ manifest.gsub!(/\(in [^\)]+\)\n/, "")
21
+
22
+ unless manifest.empty?
23
+ print "\n", "#"*68, "\n"
24
+ print <<-EOS
25
+ Manifest.txt is not up-to-date. Please review the changes below.
26
+ If the changes are correct, run 'rake check_manifest | patch'
27
+ and then run this command again.
28
+ EOS
29
+ print "#"*68, "\n\n"
30
+ puts manifest
31
+ else
32
+ gemspec = `rake debug_gem`
33
+ gemspec.gsub!(/\(in [^\)]+\)\n/, "")
34
+ File.open("time_point.gemspec", 'w') {|f| f.write(gemspec) }
35
+ end
36
+ end
data/lib/time_point.rb ADDED
@@ -0,0 +1,415 @@
1
+ require 'date'
2
+ require 'time'
3
+
4
+ class Array
5
+ def includes_sequence?(sequence)
6
+ first_exists = []
7
+ each_index do |i|
8
+ first_exists << i if self[i] == sequence[0]
9
+ end
10
+ first_exists.any? do |i|
11
+ return i if self[i...(i+sequence.length)] == sequence
12
+ end
13
+ end
14
+
15
+ def include_value?(v)
16
+ any? do |iv|
17
+ case iv
18
+ when Range || Array
19
+ v.to_i.in?(iv)
20
+ else
21
+ if iv.to_s =~ /^\d+$/ && v.to_s =~ /^\d+$/
22
+ iv.to_i == v.to_i
23
+ else
24
+ iv.to_s == v.to_s
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ class Range
32
+ alias :include_value? :include?
33
+ end
34
+
35
+ class Object
36
+ def value_in?(*arg)
37
+ arg = arg[0] if arg.length == 1 && arg[0].respond_to?(:include_value?)
38
+ arg.include_value?(self)
39
+ end
40
+ def in?(*arg)
41
+ arg = arg[0] if arg.length == 1 && arg[0].respond_to?(:include?)
42
+ arg.include?(self)
43
+ end
44
+ def my_methods
45
+ methods.sort - Object.methods
46
+ end
47
+ end
48
+
49
+ module WdayOrd
50
+ def wday_ord
51
+ (day.to_f / 7).ceil
52
+ end
53
+ end
54
+ class DateTime
55
+ include WdayOrd
56
+ end
57
+ class Time
58
+ include WdayOrd
59
+ end
60
+
61
+ class TimePoint
62
+ VERSION = '1.0.0'
63
+ class ArrayOfRanges < Array
64
+ def self.new(*values)
65
+ n = allocate
66
+ n.push(*values)
67
+ n
68
+ end
69
+ end
70
+
71
+ class Classification
72
+ class << self
73
+ attr_reader :order, :translations
74
+
75
+ def abbreviations
76
+ @abbreviations ||= translations.inject({}) do |h,(k,v)|
77
+ h[v] = k unless h.has_key?(v) && h[v].length < k.length
78
+ h
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ class WDay < Classification
85
+ @order = %w(Sunday Monday Tuesday Wednesday Thursday Friday)
86
+ @translations = {
87
+ 'S' => 'Sunday',
88
+ 'M' => 'Monday',
89
+ 'T' => 'Tuesday',
90
+ 'W' => 'Wednesday',
91
+ 'Th' => 'Thursday',
92
+ 'F' => 'Friday',
93
+ 'Sa' => 'Saturday',
94
+ 'Sundays' => 'Sunday',
95
+ 'Mondays' => 'Monday',
96
+ 'Tuesdays' => 'Tuesday',
97
+ 'Wednesdays' => 'Wednesday',
98
+ 'Thursdays' => 'Thursday',
99
+ 'Fridays' => 'Friday',
100
+ 'Saturdays' => 'Saturday'
101
+ }
102
+ end
103
+ class Month < Classification
104
+ @order = %w(January February March April May June July August September October November December)
105
+ @translations = {
106
+ 'Jan' => 'January',
107
+ 'Feb' => 'February',
108
+ 'Mar' => 'March',
109
+ 'Apr' => 'April',
110
+ 'Jun' => 'June',
111
+ 'Jul' => 'July',
112
+ 'Aug' => 'August',
113
+ 'Sep' => 'September',
114
+ 'Oct' => 'October',
115
+ 'Nov' => 'November',
116
+ 'Dec' => 'December'
117
+ }
118
+ end
119
+
120
+ # These are in a specific order
121
+ CommonPatterns = [
122
+ 'ord range ord',
123
+ 'ord union ord',
124
+ 'wday range wday',
125
+ 'wday union wday',
126
+ 'month ord',
127
+ 'ord wday',
128
+ 'ord month timerange',
129
+ 'month_ord timerange',
130
+ 'month union month',
131
+ 'month range month',
132
+ 'ord_wday month',
133
+ 'ord_wday timerange',
134
+ 'ord_wday_month timerange'
135
+ ]
136
+ CommonPatternActions = {
137
+ 'ord range ord' => lambda {|words,i|
138
+ words[i][:ord] = (words[i][:ord].to_i..words[i+2][:ord].to_i)
139
+ words.slice!(i+1,2)
140
+ },
141
+ 'ord union ord' => lambda {|words,i|
142
+ words[i][:ord] = ArrayOfRanges.new(words[i][:ord], words[i+2][:ord])
143
+ words.slice!(i+1,2)
144
+ },
145
+ 'wday range wday' => lambda {|words,i|
146
+ words[i][:wday] = (words[i][:wday].to_i..words[i+2][:wday].to_i)
147
+ words.slice!(i+1,2)
148
+ },
149
+ 'wday union wday' => lambda {|words,i|
150
+ words[i][:wday] = ArrayOfRanges.new(words[i][:wday], words[i+2][:wday])
151
+ words.slice!(i+1,2)
152
+ },
153
+ 'month ord' => lambda {|words,i|
154
+ words[i][:type] = 'month_ord'
155
+ words[i][:ord] = words[i+1][:ord]
156
+ words.slice!(i+1,1)
157
+ },
158
+ 'ord wday' => lambda {|words,i|
159
+ words[i][:type] = 'ord_wday'
160
+ words[i][:wday] = words[i+1][:wday]
161
+ words.slice!(i+1,1)
162
+ },
163
+ 'ord month timerange' => lambda {|words,i|
164
+ words[i][:type] = 'month_ord_timerange'
165
+ words[i][:month] = words[i+1][:month]
166
+ words[i][:start_time] = words[i+2][:start_time]
167
+ words[i][:end_time] = words[i+2][:end_time]
168
+ words.slice!(i+1,2)
169
+ },
170
+ 'month_ord timerange' => lambda {|words,i|
171
+ words[i][:type] = 'month_ord_timerange'
172
+ words[i][:start_time] = words[i+1][:start_time]
173
+ words[i][:end_time] = words[i+1][:end_time]
174
+ words.slice!(i+1,1)
175
+ },
176
+ 'month union month' => lambda {|words,i|
177
+ words[i][:month] = ArrayOfRanges.new(words[i][:month], words[i+2][:month])
178
+ words.slice!(i+1,2)
179
+ },
180
+ 'month range month' => lambda {|words,i|
181
+ raise "Not Implemented Yet!"
182
+ },
183
+ 'ord_wday month' => lambda {|words,i|
184
+ words[i][:type] = 'ord_wday_month'
185
+ words[i][:month] = words[i+1][:month]
186
+ words.slice!(i+1,1)
187
+ },
188
+ 'ord_wday timerange' => lambda {|words,i|
189
+ words[i][:type] = 'ord_wday_timerange'
190
+ words[i][:start_time] = words[i+1][:start_time]
191
+ words[i][:end_time] = words[i+1][:end_time]
192
+ words.slice!(i+1,1)
193
+ },
194
+ 'ord_wday_month timerange' => lambda {|words,i|
195
+ words[i][:type] = 'ord_wday_month_timerange'
196
+ words[i][:start_time] = words[i+1][:start_time]
197
+ words[i][:end_time] = words[i+1][:end_time]
198
+ words.slice!(i+1,1)
199
+ }
200
+ }
201
+
202
+ BooleanPatterns = [
203
+ 'union'
204
+ ]
205
+
206
+ BooleanPatternActions = {
207
+ 'union' => lambda {|words,i|
208
+ words[i-1] = TimePoint::Union.new(words[i-1], words[i+1])
209
+ words.slice!(i,2)
210
+ puts words.inspect if $DEBUG
211
+ }
212
+ }
213
+
214
+ TimeRegexp = '\d{1,2}(?::\d{1,2})?(?:am|pm)'
215
+ WordTypes = {
216
+ :ord => /^(\d+)(?:st|nd|rd|th)?$/i,
217
+ :wday => /^(#{WDay.order.join('|')})s$/i,
218
+ :month => /^#{Month.order.join('|')}$/i,
219
+ :union => /^(?:and)$/i,
220
+ :range => /^(?:-|to|through)$/,
221
+ :timerange => /^(#{TimeRegexp}?)-(#{TimeRegexp})$/i,
222
+ :time => /^#{TimeRegexp}$/i
223
+ }
224
+
225
+ class << self
226
+ def parse(expression)
227
+ # 1. Normalize the expression
228
+ # TODO: re-create normalize: ' -&| ', 'time-time'
229
+ expression.gsub!(/([\-\&\|])/,' \1 ')
230
+ expression.gsub!(/(#{TimeRegexp}?)\s+-\s+(#{TimeRegexp})/,'\1-\2')
231
+
232
+ # 2. Analyze the expression
233
+ words = expression.split(/\s+/)
234
+ puts words.inspect if $DEBUG
235
+ analyzed_expression = words.inject([]) do |a,word|
236
+ a << case word
237
+ when WordTypes[:ord]
238
+ {:type => 'ord', :ord => $1}
239
+ when WordTypes[:wday]
240
+ {:type => 'wday', :wday => $1}
241
+ when WordTypes[:month]
242
+ {:type => 'month', :month => word}
243
+ when WordTypes[:union]
244
+ {:type => 'union'}
245
+ when WordTypes[:range]
246
+ {:type => 'range'}
247
+ when WordTypes[:timerange]
248
+ {:type => 'timerange', :start_time => $1, :end_time => $2}
249
+ when WordTypes[:time]
250
+ {:type => 'time', :time => word}
251
+ end
252
+ end.compact
253
+ def analyzed_expression.collect_types
254
+ collect {|e| e[:type]}
255
+ end
256
+
257
+ # 3. Combine common patterns
258
+ puts analyzed_expression.inspect if $DEBUG
259
+ puts analyzed_expression.collect_types.inspect if $DEBUG
260
+
261
+ something_was_modified = true
262
+ while something_was_modified
263
+ something_was_modified = false
264
+ before_length = analyzed_expression.length
265
+ CommonPatterns.each do |pattern|
266
+ while i = analyzed_expression.collect_types.includes_sequence?(pattern.split(/ /))
267
+ CommonPatternActions[pattern].call(analyzed_expression,i)
268
+ end
269
+ end
270
+ after_length = analyzed_expression.length
271
+ something_was_modified = true if before_length != after_length
272
+ end
273
+
274
+ puts analyzed_expression.inspect if $DEBUG
275
+ puts analyzed_expression.collect_types.inspect if $DEBUG
276
+
277
+ # What remains should be simply sections of boolean logic
278
+ # 4. Parse boolean logic
279
+ analyzed_expression.each_index do |i|
280
+ analyzed_expression[i] = TimePoint.new(analyzed_expression[i]) unless ['union', 'range'].include? analyzed_expression[i][:type]
281
+ end
282
+
283
+ BooleanPatterns.each do |pattern|
284
+ while i = analyzed_expression.collect_types.includes_sequence?(pattern.split(/ /))
285
+ BooleanPatternActions[pattern].call(analyzed_expression,i)
286
+ break if analyzed_expression.length == 1
287
+ end
288
+ end
289
+
290
+ return analyzed_expression[0]
291
+ end
292
+ end
293
+
294
+ def initialize(options)
295
+ options.each do |key,value|
296
+ instance_variable_set(:"@#{key}", value)
297
+ end
298
+ end
299
+
300
+ def [](key)
301
+ instance_variable_get(:"@#{key}")
302
+ end
303
+
304
+ def start_pm?
305
+ if @start_time =~ /(am|pm)$/ || @end_time =~ /(am|pm)$/
306
+ $1 == 'pm'
307
+ else
308
+ nil
309
+ end
310
+ end
311
+
312
+ def include?(datetime)
313
+ return false unless occurs_on_day?(datetime)
314
+ if @type =~ /timerange/
315
+ test_date = datetime.strftime("%Y-%m-%d")
316
+ test_start_time = Time.parse("#{test_date} #{@start_time}#{start_pm? ? 'pm' : 'am'}")
317
+ test_end_time = Time.parse("#{test_date} #{@end_time}")
318
+ puts "TimeRange: date:#{test_date} test_start:#{test_start_time} test_end:#{test_end_time} <=> #{datetime}" if $DEBUG
319
+ return false unless datetime.between?(test_start_time, test_end_time)
320
+ end
321
+ return true
322
+ puts "#{datetime} Included!" if $DEBUG
323
+ end
324
+
325
+ def occurs_on_day?(datetime)
326
+ puts "#{datetime} IN? #{inspect}" if $DEBUG
327
+ puts "Correct month? #{Month.order[datetime.month-1].inspect}==#{@month.inspect} : #{Month.order[datetime.month-1].value_in?(@month)}" if @type =~ /month/ if $DEBUG
328
+ return false if @type =~ /month/ && !Month.order[datetime.month-1].value_in?(@month)
329
+ if @type =~ /ord_wday/
330
+ puts "Weekday: #{WDay.order[datetime.wday].inspect} in? #{@wday.inspect} == #{WDay.order[datetime.wday].value_in?(@wday)}" if $DEBUG
331
+ return false unless WDay.order[datetime.wday].value_in?(@wday)
332
+ puts "WeekdayOrd: #{datetime.wday_ord} in? #{@ord.inspect} == #{datetime.wday_ord.value_in?(@ord)}" if $DEBUG
333
+ return false unless datetime.wday_ord.value_in?(@ord)
334
+ end
335
+ if @type =~ /month_ord/
336
+ puts "Day #{datetime.day} == #{@ord.inspect} >> #{datetime.day.value_in?(@ord)}" if $DEBUG
337
+ return false unless datetime.day.value_in?(@ord)
338
+ end
339
+ # puts "Type: #{@type}" if $DEBUG
340
+ # case
341
+ # when @type == 'ord_wday_month'
342
+ # return true
343
+ # when @type == 'ord_wday_month_timerange'
344
+ # return true
345
+ # when @type == 'ord_wday_timerange'
346
+ # return true
347
+ # when @type == 'month_ord_timerange'
348
+ # end
349
+ puts "Occurs on #{datetime}!" if $DEBUG
350
+ return true
351
+ end
352
+
353
+ def occurrances_on_day(date)
354
+ occurs_on_day?(date) ? [{:start_time => start_time(date), :end_time => end_time(date)}] : []
355
+ end
356
+
357
+ def start_time(date=nil)
358
+ if date
359
+ Time.parse("#{date.strftime("%Y-%m-%d")} #{@start_time}#{start_pm? ? 'pm' : 'am'}")
360
+ else
361
+ @start_time
362
+ end
363
+ end
364
+ def end_time(date=nil)
365
+ if date
366
+ Time.parse("#{date.strftime("%Y-%m-%d")} #{@end_time}")
367
+ else
368
+ @end_time
369
+ end
370
+ end
371
+
372
+ class Union
373
+ def set
374
+ @set ||= []
375
+ end
376
+
377
+ def initialize(*args)
378
+ # @set = args.select {|e| e.is_a?(TimePoint) || e.is_a?(Range)}
379
+ @set = args.select {|e| e.is_a?(TimePoint)}
380
+ # @set = args
381
+ end
382
+
383
+ def include?(other)
384
+ set.any? {|tp| tp.include?(other)}
385
+ end
386
+
387
+ def occurs_on_day?(other)
388
+ set.any? {|tp| tp.occurs_on_day?(other)}
389
+ end
390
+
391
+ def occurrances_on_day(other)
392
+ set.inject([]) {|a,tp| a.concat(tp.occurrances_on_day(other)); a}
393
+ end
394
+
395
+ def eql?(other)
396
+ if other.is_a?(TimePoint::Union)
397
+ set.length == other.length && set.length.times { |i| return false unless set[i].eql? other[i] }
398
+ else
399
+ # what else can we compare to?
400
+ raise "Comparison of TimePointSet with something different (#{other.class.name})."
401
+ end
402
+ end
403
+
404
+ private
405
+ # Sends all other methods to the set array.
406
+ def method_missing(name, *args, &block)
407
+ if set.respond_to?(name)
408
+ args << block if block_given?
409
+ set.send(name, *args)
410
+ else
411
+ super
412
+ end
413
+ end
414
+ end
415
+ end
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require File.dirname(__FILE__) + '../lib/time_point'
4
+
5
+ describe TimePoint do
6
+ it "1st-2nd And 4th Thursdays Of March And April 5-6:30pm And March 16th - 24th At 2-2:30pm" do
7
+ t = TimePoint.parse("1st-2nd And 4th Thursdays Of March And April 5-6:30pm And March 16th - 24th At 2-2:30pm")
8
+ # march 5, 12, 26, april 2, 9, 23
9
+ t.include?(Time.parse('2009-03-05 5:54pm')).should eql(true)
10
+ t.include?(Time.parse('2009-03-05 18:24')).should eql(true)
11
+ t.include?(Time.parse('2009-03-05 18:30')).should eql(true)
12
+ t.include?(Time.parse('2009-03-05 18:31')).should eql(false)
13
+ # t.include?(Time.parse('2009-04-26 18:31')).should eql(false)
14
+ t.occurs_on_day?(Time.parse('2009-03-04')).should_not eql(true)
15
+ t.occurs_on_day?(Time.parse('2009-03-11')).should_not eql(true)
16
+ t.occurs_on_day?(Time.parse('2009-03-25')).should_not eql(true)
17
+ t.occurs_on_day?(Time.parse('2009-04-01')).should_not eql(true)
18
+ t.occurs_on_day?(Time.parse('2009-04-08')).should_not eql(true)
19
+ t.occurs_on_day?(Time.parse('2009-04-22')).should_not eql(true)
20
+ t.occurs_on_day?(Time.parse('2009-03-05')).should eql(true)
21
+ t.occurs_on_day?(Time.parse('2009-03-12')).should eql(true)
22
+ t.occurs_on_day?(Time.parse('2009-03-26')).should eql(true)
23
+ t.occurs_on_day?(Time.parse('2009-04-02')).should eql(true)
24
+ t.occurs_on_day?(Time.parse('2009-04-09')).should eql(true)
25
+ t.occurs_on_day?(Time.parse('2009-04-23')).should eql(true)
26
+ t.occurs_on_day?(Time.parse('2009-03-06')).should_not eql(true)
27
+ t.occurs_on_day?(Time.parse('2009-03-13')).should_not eql(true)
28
+ t.occurs_on_day?(Time.parse('2009-03-27')).should_not eql(true)
29
+ t.occurs_on_day?(Time.parse('2009-04-03')).should_not eql(true)
30
+ t.occurs_on_day?(Time.parse('2009-04-10')).should_not eql(true)
31
+ t.occurs_on_day?(Time.parse('2009-04-24')).should_not eql(true)
32
+ t.occurrances_on_day(Time.parse('2009-04-26')).length.should eql(0)
33
+ t.occurrances_on_day(Time.parse('2009-04-23')).length.should eql(1)
34
+ end
35
+
36
+ it "1st Thursdays at 4-5pm and 1st - 4th of March at 2-3:30pm" do
37
+ t = TimePoint.parse("1st Thursdays at 4-5pm and 1st - 4th of March and April at 2-3:30pm")
38
+ t.occurs_on_day?(Time.parse('2009-04-02')).should eql(true)
39
+ t.occurrances_on_day(Time.parse('2009-04-01')).length.should eql(1)
40
+ t.occurrances_on_day(Time.parse('2009-04-02')).length.should eql(2)
41
+ t.occurrances_on_day(Time.parse('2009-05-07')).length.should eql(1)
42
+ end
43
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dcparker-time_point
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Parker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.3
24
+ version:
25
+ description: A parser for definitions of recurring events in natural English.
26
+ email: gems@behindlogic.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - History.txt
33
+ - Manifest.txt
34
+ - README.txt
35
+ files:
36
+ - History.txt
37
+ - Manifest.txt
38
+ - README.txt
39
+ - Rakefile
40
+ - lib/time_point.rb
41
+ - spec/time_point_spec.rb
42
+ has_rdoc: false
43
+ homepage: http://github.com/dcparker/time_point
44
+ licenses:
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --main
48
+ - README.txt
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project: time_point
66
+ rubygems_version: 1.3.5
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: A parser for definitions of recurring events in natural English.
70
+ test_files: []
71
+