chronic 0.8.0 → 0.9.1
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.
- checksums.yaml +7 -0
- data/.travis.yml +8 -0
- data/HISTORY.md +17 -0
- data/README.md +38 -45
- data/lib/chronic/handler.rb +7 -7
- data/lib/chronic/handlers.rb +83 -27
- data/lib/chronic/numerizer.rb +11 -2
- data/lib/chronic/{chronic.rb → parser.rb} +55 -134
- data/lib/chronic/repeater.rb +11 -11
- data/lib/chronic/separator.rb +15 -1
- data/lib/chronic/span.rb +2 -2
- data/lib/chronic/token.rb +4 -0
- data/lib/chronic.rb +96 -69
- data/test/test_chronic.rb +9 -27
- data/test/test_handler.rb +17 -13
- data/test/test_numerizer.rb +15 -1
- data/test/test_parsing.rb +130 -18
- data/test/test_span.rb +1 -1
- metadata +13 -18
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 7033fa51e89c8552e935aab7ee0bb5673338d063
|
|
4
|
+
data.tar.gz: 21a09efad9e8da2dcd3ed32a5c8605707f36abc7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f3d63e888e712dad2b0d4c70d11688607e56a3d1b4ac11632e14b3f23a57f21a2fd32403f50cf13002a9859607a89e6521b5c39f4ec003edd5631791cf7cf409
|
|
7
|
+
data.tar.gz: 9e2d80dcf087643ce865ede613dbd2d006942b1986e46c02f75b012711ee79bb74f0b576ba812d8b54ab599411213548effc036cc888c97b82c70fdf21627365
|
data/.travis.yml
ADDED
data/HISTORY.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
# 0.9.1 / 2013-02-25
|
|
2
|
+
|
|
3
|
+
* Ensure Chronic strips periods from day portions (#173)
|
|
4
|
+
* Properly numerize "twelfth", "twentieth" etc. (#172, James McKinney)
|
|
5
|
+
* Ensure Chronic is compatible with Ruby 2.0.0 (#165, Ravil Bayramgalin)
|
|
6
|
+
|
|
7
|
+
# 0.9.0 / 2012-12-21
|
|
8
|
+
|
|
9
|
+
* Implement Chronic::Parser class and create an instance of this class
|
|
10
|
+
instead of leaving all data in the class level of Chronic
|
|
11
|
+
* Various bug fixes
|
|
12
|
+
* Add support for excel date formats (#149, @jmondo)
|
|
13
|
+
* Added support for time expressions such as '10 till' or 'half
|
|
14
|
+
past two' (#146, @chicagogrooves)
|
|
15
|
+
* Add support for RepeaterDayName, RepeaterMonthName,
|
|
16
|
+
Ordinal/ScalarDay and Time (#153, @kareemk)
|
|
17
|
+
|
|
1
18
|
# 0.8.0 / 2012-09-16
|
|
2
19
|
|
|
3
20
|
* Support parsing "<ordinal> of this month" (#109)
|
data/README.md
CHANGED
|
@@ -1,60 +1,47 @@
|
|
|
1
1
|
Chronic
|
|
2
2
|
=======
|
|
3
3
|
|
|
4
|
-
## DESCRIPTION
|
|
5
|
-
|
|
6
4
|
Chronic is a natural language date/time parser written in pure Ruby. See below
|
|
7
5
|
for the wide variety of formats Chronic will parse.
|
|
8
6
|
|
|
7
|
+
## Installation
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
$ [sudo] gem install chronic
|
|
15
|
-
|
|
16
|
-
### GitHub
|
|
9
|
+
```
|
|
10
|
+
$ gem install chronic
|
|
11
|
+
```
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
$ cd chronic && gem build chronic.gemspec
|
|
20
|
-
$ gem install chronic-<version>.gem
|
|
13
|
+
## Usage
|
|
21
14
|
|
|
15
|
+
```ruby
|
|
16
|
+
require 'chronic'
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
Time.now #=> Sun Aug 27 23:18:25 PDT 2006
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
Chronic.parse('tomorrow')
|
|
21
|
+
#=> Mon Aug 28 12:00:00 PDT 2006
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
Chronic.parse('monday', :context => :past)
|
|
24
|
+
#=> Mon Aug 21 12:00:00 PDT 2006
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
Chronic.parse('this tuesday 5:00')
|
|
27
|
+
#=> Tue Aug 29 17:00:00 PDT 2006
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
|
|
30
|
+
#=> Tue Aug 29 05:00:00 PDT 2006
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
|
|
33
|
+
#=> Sat May 27 12:00:00 PDT 2000
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
Chronic.parse('may 27th', :guess => false)
|
|
36
|
+
#=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
|
|
45
|
-
#=> Sat May 27 12:00:00 PDT 2000
|
|
46
|
-
|
|
47
|
-
Chronic.parse('may 27th', :guess => false)
|
|
48
|
-
#=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
|
|
49
|
-
|
|
50
|
-
Chronic.parse('6/4/2012', :endian_precedence => :little)
|
|
51
|
-
#=> Fri Apr 06 00:00:00 PDT 2012
|
|
52
|
-
|
|
38
|
+
Chronic.parse('6/4/2012', :endian_precedence => :little)
|
|
39
|
+
#=> Fri Apr 06 00:00:00 PDT 2012
|
|
40
|
+
```
|
|
53
41
|
|
|
54
42
|
See `Chronic.parse` for detailed usage instructions.
|
|
55
43
|
|
|
56
|
-
|
|
57
|
-
## EXAMPLES
|
|
44
|
+
## Examples
|
|
58
45
|
|
|
59
46
|
Chronic can parse a huge variety of date and time formats. Following is a
|
|
60
47
|
small sample of strings that will be properly parsed. Parsing is case
|
|
@@ -68,12 +55,17 @@ Simple
|
|
|
68
55
|
* friday 13:00
|
|
69
56
|
* mon 2:35
|
|
70
57
|
* 4pm
|
|
58
|
+
* 10 to 8
|
|
59
|
+
* 10 past 2
|
|
60
|
+
* half past 2
|
|
71
61
|
* 6 in the morning
|
|
72
62
|
* friday 1pm
|
|
73
63
|
* sat 7 in the evening
|
|
74
64
|
* yesterday
|
|
75
65
|
* today
|
|
76
66
|
* tomorrow
|
|
67
|
+
* last week
|
|
68
|
+
* next week
|
|
77
69
|
* this tuesday
|
|
78
70
|
* next month
|
|
79
71
|
* last winter
|
|
@@ -139,7 +131,7 @@ Specific Times (many of the above with an added time)
|
|
|
139
131
|
* etc
|
|
140
132
|
|
|
141
133
|
|
|
142
|
-
##
|
|
134
|
+
## Time Zones
|
|
143
135
|
|
|
144
136
|
Chronic allows you to set which Time class to use when constructing times. By
|
|
145
137
|
default, the built in Ruby time class creates times in your system's local
|
|
@@ -147,13 +139,14 @@ time zone. You can set this to something like ActiveSupport's
|
|
|
147
139
|
[TimeZone](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html)
|
|
148
140
|
class to get full time zone support.
|
|
149
141
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
142
|
+
```
|
|
143
|
+
>> Time.zone = "UTC"
|
|
144
|
+
>> Chronic.time_class = Time.zone
|
|
145
|
+
>> Chronic.parse("June 15 2006 at 5:45 AM")
|
|
146
|
+
=> Thu, 15 Jun 2006 05:45:00 UTC +00:00
|
|
147
|
+
```
|
|
155
148
|
|
|
156
|
-
##
|
|
149
|
+
## Limitations
|
|
157
150
|
|
|
158
151
|
Chronic uses Ruby's built in Time class for all time storage and computation.
|
|
159
152
|
Because of this, only times that the Time class can handle will be properly
|
|
@@ -161,7 +154,7 @@ parsed. Parsing for times outside of this range will simply return nil.
|
|
|
161
154
|
Support for a wider range of times is planned for a future release.
|
|
162
155
|
|
|
163
156
|
|
|
164
|
-
##
|
|
157
|
+
## Contribute
|
|
165
158
|
|
|
166
159
|
If you'd like to hack on Chronic, start by forking the repo on GitHub:
|
|
167
160
|
|
data/lib/chronic/handler.rb
CHANGED
|
@@ -6,8 +6,8 @@ module Chronic
|
|
|
6
6
|
attr_reader :handler_method
|
|
7
7
|
|
|
8
8
|
# pattern - An Array of patterns to match tokens against.
|
|
9
|
-
# handler_method - A
|
|
10
|
-
# when
|
|
9
|
+
# handler_method - A Symbol representing the method to be invoked
|
|
10
|
+
# when a pattern matches.
|
|
11
11
|
def initialize(pattern, handler_method)
|
|
12
12
|
@pattern = pattern
|
|
13
13
|
@handler_method = handler_method
|
|
@@ -43,14 +43,14 @@ module Chronic
|
|
|
43
43
|
if definitions.key?(name.to_sym)
|
|
44
44
|
sub_handlers = definitions[name.to_sym]
|
|
45
45
|
else
|
|
46
|
-
raise
|
|
46
|
+
raise "Invalid subset #{name} specified"
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
sub_handlers.each do |sub_handler|
|
|
50
50
|
return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
|
|
51
51
|
end
|
|
52
52
|
else
|
|
53
|
-
raise
|
|
53
|
+
raise "Invalid match type: #{element.class}"
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -58,13 +58,13 @@ module Chronic
|
|
|
58
58
|
return true
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def invoke(type, tokens, options)
|
|
61
|
+
def invoke(type, tokens, parser, options)
|
|
62
62
|
if Chronic.debug
|
|
63
63
|
puts "-#{type}"
|
|
64
64
|
puts "Handler: #{@handler_method}"
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
parser.send(@handler_method, tokens, options)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
# other - The other Handler object to compare.
|
|
@@ -85,4 +85,4 @@ module Chronic
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
end
|
|
88
|
-
end
|
|
88
|
+
end
|
data/lib/chronic/handlers.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Chronic
|
|
|
4
4
|
|
|
5
5
|
# Handle month/day
|
|
6
6
|
def handle_m_d(month, day, time_tokens, options)
|
|
7
|
-
month.start =
|
|
7
|
+
month.start = self.now
|
|
8
8
|
span = month.this(options[:context])
|
|
9
9
|
year, month = span.begin.year, span.begin.month
|
|
10
10
|
day_start = Chronic.time_class.local(year, month, day)
|
|
@@ -17,7 +17,7 @@ module Chronic
|
|
|
17
17
|
month = tokens[0].get_tag(RepeaterMonthName)
|
|
18
18
|
day = tokens[1].get_tag(ScalarDay).type
|
|
19
19
|
|
|
20
|
-
return if month_overflow?(
|
|
20
|
+
return if month_overflow?(self.now.year, month.index, day)
|
|
21
21
|
|
|
22
22
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
23
23
|
end
|
|
@@ -34,7 +34,7 @@ module Chronic
|
|
|
34
34
|
token_range = 0..0
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
return if month_overflow?(
|
|
37
|
+
return if month_overflow?(self.now.year, month.index, day)
|
|
38
38
|
|
|
39
39
|
handle_m_d(month, day, tokens[token_range], options)
|
|
40
40
|
end
|
|
@@ -44,7 +44,7 @@ module Chronic
|
|
|
44
44
|
month = tokens[0].get_tag(RepeaterMonthName)
|
|
45
45
|
day = tokens[1].get_tag(OrdinalDay).type
|
|
46
46
|
|
|
47
|
-
return if month_overflow?(
|
|
47
|
+
return if month_overflow?(self.now.year, month.index, day)
|
|
48
48
|
|
|
49
49
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
50
50
|
end
|
|
@@ -61,7 +61,7 @@ module Chronic
|
|
|
61
61
|
month = tokens[1].get_tag(RepeaterMonthName)
|
|
62
62
|
day = tokens[0].get_tag(OrdinalDay).type
|
|
63
63
|
|
|
64
|
-
return if month_overflow?(
|
|
64
|
+
return if month_overflow?(self.now.year, month.index, day)
|
|
65
65
|
|
|
66
66
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
67
67
|
end
|
|
@@ -87,7 +87,7 @@ module Chronic
|
|
|
87
87
|
month = tokens[1].get_tag(RepeaterMonthName)
|
|
88
88
|
day = tokens[0].get_tag(ScalarDay).type
|
|
89
89
|
|
|
90
|
-
return if month_overflow?(
|
|
90
|
+
return if month_overflow?(self.now.year, month.index, day)
|
|
91
91
|
|
|
92
92
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
93
93
|
end
|
|
@@ -104,7 +104,7 @@ module Chronic
|
|
|
104
104
|
token_range = 0..0
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
return if month_overflow?(
|
|
107
|
+
return if month_overflow?(self.now.year, month.index, day)
|
|
108
108
|
|
|
109
109
|
handle_m_d(month, day, tokens[token_range], options)
|
|
110
110
|
end
|
|
@@ -229,13 +229,14 @@ module Chronic
|
|
|
229
229
|
def handle_sm_sd(tokens, options)
|
|
230
230
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
231
231
|
day = tokens[1].get_tag(ScalarDay).type
|
|
232
|
-
year =
|
|
232
|
+
year = self.now.year
|
|
233
233
|
time_tokens = tokens.last(tokens.size - 2)
|
|
234
234
|
|
|
235
235
|
return if month_overflow?(year, month, day)
|
|
236
236
|
|
|
237
237
|
begin
|
|
238
238
|
day_start = Chronic.time_class.local(year, month, day)
|
|
239
|
+
day_start = Chronic.time_class.local(year + 1, month, day) if options[:context] == :future && day_start < now
|
|
239
240
|
day_or_time(day_start, time_tokens, options)
|
|
240
241
|
rescue ArgumentError
|
|
241
242
|
nil
|
|
@@ -249,11 +250,7 @@ module Chronic
|
|
|
249
250
|
handle_sm_sd(new_tokens + time_tokens, options)
|
|
250
251
|
end
|
|
251
252
|
|
|
252
|
-
|
|
253
|
-
def handle_sm_sy(tokens, options)
|
|
254
|
-
month = tokens[0].get_tag(ScalarMonth).type
|
|
255
|
-
year = tokens[1].get_tag(ScalarYear).type
|
|
256
|
-
|
|
253
|
+
def handle_year_and_month(year, month)
|
|
257
254
|
if month == 12
|
|
258
255
|
next_month_year = year + 1
|
|
259
256
|
next_month_month = 1
|
|
@@ -270,18 +267,64 @@ module Chronic
|
|
|
270
267
|
end
|
|
271
268
|
end
|
|
272
269
|
|
|
270
|
+
# Handle scalar-month/scalar-year
|
|
271
|
+
def handle_sm_sy(tokens, options)
|
|
272
|
+
month = tokens[0].get_tag(ScalarMonth).type
|
|
273
|
+
year = tokens[1].get_tag(ScalarYear).type
|
|
274
|
+
handle_year_and_month(year, month)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Handle scalar-year/scalar-month
|
|
278
|
+
def handle_sy_sm(tokens, options)
|
|
279
|
+
year = tokens[0].get_tag(ScalarYear).type
|
|
280
|
+
month = tokens[1].get_tag(ScalarMonth).type
|
|
281
|
+
handle_year_and_month(year, month)
|
|
282
|
+
end
|
|
283
|
+
|
|
273
284
|
# Handle RepeaterDayName RepeaterMonthName OrdinalDay
|
|
274
285
|
def handle_rdn_rmn_od(tokens, options)
|
|
275
286
|
month = tokens[1].get_tag(RepeaterMonthName)
|
|
276
287
|
day = tokens[2].get_tag(OrdinalDay).type
|
|
277
|
-
|
|
288
|
+
time_tokens = tokens.last(tokens.size - 3)
|
|
289
|
+
year = self.now.year
|
|
278
290
|
|
|
279
291
|
return if month_overflow?(year, month.index, day)
|
|
280
292
|
|
|
281
293
|
begin
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
294
|
+
if time_tokens.empty?
|
|
295
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
296
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
|
297
|
+
Span.new(start_time, end_time)
|
|
298
|
+
else
|
|
299
|
+
day_start = Chronic.time_class.local(year, month.index, day)
|
|
300
|
+
day_or_time(day_start, time_tokens, options)
|
|
301
|
+
end
|
|
302
|
+
rescue ArgumentError
|
|
303
|
+
nil
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Handle RepeaterDayName OrdinalDay
|
|
308
|
+
def handle_rdn_od(tokens, options)
|
|
309
|
+
day = tokens[1].get_tag(OrdinalDay).type
|
|
310
|
+
time_tokens = tokens.last(tokens.size - 2)
|
|
311
|
+
year = self.now.year
|
|
312
|
+
month = self.now.month
|
|
313
|
+
if options[:context] == :future
|
|
314
|
+
self.now.day > day ? month += 1 : month
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
return if month_overflow?(year, month, day)
|
|
318
|
+
|
|
319
|
+
begin
|
|
320
|
+
if time_tokens.empty?
|
|
321
|
+
start_time = Chronic.time_class.local(year, month, day)
|
|
322
|
+
end_time = time_with_rollover(year, month, day + 1)
|
|
323
|
+
Span.new(start_time, end_time)
|
|
324
|
+
else
|
|
325
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
326
|
+
day_or_time(day_start, time_tokens, options)
|
|
327
|
+
end
|
|
285
328
|
rescue ArgumentError
|
|
286
329
|
nil
|
|
287
330
|
end
|
|
@@ -291,14 +334,20 @@ module Chronic
|
|
|
291
334
|
def handle_rdn_rmn_sd(tokens, options)
|
|
292
335
|
month = tokens[1].get_tag(RepeaterMonthName)
|
|
293
336
|
day = tokens[2].get_tag(ScalarDay).type
|
|
294
|
-
|
|
337
|
+
time_tokens = tokens.last(tokens.size - 3)
|
|
338
|
+
year = self.now.year
|
|
295
339
|
|
|
296
340
|
return if month_overflow?(year, month.index, day)
|
|
297
341
|
|
|
298
342
|
begin
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
343
|
+
if time_tokens.empty?
|
|
344
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
345
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
|
346
|
+
Span.new(start_time, end_time)
|
|
347
|
+
else
|
|
348
|
+
day_start = Chronic.time_class.local(year, month.index, day)
|
|
349
|
+
day_or_time(day_start, time_tokens, options)
|
|
350
|
+
end
|
|
302
351
|
rescue ArgumentError
|
|
303
352
|
nil
|
|
304
353
|
end
|
|
@@ -332,7 +381,8 @@ module Chronic
|
|
|
332
381
|
end_time = Chronic.time_class.local(year, month, day + 1, h, m, s)
|
|
333
382
|
else
|
|
334
383
|
time = Chronic.time_class.local(year, month, day)
|
|
335
|
-
|
|
384
|
+
day += 1 unless day >= 31
|
|
385
|
+
end_time = Chronic.time_class.local(year, month, day)
|
|
336
386
|
end
|
|
337
387
|
Span.new(time, end_time)
|
|
338
388
|
end
|
|
@@ -364,8 +414,7 @@ module Chronic
|
|
|
364
414
|
|
|
365
415
|
# Handle scalar/repeater/pointer
|
|
366
416
|
def handle_s_r_p(tokens, options)
|
|
367
|
-
|
|
368
|
-
span = Span.new(Chronic.now, Chronic.now + 1)
|
|
417
|
+
span = Span.new(self.now, self.now + 1)
|
|
369
418
|
|
|
370
419
|
handle_srp(tokens, span, options)
|
|
371
420
|
end
|
|
@@ -382,6 +431,13 @@ module Chronic
|
|
|
382
431
|
handle_srp(tokens, anchor_span, options)
|
|
383
432
|
end
|
|
384
433
|
|
|
434
|
+
def handle_s_r_a_s_r_p_a(tokens, options)
|
|
435
|
+
anchor_span = get_anchor(tokens[4..tokens.size - 1], options)
|
|
436
|
+
|
|
437
|
+
span = handle_srp(tokens[0..1]+tokens[4..6], anchor_span, options)
|
|
438
|
+
handle_srp(tokens[2..3]+tokens[4..6], span, options)
|
|
439
|
+
end
|
|
440
|
+
|
|
385
441
|
# narrows
|
|
386
442
|
|
|
387
443
|
# Handle oridinal repeaters
|
|
@@ -421,7 +477,7 @@ module Chronic
|
|
|
421
477
|
outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
|
|
422
478
|
|
|
423
479
|
if !time_tokens.empty?
|
|
424
|
-
|
|
480
|
+
self.now = outer_span.begin
|
|
425
481
|
get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
|
|
426
482
|
else
|
|
427
483
|
outer_span
|
|
@@ -439,7 +495,7 @@ module Chronic
|
|
|
439
495
|
end
|
|
440
496
|
|
|
441
497
|
head = repeaters.shift
|
|
442
|
-
head.start =
|
|
498
|
+
head.start = self.now
|
|
443
499
|
|
|
444
500
|
case grabber.type
|
|
445
501
|
when :last
|
|
@@ -453,7 +509,7 @@ module Chronic
|
|
|
453
509
|
when :next
|
|
454
510
|
outer_span = head.next(:future)
|
|
455
511
|
else
|
|
456
|
-
raise
|
|
512
|
+
raise "Invalid grabber"
|
|
457
513
|
end
|
|
458
514
|
|
|
459
515
|
if Chronic.debug
|
data/lib/chronic/numerizer.rb
CHANGED
|
@@ -37,7 +37,16 @@ module Chronic
|
|
|
37
37
|
['seventh', '7'],
|
|
38
38
|
['eighth', '8'],
|
|
39
39
|
['ninth', '9'],
|
|
40
|
-
['tenth', '10']
|
|
40
|
+
['tenth', '10'],
|
|
41
|
+
['twelfth', '12'],
|
|
42
|
+
['twentieth', '20'],
|
|
43
|
+
['thirtieth', '30'],
|
|
44
|
+
['fourtieth', '40'],
|
|
45
|
+
['fiftieth', '50'],
|
|
46
|
+
['sixtieth', '60'],
|
|
47
|
+
['seventieth', '70'],
|
|
48
|
+
['eightieth', '80'],
|
|
49
|
+
['ninetieth', '90']
|
|
41
50
|
]
|
|
42
51
|
|
|
43
52
|
TEN_PREFIXES = [
|
|
@@ -90,7 +99,7 @@ module Chronic
|
|
|
90
99
|
# hundreds, thousands, millions, etc.
|
|
91
100
|
|
|
92
101
|
BIG_PREFIXES.each do |bp|
|
|
93
|
-
string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
|
|
102
|
+
string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { $1.empty? ? bp[1] : '<num>' + (bp[1] * $1.to_i).to_s}
|
|
94
103
|
andition(string)
|
|
95
104
|
end
|
|
96
105
|
|