chronic 0.1.0 → 0.1.5

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,29 @@
1
+ = 0.1.5 2006-12-20
2
+
3
+ * fixed 'aug 20' returning next year if current month is august
4
+ * modified behavior of 'from now'
5
+ * added support for seconds on times, and thus db timestamp format: "2006-12-20 18:04:23"
6
+ * made Hoe compliant
7
+
8
+ = 0.1.4
9
+
10
+ * removed verbose error checking code. oops. :-/
11
+
12
+ = 0.1.3
13
+
14
+ * improved regexes for word variations (Josh Goebel)
15
+ * fixed a bug that caused "today at 3am" to return nil if current time is after 3am
16
+
17
+ = 0.1.2
18
+
19
+ * removed Date dependency (now works on windows properly without fiddling)
20
+
21
+ = 0.1.1
22
+
23
+ * run to_s on incoming object
24
+ * fixed loop loading of repeaters files (out of order on some machines)
25
+ * fixed find_within to use this instead of next (was breaking "today at 6pm")
26
+
27
+ = 0.1.0
28
+
29
+ * initial release
data/Manifest.txt ADDED
@@ -0,0 +1,43 @@
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
+ test/parse_numbers.rb
30
+ test/suite.rb
31
+ test/test_Chronic.rb
32
+ test/test_Handler.rb
33
+ test/test_RepeaterDayName.rb
34
+ test/test_RepeaterFortnight.rb
35
+ test/test_RepeaterHour.rb
36
+ test/test_RepeaterMonth.rb
37
+ test/test_RepeaterMonthName.rb
38
+ test/test_RepeaterTime.rb
39
+ test/test_RepeaterWeek.rb
40
+ test/test_RepeaterYear.rb
41
+ test/test_Span.rb
42
+ test/test_Token.rb
43
+ 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,39 @@
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.description = p.paragraphs_of('README.txt', 2).join("\n\n")
11
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
12
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
13
+ end
14
+
15
+ # vim: syntax=Ruby
16
+
17
+ __END__
18
+
19
+ require 'rubygems'
20
+
21
+ SPEC = Gem::Specification.new do |s|
22
+ s.name = 'chronic'
23
+ s.version = '0.1.5'
24
+ s.author = 'Tom Preston-Werner'
25
+ s.email = 'tom@rubyisawesome.com'
26
+ s.homepage = 'http://chronic.rubyforge.org'
27
+ s.platform = Gem::Platform::RUBY
28
+ s.summary = "A natural language date parser"
29
+ candidates = Dir["{lib,test}/**/*"]
30
+ s.files = candidates.delete_if do |item|
31
+ item.include?('.svn')
32
+ end
33
+ s.require_path = "lib"
34
+ s.autorequire = "chronic"
35
+ s.test_file = "test/suite.rb"
36
+ s.has_rdoc = true
37
+ s.extra_rdoc_files = ['README']
38
+ s.rdoc_options << '--main' << 'README'
39
+ end
@@ -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]
@@ -73,7 +73,7 @@ module Chronic
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,7 @@ 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
104
  normalized_text.gsub!(/['"\.]/, '')
105
105
  normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
106
106
  normalized_text.gsub!(/\btoday\b/, 'this day')
@@ -108,7 +108,6 @@ module Chronic
108
108
  normalized_text.gsub!(/\byesterday\b/, 'last day')
109
109
  normalized_text.gsub!(/\bnoon\b/, '12:00')
110
110
  normalized_text.gsub!(/\bmidnight\b/, '24:00')
111
- normalized_text.gsub!(/\bfrom now\b/, 'future')
112
111
  normalized_text.gsub!(/\bbefore now\b/, 'past')
113
112
  normalized_text.gsub!(/\bnow\b/, 'this second')
114
113
  normalized_text.gsub!(/\b(ago|before)\b/, 'past')
@@ -88,7 +88,7 @@ module Chronic
88
88
 
89
89
  def handle_m_d(month, day, time_tokens, options) #:nodoc:
90
90
  month.start = @now
91
- span = month.next(options[:context])
91
+ span = month.this(options[:context])
92
92
 
93
93
  day_start = Time.local(span.begin.year, span.begin.month, day)
94
94
 
@@ -213,7 +213,7 @@ module Chronic
213
213
 
214
214
  def handle_s_r_p(tokens, options) #:nodoc:
215
215
  repeater = tokens[1].get_tag(Repeater)
216
-
216
+
217
217
  span =
218
218
  case true
219
219
  when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
@@ -274,32 +274,38 @@ module Chronic
274
274
 
275
275
  repeaters = self.get_repeaters(tokens)
276
276
  repeaters.size.times { tokens.pop }
277
-
278
- if tokens.last && tokens.last.get_tag(Grabber)
279
- grabber = tokens.last.get_tag(Grabber)
277
+
278
+ if tokens.first && tokens.first.get_tag(Grabber)
279
+ grabber = tokens.first.get_tag(Grabber)
280
280
  tokens.pop
281
281
  end
282
282
 
283
283
  head = repeaters.shift
284
284
  head.start = @now
285
-
285
+
286
286
  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)
287
+ when :last
288
+ outer_span = head.next(:past)
289
+ when :this
290
+ if repeaters.size > 0
291
+ outer_span = head.this(:none)
292
+ else
293
+ outer_span = head.this(options[:context])
294
+ end
295
+ when :next
296
+ outer_span = head.next(:future)
290
297
  else raise(ChronicPain, "Invalid grabber")
291
298
  end
292
299
 
300
+ puts "--#{outer_span}" if Chronic.debug
293
301
  anchor = find_within(repeaters, outer_span, pointer)
294
302
  end
295
303
 
296
304
  def get_repeaters(tokens) #:nodoc:
297
305
  repeaters = []
298
- tokens.reverse.each do |token|
306
+ tokens.each do |token|
299
307
  if t = token.get_tag(Repeater)
300
308
  repeaters << t
301
- else
302
- break
303
309
  end
304
310
  end
305
311
  repeaters.sort.reverse
@@ -309,12 +315,13 @@ module Chronic
309
315
  # Returns a Span representing the innermost time span
310
316
  # or nil if no repeater union could be found
311
317
  def find_within(tags, span, pointer) #:nodoc:
318
+ puts "--#{span}" if Chronic.debug
312
319
  return span if tags.empty?
313
320
 
314
321
  head, *rest = tags
315
322
  head.start = pointer == :future ? span.begin : span.end
316
- h = head.next(pointer)
317
-
323
+ h = head.this(:none)
324
+
318
325
  if span.include?(h.begin) || span.include?(h.end)
319
326
  return find_within(rest, h, pointer)
320
327
  else
@@ -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}
@@ -52,7 +52,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
52
52
  /^mornings?$/ => :morning,
53
53
  /^afternoons?$/ => :afternoon,
54
54
  /^evenings?$/ => :evening,
55
- /^nights?$/ => :night}
55
+ /^(night|nite)s?$/ => :night}
56
56
  scanner.keys.each do |scanner_item|
57
57
  return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
58
58
  end
@@ -60,7 +60,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
60
60
  end
61
61
 
62
62
  def self.scan_for_times(token, options)
63
- if token.word =~ /^\d{1,2}(:?\d{2})?$/
63
+ if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
64
64
  return Chronic::RepeaterTime.new(token.word, options)
65
65
  end
66
66
  return nil
@@ -99,13 +99,13 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
99
99
  # returns the next occurance of this repeatable.
100
100
  def next(pointer)
101
101
  !@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")
102
+ [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
103
103
  #raise("Repeatable#next must be overridden in subclasses")
104
104
  end
105
105
 
106
106
  def this(pointer)
107
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
+ [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
109
109
  end
110
110
 
111
111
  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)
@@ -20,6 +24,9 @@ class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
20
24
  when :past
21
25
  day_begin = Time.local(@now.year, @now.month, @now.day)
22
26
  day_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
27
+ when :none
28
+ day_begin = Time.local(@now.year, @now.month, @now.day)
29
+ day_end = Time.local(@now.year, @now.month, @now.day) + DAY_SECONDS
23
30
  end
24
31
 
25
32
  Chronic::Span.new(day_begin, day_end)
@@ -4,24 +4,29 @@ class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
4
4
  def next(pointer)
5
5
  super
6
6
 
7
- if !@current_day
8
- @current_day = Date.parse(@now.to_s)
9
- @current_day += pointer == :future ? 1 : -1
7
+ direction = pointer == :future ? 1 : -1
8
+
9
+ if !@current_day_start
10
+ @current_day_start = Time.local(@now.year, @now.month, @now.day)
11
+ @current_day_start += direction * DAY_SECONDS
12
+
10
13
  day_num = symbol_to_number(@type)
11
- while @current_day.wday != day_num
12
- @current_day += pointer == :future ? 1 : -1
14
+
15
+ while @current_day_start.wday != day_num
16
+ @current_day_start += direction * DAY_SECONDS
13
17
  end
14
18
  else
15
- @current_day += pointer == :future ? 7 : -7
19
+ @current_day_start += direction * 7 * DAY_SECONDS
16
20
  end
17
- span_begin = Time.parse(@current_day.to_s)
18
- Chronic::Span.new(span_begin, span_begin + width)
21
+
22
+ Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
19
23
  end
20
24
 
21
25
  def this(pointer = :future)
22
26
  super
23
27
 
24
- self.next(:future)
28
+ pointer = :future if pointer == :none
29
+ self.next(pointer)
25
30
  end
26
31
 
27
32
  def width
@@ -29,6 +29,8 @@ class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
29
29
  def this(pointer = :future)
30
30
  super
31
31
 
32
+ pointer = :future if pointer == :none
33
+
32
34
  case pointer
33
35
  when :future
34
36
  this_fortnight_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
@@ -42,7 +44,6 @@ class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
42
44
  this_fortnight_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
43
45
  sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
44
46
  sunday_repeater.start = @now
45
- #sunday_repeater.next(:past)
46
47
  last_sunday_span = sunday_repeater.next(:past)
47
48
  this_fortnight_start = last_sunday_span.begin
48
49
  Chronic::Span.new(this_fortnight_start, this_fortnight_end)
@@ -1,9 +1,30 @@
1
1
  class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
2
2
  MINUTE_SECONDS = 60
3
3
 
4
+ def next(pointer = :future)
5
+ super
6
+
7
+ direction = pointer == :future ? 1 : -1
8
+
9
+ raise 'not implemented'
10
+ end
11
+
4
12
  def this(pointer = :future)
5
- minute_begin = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min)
6
- Chronic::Span.new(minute_begin, minute_begin + MINUTE_SECONDS)
13
+ super
14
+
15
+ case pointer
16
+ when :future
17
+ minute_begin = @now
18
+ minute_end = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min)
19
+ when :past
20
+ minute_begin = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min)
21
+ minute_end = @now
22
+ when :none
23
+ minute_begin = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min)
24
+ minute_end = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
25
+ end
26
+
27
+ Chronic::Span.new(minute_begin, minute_end)
7
28
  end
8
29
 
9
30
  def offset(span, amount, pointer)
@@ -24,6 +24,9 @@ class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
24
24
  when :past
25
25
  month_start = Time.local(@now.year, @now.month)
26
26
  month_end = Time.local(@now.year, @now.month, @now.day)
27
+ when :none
28
+ month_start = Time.local(@now.year, @now.month)
29
+ month_end = self.offset_by(Time.local(@now.year, @now.month), 1, :future)
27
30
  end
28
31
 
29
32
  Chronic::Span.new(month_start, month_end)
@@ -13,6 +13,12 @@ class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
13
13
  else @now.month > target_month
14
14
  @current_month_begin = Time.local(@now.year + 1, target_month)
15
15
  end
16
+ when :none
17
+ if @now.month <= target_month
18
+ @current_month_begin = Time.local(@now.year, target_month)
19
+ else @now.month > target_month
20
+ @current_month_begin = Time.local(@now.year + 1, target_month)
21
+ end
16
22
  when :past
17
23
  if @now.month > target_month
18
24
  @current_month_begin = Time.local(@now.year, target_month)
@@ -47,7 +53,12 @@ class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
47
53
  def this(pointer = :future)
48
54
  super
49
55
 
50
- self.next(pointer)
56
+ case pointer
57
+ when :past
58
+ self.next(pointer)
59
+ when :future, :none
60
+ self.next(:none)
61
+ end
51
62
  end
52
63
 
53
64
  def width
@@ -16,6 +16,8 @@ class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
16
16
  end
17
17
 
18
18
  def this(pointer = :future)
19
+ super
20
+
19
21
  Chronic::Span.new(@now, @now + 1)
20
22
  end
21
23
 
@@ -25,7 +25,7 @@ class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
25
25
  end
26
26
 
27
27
  def initialize(time, options = {})
28
- t = time.sub(/\:/, '')
28
+ t = time.gsub(/\:/, '')
29
29
  @type =
30
30
  if (1..2) === t.size
31
31
  Tick.new(t.to_i * 60 * 60, true)
@@ -34,8 +34,13 @@ class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
34
34
  elsif t.size == 4
35
35
  ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
36
36
  Tick.new(t[0..1].to_i * 60 * 60 + t[2..3].to_i * 60, ambiguous)
37
+ elsif t.size == 5
38
+ Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
39
+ elsif t.size == 6
40
+ ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
41
+ Tick.new(t[0..1].to_i * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
37
42
  else
38
- raise("Time cannot exceed four digits")
43
+ raise("Time cannot exceed six digits")
39
44
  end
40
45
  end
41
46
 
@@ -92,7 +97,10 @@ class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
92
97
  end
93
98
 
94
99
  def this(context = :future)
95
- [:future, :past].include?(context) || raise("First argument 'context' must be one of :past or :future")
100
+ super
101
+
102
+ context = :future if context == :none
103
+
96
104
  self.next(context)
97
105
  end
98
106
 
@@ -44,6 +44,12 @@ class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
44
44
  last_sunday_span = sunday_repeater.next(:past)
45
45
  this_week_start = last_sunday_span.begin
46
46
  Chronic::Span.new(this_week_start, this_week_end)
47
+ when :none
48
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
49
+ sunday_repeater.start = @now
50
+ last_sunday_span = sunday_repeater.next(:past)
51
+ this_week_start = last_sunday_span.begin
52
+ Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
47
53
  end
48
54
  end
49
55
 
@@ -28,6 +28,9 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
28
28
  when :past
29
29
  this_year_start = Time.local(@now.year, 1, 1)
30
30
  this_year_end = Time.local(@now.year, @now.month, @now.day)
31
+ when :none
32
+ this_year_start = Time.local(@now.year, 1, 1)
33
+ this_year_end = Time.local(@now.year + 1, 1, 1)
31
34
  end
32
35
 
33
36
  Chronic::Span.new(this_year_start, this_year_end)
@@ -40,7 +40,7 @@ module Chronic
40
40
  end
41
41
 
42
42
  def self.scan_for_years(token, post_token)
43
- if token.word =~ /^\d\d(\d\d)?$/
43
+ if token.word =~ /^([1-9]\d)?\d\d?$/
44
44
  unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
45
45
  return ScalarYear.new(token.word.to_i)
46
46
  end
data/lib/chronic.rb CHANGED
@@ -7,24 +7,41 @@
7
7
  #
8
8
  #=============================================================================
9
9
 
10
- require 'date'
11
-
12
10
  require 'chronic/chronic'
13
11
  require 'chronic/handlers'
12
+
13
+ require 'chronic/repeater'
14
+ require 'chronic/repeaters/repeater_year'
15
+ require 'chronic/repeaters/repeater_season'
16
+ require 'chronic/repeaters/repeater_season_name'
17
+ require 'chronic/repeaters/repeater_month'
18
+ require 'chronic/repeaters/repeater_month_name'
19
+ require 'chronic/repeaters/repeater_fortnight'
20
+ require 'chronic/repeaters/repeater_week'
21
+ require 'chronic/repeaters/repeater_weekend'
22
+ require 'chronic/repeaters/repeater_day'
23
+ require 'chronic/repeaters/repeater_day_name'
24
+ require 'chronic/repeaters/repeater_day_portion'
25
+ require 'chronic/repeaters/repeater_hour'
26
+ require 'chronic/repeaters/repeater_minute'
27
+ require 'chronic/repeaters/repeater_second'
28
+ require 'chronic/repeaters/repeater_time'
29
+
14
30
  require 'chronic/grabber'
15
- require 'chronic/ordinal'
16
31
  require 'chronic/pointer'
17
32
  require 'chronic/scalar'
33
+ require 'chronic/ordinal'
18
34
  require 'chronic/separator'
19
35
 
20
- require 'chronic/repeater'
21
- Dir["#{File.dirname(__FILE__)}/chronic/repeaters/*.rb"].each do |file|
22
- require file
23
- end
24
-
25
36
  module Chronic
26
- def self.debug=(val); @debug = val; end
37
+ VERSION = "0.1.5"
38
+
39
+ def self.debug; false; end
27
40
  end
28
41
 
29
- Chronic.debug = false
42
+ alias p_orig p
30
43
 
44
+ def p(val)
45
+ p_orig val
46
+ puts
47
+ end
data/test/test_parsing.rb CHANGED
@@ -7,7 +7,6 @@ class TestParsing < Test::Unit::TestCase
7
7
  # Wed Aug 16 14:00:00 UTC 2006
8
8
  @time_2006_08_16_14_00_00 = Time.local(2006, 8, 16, 14, 0, 0, 0)
9
9
  @time_2006_08_16_03_00_00 = Time.local(2006, 8, 16, 3, 0, 0, 0)
10
- Chronic.debug = false
11
10
  end
12
11
 
13
12
  def test__parse_guess_dates
@@ -25,6 +24,9 @@ class TestParsing < Test::Unit::TestCase
25
24
  time = Chronic.parse("may 28 at 5pm", :now => @time_2006_08_16_14_00_00, :context => :past)
26
25
  assert_equal Time.local(2006, 5, 28, 17), time
27
26
 
27
+ time = Chronic.parse("may 28 at 5:32.19pm", :now => @time_2006_08_16_14_00_00, :context => :past)
28
+ assert_equal Time.local(2006, 5, 28, 17, 32, 19), time
29
+
28
30
  # rm_od
29
31
 
30
32
  time = Chronic.parse("may 27th", :now => @time_2006_08_16_14_00_00)
@@ -125,6 +127,15 @@ class TestParsing < Test::Unit::TestCase
125
127
  time = Chronic.parse("2006-08-20 03:00", :now => @time_2006_08_16_14_00_00)
126
128
  assert_equal Time.local(2006, 8, 20, 3), time
127
129
 
130
+ time = Chronic.parse("2006-08-20 03:30:30", :now => @time_2006_08_16_14_00_00)
131
+ assert_equal Time.local(2006, 8, 20, 3, 30, 30), time
132
+
133
+ time = Chronic.parse("2006-08-20 15:30:30", :now => @time_2006_08_16_14_00_00)
134
+ assert_equal Time.local(2006, 8, 20, 15, 30, 30), time
135
+
136
+ time = Chronic.parse("2006-08-20 15:30.30", :now => @time_2006_08_16_14_00_00)
137
+ assert_equal Time.local(2006, 8, 20, 15, 30, 30), time
138
+
128
139
  # rm_sd_rt
129
140
 
130
141
  #time = Chronic.parse("jan 5 13:00", :now => @time_2006_08_16_14_00_00)
@@ -184,8 +195,11 @@ class TestParsing < Test::Unit::TestCase
184
195
  time = Chronic.parse("4:00 in the morning", :now => @time_2006_08_16_14_00_00)
185
196
  assert_equal Time.local(2006, 8, 16, 4), time
186
197
 
187
- #time = Chronic.parse("november 4", :now => @time_2006_08_16_14_00_00)
188
- #assert_equal Time.local(2006, 11, 4, 12), time
198
+ time = Chronic.parse("november 4", :now => @time_2006_08_16_14_00_00)
199
+ assert_equal Time.local(2006, 11, 4, 12), time
200
+
201
+ time = Chronic.parse("aug 24", :now => @time_2006_08_16_14_00_00)
202
+ assert_equal Time.local(2006, 8, 24, 12), time
189
203
  end
190
204
 
191
205
  def test_parse_guess_rrr
@@ -300,10 +314,22 @@ class TestParsing < Test::Unit::TestCase
300
314
  assert_equal Time.local(2006, 8, 16, 13, 59, 59), time
301
315
  end
302
316
 
303
- def test_parse_guess_grr
317
+ def test_parse_guess_grr
304
318
  time = Chronic.parse("yesterday at 4:00", :now => @time_2006_08_16_14_00_00)
305
319
  assert_equal Time.local(2006, 8, 15, 16), time
306
320
 
321
+ time = Chronic.parse("today at 9:00", :now => @time_2006_08_16_14_00_00)
322
+ assert_equal Time.local(2006, 8, 16, 9), time
323
+
324
+ time = Chronic.parse("today at 2100", :now => @time_2006_08_16_14_00_00)
325
+ assert_equal Time.local(2006, 8, 16, 21), time
326
+
327
+ time = Chronic.parse("this day at 0900", :now => @time_2006_08_16_14_00_00)
328
+ assert_equal Time.local(2006, 8, 16, 9), time
329
+
330
+ time = Chronic.parse("tomorrow at 0900", :now => @time_2006_08_16_14_00_00)
331
+ assert_equal Time.local(2006, 8, 17, 9), time
332
+
307
333
  time = Chronic.parse("yesterday at 4:00", :now => @time_2006_08_16_14_00_00, :ambiguous_time_range => :none)
308
334
  assert_equal Time.local(2006, 8, 15, 4), time
309
335
 
@@ -321,6 +347,15 @@ class TestParsing < Test::Unit::TestCase
321
347
  end
322
348
 
323
349
  def test_parse_guess_grrr
350
+ time = Chronic.parse("today at 6:00pm", :now => @time_2006_08_16_14_00_00)
351
+ assert_equal Time.local(2006, 8, 16, 18), time
352
+
353
+ time = Chronic.parse("today at 6:00am", :now => @time_2006_08_16_14_00_00)
354
+ assert_equal Time.local(2006, 8, 16, 6), time
355
+
356
+ time = Chronic.parse("this day 1800", :now => @time_2006_08_16_14_00_00)
357
+ assert_equal Time.local(2006, 8, 16, 18), time
358
+
324
359
  time = Chronic.parse("yesterday at 4:00pm", :now => @time_2006_08_16_14_00_00)
325
360
  assert_equal Time.local(2006, 8, 15, 16), time
326
361
  end
@@ -352,7 +387,7 @@ class TestParsing < Test::Unit::TestCase
352
387
  assert_equal Time.local(2006, 7, 26, 14, 30, 30), time
353
388
 
354
389
  time = Chronic.parse("3 days ago", :now => @time_2006_08_16_14_00_00)
355
- assert_equal Time.local(2006, 8, 13, 14, 0, 30), time
390
+ assert_equal Time.local(2006, 8, 13, 14), time
356
391
 
357
392
  #time = Chronic.parse("1 monday ago", :now => @time_2006_08_16_14_00_00)
358
393
  #assert_equal Time.local(2006, 8, 14, 12), time
@@ -361,7 +396,7 @@ class TestParsing < Test::Unit::TestCase
361
396
  assert_equal Time.local(2006, 8, 12, 9), time
362
397
 
363
398
  time = Chronic.parse("7 hours ago", :now => @time_2006_08_16_14_00_00)
364
- assert_equal Time.local(2006, 8, 16, 7, 0, 30), time
399
+ assert_equal Time.local(2006, 8, 16, 7), time
365
400
 
366
401
  time = Chronic.parse("3 minutes ago", :now => @time_2006_08_16_14_00_00)
367
402
  assert_equal Time.local(2006, 8, 16, 13, 57), time
@@ -372,7 +407,7 @@ class TestParsing < Test::Unit::TestCase
372
407
  # future
373
408
 
374
409
  time = Chronic.parse("3 years from now", :now => @time_2006_08_16_14_00_00)
375
- assert_equal Time.local(2009, 8, 16, 14, 30, 30), time
410
+ assert_equal Time.local(2009, 8, 16, 14, 0, 0), time
376
411
 
377
412
  time = Chronic.parse("6 months hence", :now => @time_2006_08_16_14_00_00)
378
413
  assert_equal Time.local(2007, 2, 16, 14, 30, 30), time
@@ -381,16 +416,16 @@ class TestParsing < Test::Unit::TestCase
381
416
  assert_equal Time.local(2006, 9, 27, 14, 30, 30), time
382
417
 
383
418
  time = Chronic.parse("1 week from now", :now => @time_2006_08_16_14_00_00)
384
- assert_equal Time.local(2006, 8, 23, 14, 30, 30), time
419
+ assert_equal Time.local(2006, 8, 23, 14, 0, 0), time
385
420
 
386
421
  time = Chronic.parse("1 day hence", :now => @time_2006_08_16_14_00_00)
387
- assert_equal Time.local(2006, 8, 17, 14, 0, 30), time
422
+ assert_equal Time.local(2006, 8, 17, 14), time
388
423
 
389
424
  time = Chronic.parse("5 mornings hence", :now => @time_2006_08_16_14_00_00)
390
425
  assert_equal Time.local(2006, 8, 21, 9), time
391
426
 
392
427
  time = Chronic.parse("1 hour from now", :now => @time_2006_08_16_14_00_00)
393
- assert_equal Time.local(2006, 8, 16, 15, 0, 30), time
428
+ assert_equal Time.local(2006, 8, 16, 15), time
394
429
 
395
430
  time = Chronic.parse("20 minutes hence", :now => @time_2006_08_16_14_00_00)
396
431
  assert_equal Time.local(2006, 8, 16, 14, 20), time
@@ -401,7 +436,7 @@ class TestParsing < Test::Unit::TestCase
401
436
 
402
437
  def test_parse_guess_p_s_r
403
438
  time = Chronic.parse("in 3 hours", :now => @time_2006_08_16_14_00_00)
404
- assert_equal Time.local(2006, 8, 16, 17, 0, 30), time
439
+ assert_equal Time.local(2006, 8, 16, 17), time
405
440
  end
406
441
 
407
442
  def test_parse_guess_s_r_p_a
metadata CHANGED
@@ -3,16 +3,16 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: chronic
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.0
7
- date: 2006-09-06 00:00:00 -07:00
6
+ version: 0.1.5
7
+ date: 2006-12-21 00:00:00 -08:00
8
8
  summary: A natural language date parser
9
9
  require_paths:
10
10
  - lib
11
- email: tom@rubyisawesome.com
12
- homepage: http://chronic.rubyforge.org
13
- rubyforge_project:
14
- description:
15
- autorequire: chronic
11
+ email: ryand-ruby@zenspider.com
12
+ homepage: "\thttp://chronic.rubyforge.org/"
13
+ rubyforge_project: chronic
14
+ description: Chronic is a natural language date/time parser written in pure Ruby. See below for the wide variety of formats Chronic will parse.
15
+ autorequire:
16
16
  default_executable:
17
17
  bindir: bin
18
18
  has_rdoc: true
@@ -26,9 +26,12 @@ platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
28
  authors:
29
- - Tom Werner
29
+ - Ryan Davis
30
30
  files:
31
- - lib/chronic
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ - Rakefile
32
35
  - lib/chronic.rb
33
36
  - lib/chronic/chronic.rb
34
37
  - lib/chronic/grabber.rb
@@ -36,9 +39,6 @@ files:
36
39
  - lib/chronic/ordinal.rb
37
40
  - lib/chronic/pointer.rb
38
41
  - lib/chronic/repeater.rb
39
- - lib/chronic/repeaters
40
- - lib/chronic/scalar.rb
41
- - lib/chronic/separator.rb
42
42
  - lib/chronic/repeaters/repeater_day.rb
43
43
  - lib/chronic/repeaters/repeater_day_name.rb
44
44
  - lib/chronic/repeaters/repeater_day_portion.rb
@@ -54,11 +54,12 @@ files:
54
54
  - lib/chronic/repeaters/repeater_week.rb
55
55
  - lib/chronic/repeaters/repeater_weekend.rb
56
56
  - lib/chronic/repeaters/repeater_year.rb
57
+ - lib/chronic/scalar.rb
58
+ - lib/chronic/separator.rb
57
59
  - test/parse_numbers.rb
58
60
  - test/suite.rb
59
61
  - test/test_Chronic.rb
60
62
  - test/test_Handler.rb
61
- - test/test_parsing.rb
62
63
  - test/test_RepeaterDayName.rb
63
64
  - test/test_RepeaterFortnight.rb
64
65
  - test/test_RepeaterHour.rb
@@ -69,19 +70,38 @@ files:
69
70
  - test/test_RepeaterYear.rb
70
71
  - test/test_Span.rb
71
72
  - test/test_Token.rb
72
- - README
73
+ - test/test_parsing.rb
73
74
  test_files:
74
- - test/suite.rb
75
- rdoc_options:
76
- - --main
77
- - README
78
- extra_rdoc_files:
79
- - README
75
+ - test/test_Chronic.rb
76
+ - test/test_Handler.rb
77
+ - test/test_parsing.rb
78
+ - test/test_RepeaterDayName.rb
79
+ - test/test_RepeaterFortnight.rb
80
+ - test/test_RepeaterHour.rb
81
+ - test/test_RepeaterMonth.rb
82
+ - test/test_RepeaterMonthName.rb
83
+ - test/test_RepeaterTime.rb
84
+ - test/test_RepeaterWeek.rb
85
+ - test/test_RepeaterYear.rb
86
+ - test/test_Span.rb
87
+ - test/test_Token.rb
88
+ rdoc_options: []
89
+
90
+ extra_rdoc_files: []
91
+
80
92
  executables: []
81
93
 
82
94
  extensions: []
83
95
 
84
96
  requirements: []
85
97
 
86
- dependencies: []
87
-
98
+ dependencies:
99
+ - !ruby/object:Gem::Dependency
100
+ name: hoe
101
+ version_requirement:
102
+ version_requirements: !ruby/object:Gem::Version::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 1.1.6
107
+ version: