chronic 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,38 @@
1
+ = 0.2.0 2007-03-20
2
+
3
+ * fixed time overflow issue
4
+ * implemented numerizer, allowing the use of number words (e.g. five weeks ago) (shalev)
5
+
6
+ = 0.1.6 2006-01-15
7
+
8
+ * added 'weekend' support (eventualbuddha)
9
+
10
+ = 0.1.5 2006-12-20
11
+
12
+ * fixed 'aug 20' returning next year if current month is august
13
+ * modified behavior of 'from now'
14
+ * added support for seconds on times, and thus db timestamp format: "2006-12-20 18:04:23"
15
+ * made Hoe compliant
16
+
17
+ = 0.1.4
18
+
19
+ * removed verbose error checking code. oops. :-/
20
+
21
+ = 0.1.3
22
+
23
+ * improved regexes for word variations (Josh Goebel)
24
+ * fixed a bug that caused "today at 3am" to return nil if current time is after 3am
25
+
26
+ = 0.1.2
27
+
28
+ * removed Date dependency (now works on windows properly without fiddling)
29
+
30
+ = 0.1.1
31
+
32
+ * run to_s on incoming object
33
+ * fixed loop loading of repeaters files (out of order on some machines)
34
+ * fixed find_within to use this instead of next (was breaking "today at 6pm")
35
+
36
+ = 0.1.0
37
+
38
+ * initial release
data/Manifest.txt ADDED
@@ -0,0 +1,45 @@
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/numerizer/numerizer.rb
30
+ test/suite.rb
31
+ test/test_Chronic.rb
32
+ test/test_Handler.rb
33
+ test/test_Numerizer.rb
34
+ test/test_RepeaterDayName.rb
35
+ test/test_RepeaterFortnight.rb
36
+ test/test_RepeaterHour.rb
37
+ test/test_RepeaterMonth.rb
38
+ test/test_RepeaterMonthName.rb
39
+ test/test_RepeaterTime.rb
40
+ test/test_RepeaterWeek.rb
41
+ test/test_RepeaterWeekend.rb
42
+ test/test_RepeaterYear.rb
43
+ test/test_Span.rb
44
+ test/test_Token.rb
45
+ test/test_parsing.rb
@@ -1,14 +1,18 @@
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
 
@@ -39,7 +43,7 @@ You can parse strings containing a natural language date using the Chronic.parse
39
43
 
40
44
  See Chronic.parse for detailed usage instructions.
41
45
 
42
- ==Examples
46
+ == EXAMPLES:
43
47
 
44
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.
45
49
 
@@ -110,11 +114,36 @@ Specific Dates
110
114
  Specific Times (many of the above with an added time)
111
115
 
112
116
  January 5 at 7pm
113
- 1979-05-27 05:00
117
+ 1979-05-27 05:00:00
114
118
  etc
115
119
 
116
- ==Limitations
120
+ == LIMITATIONS:
117
121
 
118
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.
119
123
 
120
- 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,41 @@
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
+ p.need_tar = false
14
+ p.extra_deps = []
15
+ end
16
+
17
+ # vim: syntax=Ruby
18
+
19
+ __END__
20
+
21
+ require 'rubygems'
22
+
23
+ SPEC = Gem::Specification.new do |s|
24
+ s.name = 'chronic'
25
+ s.version = '0.1.5'
26
+ s.author = 'Tom Preston-Werner'
27
+ s.email = 'tom@rubyisawesome.com'
28
+ s.homepage = 'http://chronic.rubyforge.org'
29
+ s.platform = Gem::Platform::RUBY
30
+ s.summary = "A natural language date parser"
31
+ candidates = Dir["{lib,test}/**/*"]
32
+ s.files = candidates.delete_if do |item|
33
+ item.include?('.svn')
34
+ end
35
+ s.require_path = "lib"
36
+ s.autorequire = "chronic"
37
+ s.test_file = "test/suite.rb"
38
+ s.has_rdoc = true
39
+ s.extra_rdoc_files = ['README']
40
+ s.rdoc_options << '--main' << 'README'
41
+ end
@@ -101,6 +101,7 @@ module Chronic
101
101
  # ordinals (third => 3rd)
102
102
  def pre_normalize(text) #:nodoc:
103
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)
@@ -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,18 +213,20 @@ module Chronic
213
213
 
214
214
  def handle_s_r_p(tokens, options) #:nodoc:
215
215
  repeater = tokens[1].get_tag(Repeater)
216
+
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
216
228
 
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
229
+ span = self.parse("this second", :guess => false, :now => @now)
228
230
 
229
231
  self.handle_srp(tokens, span, options)
230
232
  end
@@ -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
@@ -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
@@ -72,7 +72,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
72
72
  /^months?$/ => :month,
73
73
  /^fortnights?$/ => :fortnight,
74
74
  /^weeks?$/ => :week,
75
- /^weekends?$/ => :weekends,
75
+ /^weekends?$/ => :weekend,
76
76
  /^days?$/ => :day,
77
77
  /^hours?$/ => :hour,
78
78
  /^minutes?$/ => :minute,
@@ -99,12 +99,12 @@ 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
- !@now.nil? || raise("Start point must be set before calling #next")
107
+ !@now.nil? || raise("Start point must be set before calling #this")
108
108
  [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
109
109
  end
110
110
 
@@ -24,8 +24,8 @@ class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
24
24
 
25
25
  case pointer
26
26
  when :future
27
- hour_start = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
28
- hour_end = Time.local(@now.year, @now.month, @now.day, @now.hour + 1)
27
+ hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
28
+ hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
29
29
  when :past
30
30
  hour_start = Time.local(@now.year, @now.month, @now.day, @now.hour)
31
31
  hour_end = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min)
@@ -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,9 +53,12 @@ class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
47
53
  def this(pointer = :future)
48
54
  super
49
55
 
50
- pointer = :future if pointer == :none
51
-
52
- self.next(pointer)
56
+ case pointer
57
+ when :past
58
+ self.next(pointer)
59
+ when :future, :none
60
+ self.next(:none)
61
+ end
53
62
  end
54
63
 
55
64
  def width
@@ -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
 
@@ -1,6 +1,55 @@
1
1
  class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
2
2
  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
3
3
 
4
+ def next(pointer)
5
+ super
6
+
7
+ if !@current_week_start
8
+ case pointer
9
+ when :future
10
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
11
+ saturday_repeater.start = @now
12
+ next_saturday_span = saturday_repeater.next(:future)
13
+ @current_week_start = next_saturday_span.begin
14
+ when :past
15
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
16
+ saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
17
+ last_saturday_span = saturday_repeater.next(:past)
18
+ @current_week_start = last_saturday_span.begin
19
+ end
20
+ else
21
+ direction = pointer == :future ? 1 : -1
22
+ @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
23
+ end
24
+
25
+ Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
26
+ end
27
+
28
+ def this(pointer = :future)
29
+ super
30
+
31
+ case pointer
32
+ when :future, :none
33
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
34
+ saturday_repeater.start = @now
35
+ this_saturday_span = saturday_repeater.this(:future)
36
+ Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
37
+ when :past
38
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
39
+ saturday_repeater.start = @now
40
+ last_saturday_span = saturday_repeater.this(:past)
41
+ Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
42
+ end
43
+ end
44
+
45
+ def offset(span, amount, pointer)
46
+ direction = pointer == :future ? 1 : -1
47
+ weekend = Chronic::RepeaterWeekend.new(:weekend)
48
+ weekend.start = span.begin
49
+ start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
50
+ Chronic::Span.new(start, start + (span.end - span.begin))
51
+ end
52
+
4
53
  def width
5
54
  WEEKEND_SECONDS
6
55
  end
data/lib/chronic.rb CHANGED
@@ -7,6 +7,8 @@
7
7
  #
8
8
  #=============================================================================
9
9
 
10
+ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
11
+
10
12
  require 'chronic/chronic'
11
13
  require 'chronic/handlers'
12
14
 
@@ -33,7 +35,38 @@ require 'chronic/scalar'
33
35
  require 'chronic/ordinal'
34
36
  require 'chronic/separator'
35
37
 
38
+ require 'numerizer/numerizer'
39
+
36
40
  module Chronic
41
+ VERSION = "0.2.0"
42
+
37
43
  def self.debug; false; end
38
44
  end
39
45
 
46
+ alias p_orig p
47
+
48
+ def p(val)
49
+ p_orig val
50
+ puts
51
+ end
52
+
53
+ class Time
54
+ def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
55
+ if second >= 60
56
+ minute += second / 60
57
+ second = second % 60
58
+ end
59
+
60
+ if minute >= 60
61
+ hour += minute / 60
62
+ minute = minute % 60
63
+ end
64
+
65
+ if hour >= 24
66
+ day += hour / 24
67
+ hour = hour % 24
68
+ end
69
+
70
+ Time.local(year, month, day, hour, minute, second)
71
+ end
72
+ end
@@ -0,0 +1,103 @@
1
+ require 'strscan'
2
+
3
+ class Numerizer
4
+
5
+ DIRECT_NUMS = [
6
+ ['eleven', '11'],
7
+ ['twelve', '12'],
8
+ ['thirteen', '13'],
9
+ ['fourteen', '14'],
10
+ ['fifteen', '15'],
11
+ ['sixteen', '16'],
12
+ ['seventeen', '17'],
13
+ ['eighteen', '18'],
14
+ ['nineteen', '19'],
15
+ ['ninteen', '19'], # Common mis-spelling
16
+ ['zero', '0'],
17
+ ['one', '1'],
18
+ ['two', '2'],
19
+ ['three', '3'],
20
+ ['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty
21
+ ['five', '5'],
22
+ ['six(\W|$)', '6\1'],
23
+ ['seven(\W|$)', '7\1'],
24
+ ['eight(\W|$)', '8\1'],
25
+ ['nine(\W|$)', '9\1'],
26
+ ['ten', '10'],
27
+ ['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1
28
+ ]
29
+
30
+ TEN_PREFIXES = [ ['twenty', 20],
31
+ ['thirty', 30],
32
+ ['fourty', 40],
33
+ ['fifty', 50],
34
+ ['sixty', 60],
35
+ ['seventy', 70],
36
+ ['eighty', 80],
37
+ ['ninety', 90]
38
+ ]
39
+
40
+ BIG_PREFIXES = [ ['hundred', 100],
41
+ ['thousand', 1000],
42
+ ['million', 1_000_000],
43
+ ['billion', 1_000_000_000],
44
+ ['trillion', 1_000_000_000_000],
45
+ ]
46
+
47
+ class << self
48
+ def numerize(string)
49
+ string = string.dup
50
+
51
+ # preprocess
52
+ string.gsub!(/ +|([^\d])-([^d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
53
+ string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
54
+
55
+ # easy/direct replacements
56
+
57
+ DIRECT_NUMS.each do |dn|
58
+ string.gsub!(/#{dn[0]}/i, dn[1])
59
+ end
60
+
61
+ # ten, twenty, etc.
62
+
63
+ TEN_PREFIXES.each do |tp|
64
+ string.gsub!(/(?:#{tp[0]})( *\d(?=[^\d]|$))*/i) { (tp[1] + $1.to_i).to_s }
65
+ end
66
+
67
+ # hundreds, thousands, millions, etc.
68
+
69
+ BIG_PREFIXES.each do |bp|
70
+ string.gsub!(/(\d*) *#{bp[0]}/i) { (bp[1] * $1.to_i).to_s}
71
+ andition(string)
72
+ #combine_numbers(string) # Should to be more efficient way to do this
73
+ end
74
+
75
+ # fractional addition
76
+ # I'm not combining this with the previous block as using float addition complicates the strings
77
+ # (with extraneous .0's and such )
78
+ string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
79
+
80
+ string
81
+ end
82
+
83
+ private
84
+ def andition(string)
85
+ sc = StringScanner.new(string)
86
+ while(sc.scan_until(/(\d+)( | and )(\d+)(?=[^\w]|$)/i))
87
+ if sc[2] =~ /and/ || sc[1].size > sc[3].size
88
+ string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[3].to_i).to_s
89
+ sc.reset
90
+ end
91
+ end
92
+ end
93
+
94
+ # def combine_numbers(string)
95
+ # sc = StringScanner.new(string)
96
+ # while(sc.scan_until(/(\d+)(?: | and |-)(\d+)(?=[^\w]|$)/i))
97
+ # string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[2].to_i).to_s
98
+ # sc.reset
99
+ # end
100
+ # end
101
+
102
+ end
103
+ end
@@ -1,11 +1,10 @@
1
- __END__
2
-
3
1
  require 'test/unit'
2
+ require 'chronic'
4
3
 
5
4
  class ParseNumbersTest < Test::Unit::TestCase
6
5
 
7
- def test_parse
8
- strings = {1 => 'one',
6
+ def test_straight_parsing
7
+ strings = { 1 => 'one',
9
8
  5 => 'five',
10
9
  10 => 'ten',
11
10
  11 => 'eleven',
@@ -24,7 +23,7 @@ class ParseNumbersTest < Test::Unit::TestCase
24
23
  100 => 'a hundred',
25
24
  100 => 'one hundred',
26
25
  150 => 'one hundred and fifty',
27
- 150 => 'one fifty',
26
+ # 150 => 'one fifty',
28
27
  200 => 'two-hundred',
29
28
  500 => '5 hundred',
30
29
  999 => 'nine hundred and ninety nine',
@@ -40,11 +39,10 @@ class ParseNumbersTest < Test::Unit::TestCase
40
39
  1_000_000 => 'one million',
41
40
  1_250_007 => 'one million two hundred fifty thousand and seven',
42
41
  1_000_000_000 => 'one billion',
43
- 1_000_000_001 => 'one billion and one'}
42
+ 1_000_000_001 => 'one billion and one' }
44
43
 
45
44
  strings.keys.sort.each do |key|
46
- assert_equal key, JW::NumberMagick.convert(strings[key])
45
+ assert_equal key, Numerizer.numerize(strings[key]).to_i
47
46
  end
48
47
  end
49
-
50
48
  end