chronic 0.1.0 → 0.2.2

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,49 @@
1
+ = 0.2.2
2
+
3
+ * added missing files (damn you manifest!)
4
+
5
+ = 0.2.1
6
+
7
+ * fixed time overflow issue
8
+ * implemented "next" for minute repeater
9
+ * generalized time dealiasing to dealias regardless of day portion and time position
10
+ * added additional token match for cases like "friday evening at 7" and "tomorrow evening at 7"
11
+ * added support for Time#to_s output format: "Mon Apr 02 17:00:00 PDT 2007"
12
+
13
+ = 0.2.0 2007-03-20
14
+
15
+ * implemented numerizer, allowing the use of number words (e.g. five weeks ago) (by shalev)
16
+
17
+ = 0.1.6 2006-01-15
18
+
19
+ * added 'weekend' support (by eventualbuddha)
20
+
21
+ = 0.1.5 2006-12-20
22
+
23
+ * fixed 'aug 20' returning next year if current month is august
24
+ * modified behavior of 'from now'
25
+ * added support for seconds on times, and thus db timestamp format: "2006-12-20 18:04:23"
26
+ * made Hoe compliant
27
+
28
+ = 0.1.4
29
+
30
+ * removed verbose error checking code. oops. :-/
31
+
32
+ = 0.1.3
33
+
34
+ * improved regexes for word variations (by Josh Goebel)
35
+ * fixed a bug that caused "today at 3am" to return nil if current time is after 3am
36
+
37
+ = 0.1.2
38
+
39
+ * removed Date dependency (now works on windows properly without fiddling)
40
+
41
+ = 0.1.1
42
+
43
+ * run to_s on incoming object
44
+ * fixed loop loading of repeaters files (out of order on some machines)
45
+ * fixed find_within to use this instead of next (was breaking "today at 6pm")
46
+
47
+ = 0.1.0
48
+
49
+ * initial release
data/Manifest.txt ADDED
@@ -0,0 +1,47 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/chronic.rb
6
+ lib/chronic/chronic.rb
7
+ lib/chronic/grabber.rb
8
+ lib/chronic/handlers.rb
9
+ lib/chronic/ordinal.rb
10
+ lib/chronic/pointer.rb
11
+ lib/chronic/repeater.rb
12
+ lib/chronic/repeaters/repeater_day.rb
13
+ lib/chronic/repeaters/repeater_day_name.rb
14
+ lib/chronic/repeaters/repeater_day_portion.rb
15
+ lib/chronic/repeaters/repeater_fortnight.rb
16
+ lib/chronic/repeaters/repeater_hour.rb
17
+ lib/chronic/repeaters/repeater_minute.rb
18
+ lib/chronic/repeaters/repeater_month.rb
19
+ lib/chronic/repeaters/repeater_month_name.rb
20
+ lib/chronic/repeaters/repeater_season.rb
21
+ lib/chronic/repeaters/repeater_season_name.rb
22
+ lib/chronic/repeaters/repeater_second.rb
23
+ lib/chronic/repeaters/repeater_time.rb
24
+ lib/chronic/repeaters/repeater_week.rb
25
+ lib/chronic/repeaters/repeater_weekend.rb
26
+ lib/chronic/repeaters/repeater_year.rb
27
+ lib/chronic/scalar.rb
28
+ lib/chronic/separator.rb
29
+ lib/chronic/time_zone.rb
30
+ lib/numerizer/numerizer.rb
31
+ test/suite.rb
32
+ test/test_Chronic.rb
33
+ test/test_Handler.rb
34
+ test/test_Numerizer.rb
35
+ test/test_RepeaterDayName.rb
36
+ test/test_RepeaterFortnight.rb
37
+ test/test_RepeaterHour.rb
38
+ test/test_RepeaterMonth.rb
39
+ test/test_RepeaterMonthName.rb
40
+ test/test_RepeaterTime.rb
41
+ test/test_RepeaterWeek.rb
42
+ test/test_RepeaterWeekend.rb
43
+ test/test_RepeaterYear.rb
44
+ test/test_Span.rb
45
+ test/test_Time.rb
46
+ test/test_Token.rb
47
+ test/test_parsing.rb
@@ -1,17 +1,22 @@
1
- =Chronic
1
+ Chronic
2
+ http://chronic.rubyforge.org/
3
+ by Tom Preston-Werner
4
+
5
+ == DESCRIPTION:
2
6
 
3
7
  Chronic is a natural language date/time parser written in pure Ruby. See below for the wide variety of formats Chronic will parse.
4
8
 
5
- ==Installation
9
+ == INSTALLATION:
6
10
 
7
11
  Chronic can be installed via RubyGems:
8
12
 
9
13
  $ sudo gem install chronic
10
14
 
11
- ==Usage
15
+ == USAGE:
12
16
 
13
17
  You can parse strings containing a natural language date using the Chronic.parse method.
14
18
 
19
+ require 'rubygems'
15
20
  require 'chronic'
16
21
 
17
22
  Time.now #=> Sun Aug 27 23:18:25 PDT 2006
@@ -38,7 +43,7 @@ You can parse strings containing a natural language date using the Chronic.parse
38
43
 
39
44
  See Chronic.parse for detailed usage instructions.
40
45
 
41
- ==Examples
46
+ == EXAMPLES:
42
47
 
43
48
  Chronic can parse a huge variety of date and time formats. Following is a small sample of strings that will be properly parsed. Parsing is case insensitive and will handle common abbreviations and misspellings.
44
49
 
@@ -109,11 +114,36 @@ Specific Dates
109
114
  Specific Times (many of the above with an added time)
110
115
 
111
116
  January 5 at 7pm
112
- 1979-05-27 05:00
117
+ 1979-05-27 05:00:00
113
118
  etc
114
119
 
115
- ==Limitations
120
+ == LIMITATIONS:
116
121
 
117
122
  Chronic uses Ruby's built in Time class for all time storage and computation. Because of this, only times that the Time class can handle will be properly parsed. Parsing for times outside of this range will simply return nil. Support for a wider range of times is planned for a future release.
118
123
 
119
- Time zones other than the local one are not currently supported. Support for other time zones is planned for a future release.
124
+ Time zones other than the local one are not currently supported. Support for other time zones is planned for a future release.
125
+
126
+ == LICENSE:
127
+
128
+ (The MIT License)
129
+
130
+ Copyright (c) 2006 Ryan Davis, Zen Spider Software
131
+
132
+ Permission is hereby granted, free of charge, to any person obtaining
133
+ a copy of this software and associated documentation files (the
134
+ "Software"), to deal in the Software without restriction, including
135
+ without limitation the rights to use, copy, modify, merge, publish,
136
+ distribute, sublicense, and/or sell copies of the Software, and to
137
+ permit persons to whom the Software is furnished to do so, subject to
138
+ the following conditions:
139
+
140
+ The above copyright notice and this permission notice shall be
141
+ included in all copies or substantial portions of the Software.
142
+
143
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
144
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
145
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
146
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
147
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
148
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
149
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/chronic.rb'
6
+
7
+ Hoe.new('chronic', Chronic::VERSION) do |p|
8
+ p.rubyforge_name = 'chronic'
9
+ p.summary = 'A natural language date parser'
10
+ p.author = 'Tom Preston-Werner'
11
+ p.email = 'tom@rubyisawesome.com'
12
+ p.description = p.paragraphs_of('README.txt', 2).join("\n\n")
13
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
14
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
15
+ p.need_tar = false
16
+ p.extra_deps = []
17
+ end
18
+
19
+ # vim: syntax=Ruby
@@ -50,7 +50,7 @@ module Chronic
50
50
  specified_options.keys.each do |key|
51
51
  default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
52
52
  end
53
- [:past, :future].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value '#{options[:context]}' for :context specified. Valid values are :past and :future.")
53
+ [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
54
54
 
55
55
  # store now for later =)
56
56
  @now = options[:now]
@@ -66,14 +66,14 @@ module Chronic
66
66
  @tokens = tokenizer.scan(@tokens, options)
67
67
  end
68
68
 
69
- [Grabber, Pointer, Scalar, Ordinal, Separator].each do |tokenizer|
69
+ [Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
70
70
  @tokens = tokenizer.scan(@tokens)
71
71
  end
72
72
 
73
73
  # strip any non-tagged tokens
74
74
  @tokens = @tokens.select { |token| token.tagged? }
75
75
 
76
- if @debug
76
+ if Chronic.debug
77
77
  puts "+---------------------------------------------------"
78
78
  puts "| " + @tokens.to_s
79
79
  puts "+---------------------------------------------------"
@@ -100,7 +100,8 @@ module Chronic
100
100
  # to numbers (three => 3), and converting ordinal words to numeric
101
101
  # ordinals (third => 3rd)
102
102
  def pre_normalize(text) #:nodoc:
103
- normalized_text = text.downcase
103
+ normalized_text = text.to_s.downcase
104
+ normalized_text = numericize_numbers(normalized_text)
104
105
  normalized_text.gsub!(/['"\.]/, '')
105
106
  normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
106
107
  normalized_text.gsub!(/\btoday\b/, 'this day')
@@ -108,7 +109,6 @@ module Chronic
108
109
  normalized_text.gsub!(/\byesterday\b/, 'last day')
109
110
  normalized_text.gsub!(/\bnoon\b/, '12:00')
110
111
  normalized_text.gsub!(/\bmidnight\b/, '24:00')
111
- normalized_text.gsub!(/\bfrom now\b/, 'future')
112
112
  normalized_text.gsub!(/\bbefore now\b/, 'past')
113
113
  normalized_text.gsub!(/\bnow\b/, 'this second')
114
114
  normalized_text.gsub!(/\b(ago|before)\b/, 'past')
@@ -119,15 +119,12 @@ module Chronic
119
119
  normalized_text.gsub!(/\btonight\b/, 'this night')
120
120
  normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
121
121
  normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
122
- normalized_text.gsub!(/\ba\b/, '1')
123
- normalized_text.gsub!(/\s+/, ' ')
124
- normalized_text = numericize_numbers(normalized_text)
125
122
  normalized_text = numericize_ordinals(normalized_text)
126
123
  end
127
124
 
128
125
  # Convert number words to numbers (three => 3)
129
126
  def numericize_numbers(text) #:nodoc:
130
- text
127
+ Numerizer.numerize(text)
131
128
  end
132
129
 
133
130
  # Convert ordinal words to numeric ordinals (third => 3rd)
@@ -6,7 +6,8 @@ module Chronic
6
6
  @definitions ||=
7
7
  {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
8
8
 
9
- :date => [Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
9
+ :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
10
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
10
11
  Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
11
12
  Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
12
13
  Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
@@ -17,13 +18,17 @@ module Chronic
17
18
  Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
18
19
  Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
19
20
 
21
+ # tonight at 7pm
20
22
  :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
23
+ Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
21
24
  Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
22
25
 
26
+ # 3 weeks from now, in 2 months
23
27
  :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
24
28
  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
25
29
  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
26
30
 
31
+ # 3rd week in march
27
32
  :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
28
33
  Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
29
34
  }
@@ -34,6 +39,7 @@ module Chronic
34
39
 
35
40
  self.definitions[:date].each do |handler|
36
41
  if handler.match(tokens, self.definitions)
42
+ puts "-date" if Chronic.debug
37
43
  good_tokens = tokens.select { |o| !o.get_tag Separator }
38
44
  return self.send(handler.handler_method, good_tokens, options)
39
45
  end
@@ -43,6 +49,7 @@ module Chronic
43
49
 
44
50
  self.definitions[:anchor].each do |handler|
45
51
  if handler.match(tokens, self.definitions)
52
+ puts "-anchor" if Chronic.debug
46
53
  good_tokens = tokens.select { |o| !o.get_tag Separator }
47
54
  return self.send(handler.handler_method, good_tokens, options)
48
55
  end
@@ -52,21 +59,24 @@ module Chronic
52
59
 
53
60
  self.definitions[:arrow].each do |handler|
54
61
  if handler.match(tokens, self.definitions)
62
+ puts "-arrow" if Chronic.debug
55
63
  good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
56
64
  return self.send(handler.handler_method, good_tokens, options)
57
65
  end
58
66
  end
59
67
 
60
- # not an arrow, let's hope it's an narrow
68
+ # not an arrow, let's hope it's a narrow
61
69
 
62
70
  self.definitions[:narrow].each do |handler|
63
71
  if handler.match(tokens, self.definitions)
72
+ puts "-narrow" if Chronic.debug
64
73
  #good_tokens = tokens.select { |o| !o.get_tag Separator }
65
74
  return self.send(handler.handler_method, tokens, options)
66
75
  end
67
76
  end
68
77
 
69
78
  # I guess you're out of luck!
79
+ puts "-none" if Chronic.debug
70
80
  return nil
71
81
  end
72
82
 
@@ -88,7 +98,7 @@ module Chronic
88
98
 
89
99
  def handle_m_d(month, day, time_tokens, options) #:nodoc:
90
100
  month.start = @now
91
- span = month.next(options[:context])
101
+ span = month.this(options[:context])
92
102
 
93
103
  day_start = Time.local(span.begin.year, span.begin.month, day)
94
104
 
@@ -122,6 +132,19 @@ module Chronic
122
132
  end
123
133
  end
124
134
 
135
+ def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
136
+ month = tokens[1].get_tag(RepeaterMonthName).index
137
+ day = tokens[2].get_tag(ScalarDay).type
138
+ year = tokens[5].get_tag(ScalarYear).type
139
+
140
+ begin
141
+ day_start = Time.local(year, month, day)
142
+ day_or_time(day_start, [tokens[3]], options)
143
+ rescue ArgumentError
144
+ nil
145
+ end
146
+ end
147
+
125
148
  def handle_rmn_sd_sy(tokens, options) #:nodoc:
126
149
  month = tokens[0].get_tag(RepeaterMonthName).index
127
150
  day = tokens[1].get_tag(ScalarDay).type
@@ -213,18 +236,20 @@ module Chronic
213
236
 
214
237
  def handle_s_r_p(tokens, options) #:nodoc:
215
238
  repeater = tokens[1].get_tag(Repeater)
239
+
240
+ # span =
241
+ # case true
242
+ # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
243
+ # self.parse("this hour", :guess => false, :now => @now)
244
+ # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
245
+ # self.parse("this minute", :guess => false, :now => @now)
246
+ # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
247
+ # self.parse("this second", :guess => false, :now => @now)
248
+ # else
249
+ # raise(ChronicPain, "Invalid repeater: #{repeater.class}")
250
+ # end
216
251
 
217
- span =
218
- case true
219
- when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
220
- self.parse("this hour", :guess => false, :now => @now)
221
- when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
222
- self.parse("this minute", :guess => false, :now => @now)
223
- when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
224
- self.parse("this second", :guess => false, :now => @now)
225
- else
226
- raise(ChronicPain, "Invalid repeater: #{repeater.class}")
227
- end
252
+ span = self.parse("this second", :guess => false, :now => @now)
228
253
 
229
254
  self.handle_srp(tokens, span, options)
230
255
  end
@@ -274,32 +299,38 @@ module Chronic
274
299
 
275
300
  repeaters = self.get_repeaters(tokens)
276
301
  repeaters.size.times { tokens.pop }
277
-
278
- if tokens.last && tokens.last.get_tag(Grabber)
279
- grabber = tokens.last.get_tag(Grabber)
302
+
303
+ if tokens.first && tokens.first.get_tag(Grabber)
304
+ grabber = tokens.first.get_tag(Grabber)
280
305
  tokens.pop
281
306
  end
282
307
 
283
308
  head = repeaters.shift
284
309
  head.start = @now
285
-
310
+
286
311
  case grabber.type
287
- when :last: outer_span = head.next(:past)
288
- when :this: outer_span = head.this(options[:context])
289
- when :next: outer_span = head.next(:future)
312
+ when :last
313
+ outer_span = head.next(:past)
314
+ when :this
315
+ if repeaters.size > 0
316
+ outer_span = head.this(:none)
317
+ else
318
+ outer_span = head.this(options[:context])
319
+ end
320
+ when :next
321
+ outer_span = head.next(:future)
290
322
  else raise(ChronicPain, "Invalid grabber")
291
323
  end
292
324
 
325
+ puts "--#{outer_span}" if Chronic.debug
293
326
  anchor = find_within(repeaters, outer_span, pointer)
294
327
  end
295
328
 
296
329
  def get_repeaters(tokens) #:nodoc:
297
330
  repeaters = []
298
- tokens.reverse.each do |token|
331
+ tokens.each do |token|
299
332
  if t = token.get_tag(Repeater)
300
333
  repeaters << t
301
- else
302
- break
303
334
  end
304
335
  end
305
336
  repeaters.sort.reverse
@@ -309,12 +340,13 @@ module Chronic
309
340
  # Returns a Span representing the innermost time span
310
341
  # or nil if no repeater union could be found
311
342
  def find_within(tags, span, pointer) #:nodoc:
343
+ puts "--#{span}" if Chronic.debug
312
344
  return span if tags.empty?
313
345
 
314
346
  head, *rest = tags
315
347
  head.start = pointer == :future ? span.begin : span.end
316
- h = head.next(pointer)
317
-
348
+ h = head.this(:none)
349
+
318
350
  if span.include?(h.begin) || span.include?(h.end)
319
351
  return find_within(rest, h, pointer)
320
352
  else
@@ -324,22 +356,54 @@ module Chronic
324
356
 
325
357
  def dealias_and_disambiguate_times(tokens, options) #:nodoc:
326
358
  # handle aliases of am/pm
327
- # 5:00 in the morning => 5:00 am
328
- # 7:00 in the evening => 7:00 pm
329
- #ttokens = []
330
- tokens.each_with_index do |t0, i|
331
- t1 = tokens[i + 1]
332
- if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
333
- if [:morning].include?(t1tag.type)
334
- t1.untag(RepeaterDayPortion)
335
- t1.tag(RepeaterDayPortion.new(:am))
336
- elsif [:afternoon, :evening, :night].include?(t1tag.type)
337
- t1.untag(RepeaterDayPortion)
338
- t1.tag(RepeaterDayPortion.new(:pm))
339
- end
359
+ # 5:00 in the morning -> 5:00 am
360
+ # 7:00 in the evening -> 7:00 pm
361
+
362
+ day_portion_index = nil
363
+ tokens.each_with_index do |t, i|
364
+ if t.get_tag(RepeaterDayPortion)
365
+ day_portion_index = i
366
+ break
340
367
  end
341
368
  end
342
- #tokens = ttokens
369
+
370
+ time_index = nil
371
+ tokens.each_with_index do |t, i|
372
+ if t.get_tag(RepeaterTime)
373
+ time_index = i
374
+ break
375
+ end
376
+ end
377
+
378
+ if (day_portion_index && time_index)
379
+ t1 = tokens[day_portion_index]
380
+ t1tag = t1.get_tag(RepeaterDayPortion)
381
+
382
+ if [:morning].include?(t1tag.type)
383
+ puts '--morning->am' if Chronic.debug
384
+ t1.untag(RepeaterDayPortion)
385
+ t1.tag(RepeaterDayPortion.new(:am))
386
+ elsif [:afternoon, :evening, :night].include?(t1tag.type)
387
+ puts "--#{t1tag.type}->pm" if Chronic.debug
388
+ t1.untag(RepeaterDayPortion)
389
+ t1.tag(RepeaterDayPortion.new(:pm))
390
+ end
391
+ end
392
+
393
+ # tokens.each_with_index do |t0, i|
394
+ # t1 = tokens[i + 1]
395
+ # if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
396
+ # if [:morning].include?(t1tag.type)
397
+ # puts '--morning->am' if Chronic.debug
398
+ # t1.untag(RepeaterDayPortion)
399
+ # t1.tag(RepeaterDayPortion.new(:am))
400
+ # elsif [:afternoon, :evening, :night].include?(t1tag.type)
401
+ # puts "--#{t1tag.type}->pm" if Chronic.debug
402
+ # t1.untag(RepeaterDayPortion)
403
+ # t1.tag(RepeaterDayPortion.new(:pm))
404
+ # end
405
+ # end
406
+ # end
343
407
 
344
408
  # handle ambiguous times if :ambiguous_time_range is specified
345
409
  if options[:ambiguous_time_range] != :none
@@ -10,9 +10,9 @@ module Chronic
10
10
  end
11
11
 
12
12
  def self.scan_for_all(token)
13
- scanner = {/past/ => :past,
14
- /future/ => :future,
15
- /in/ => :future}
13
+ scanner = {/\bpast\b/ => :past,
14
+ /\bfuture\b/ => :future,
15
+ /\bin\b/ => :future}
16
16
  scanner.keys.each do |scanner_item|
17
17
  return self.new(scanner[scanner_item]) if scanner_item =~ token.word
18
18
  end
@@ -6,7 +6,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
6
6
  if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
7
7
  if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
8
8
  if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
9
- if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
9
+ if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
10
10
  end
11
11
  tokens
12
12
  end
@@ -20,7 +20,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
20
20
  /^jun\.?e?$/ => :june,
21
21
  /^jul\.?y?$/ => :july,
22
22
  /^aug\.?(ust)?$/ => :august,
23
- /^sep\.?(tember)?$/ => :september,
23
+ /^sep\.?(t\.?|tember)?$/ => :september,
24
24
  /^oct\.?(ober)?$/ => :october,
25
25
  /^nov\.?(ember)?$/ => :november,
26
26
  /^dec\.?(ember)?$/ => :december}
@@ -33,6 +33,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
33
33
  def self.scan_for_day_names(token)
34
34
  scanner = {/^m[ou]n(day)?$/ => :monday,
35
35
  /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
36
+ /^tue$/ => :tuesday,
36
37
  /^we(dnes|nds|nns)day$/ => :wednesday,
37
38
  /^wed$/ => :wednesday,
38
39
  /^th(urs|ers)day$/ => :thursday,
@@ -52,7 +53,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
52
53
  /^mornings?$/ => :morning,
53
54
  /^afternoons?$/ => :afternoon,
54
55
  /^evenings?$/ => :evening,
55
- /^nights?$/ => :night}
56
+ /^(night|nite)s?$/ => :night}
56
57
  scanner.keys.each do |scanner_item|
57
58
  return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
58
59
  end
@@ -60,7 +61,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
60
61
  end
61
62
 
62
63
  def self.scan_for_times(token, options)
63
- if token.word =~ /^\d{1,2}(:?\d{2})?$/
64
+ if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
64
65
  return Chronic::RepeaterTime.new(token.word, options)
65
66
  end
66
67
  return nil
@@ -72,7 +73,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
72
73
  /^months?$/ => :month,
73
74
  /^fortnights?$/ => :fortnight,
74
75
  /^weeks?$/ => :week,
75
- /^weekends?$/ => :weekends,
76
+ /^weekends?$/ => :weekend,
76
77
  /^days?$/ => :day,
77
78
  /^hours?$/ => :hour,
78
79
  /^minutes?$/ => :minute,
@@ -99,13 +100,13 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
99
100
  # returns the next occurance of this repeatable.
100
101
  def next(pointer)
101
102
  !@now.nil? || raise("Start point must be set before calling #next")
102
- [:future, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
103
+ [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
103
104
  #raise("Repeatable#next must be overridden in subclasses")
104
105
  end
105
106
 
106
107
  def this(pointer)
107
- !@now.nil? || raise("Start point must be set before calling #next")
108
- [:future, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
108
+ !@now.nil? || raise("Start point must be set before calling #this")
109
+ [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
109
110
  end
110
111
 
111
112
  def to_s
@@ -4,10 +4,14 @@ class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
4
4
  def next(pointer)
5
5
  super
6
6
 
7
- @current_day ||= Date.parse(@now.to_s)
8
- @current_day += pointer == :future ? 1 : -1
9
- span_begin = Time.parse(@current_day.to_s)
10
- Chronic::Span.new(span_begin, span_begin + width)
7
+ if !@current_day_start
8
+ @current_day_start = Time.local(@now.year, @now.month, @now.day)
9
+ end
10
+
11
+ direction = pointer == :future ? 1 : -1
12
+ @current_day_start += direction * DAY_SECONDS
13
+
14
+ Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
11
15
  end
12
16
 
13
17
  def this(pointer = :future)
@@ -15,11 +19,14 @@ class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
15
19
 
16
20
  case pointer
17
21
  when :future
18
- day_begin = Time.local(@now.year, @now.month, @now.day, @now.hour + 1)
19
- day_end = Time.local(@now.year, @now.month, @now.day) + DAY_SECONDS
22
+ day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
23
+ day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
20
24
  when :past
21
- day_begin = Time.local(@now.year, @now.month, @now.day)
22
- day_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
25
+ day_begin = Time.construct(@now.year, @now.month, @now.day)
26
+ day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
27
+ when :none
28
+ day_begin = Time.construct(@now.year, @now.month, @now.day)
29
+ day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
23
30
  end
24
31
 
25
32
  Chronic::Span.new(day_begin, day_end)