chronic 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md CHANGED
@@ -1,3 +1,14 @@
1
+ # 0.9.0 / 2012-12-21
2
+
3
+ * Implement Chronic::Parser class and create an instance of this class
4
+ instead of leaving all data in the class level of Chronic
5
+ * Various bug fixes
6
+ * Add support for excel date formats (#149, @jmondo)
7
+ * Added support for time expressions such as '10 till' or 'half
8
+ past two' (#146, @chicagogrooves)
9
+ * Add support for RepeaterDayName, RepeaterMonthName,
10
+ Ordinal/ScalarDay and Time (#153, @kareemk)
11
+
1
12
  # 0.8.0 / 2012-09-16
2
13
 
3
14
  * 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
- ## INSTALLATION
11
-
12
- ### RubyGems
13
-
14
- $ [sudo] gem install chronic
15
-
16
- ### GitHub
9
+ ```
10
+ $ gem install chronic
11
+ ```
17
12
 
18
- $ git clone git://github.com/mojombo/chronic.git
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
- ## USAGE
18
+ Time.now #=> Sun Aug 27 23:18:25 PDT 2006
24
19
 
25
- You can parse strings containing a natural language date using the
26
- `Chronic.parse` method.
20
+ Chronic.parse('tomorrow')
21
+ #=> Mon Aug 28 12:00:00 PDT 2006
27
22
 
28
- require 'chronic'
23
+ Chronic.parse('monday', :context => :past)
24
+ #=> Mon Aug 21 12:00:00 PDT 2006
29
25
 
30
- Time.now #=> Sun Aug 27 23:18:25 PDT 2006
26
+ Chronic.parse('this tuesday 5:00')
27
+ #=> Tue Aug 29 17:00:00 PDT 2006
31
28
 
32
- Chronic.parse('tomorrow')
33
- #=> Mon Aug 28 12:00:00 PDT 2006
29
+ Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
30
+ #=> Tue Aug 29 05:00:00 PDT 2006
34
31
 
35
- Chronic.parse('monday', :context => :past)
36
- #=> Mon Aug 21 12:00:00 PDT 2006
32
+ Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
33
+ #=> Sat May 27 12:00:00 PDT 2000
37
34
 
38
- Chronic.parse('this tuesday 5:00')
39
- #=> Tue Aug 29 17:00:00 PDT 2006
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
- Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
42
- #=> Tue Aug 29 05:00:00 PDT 2006
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
- ## TIME ZONES
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
- >> Time.zone = "UTC"
151
- >> Chronic.time_class = Time.zone
152
- >> Chronic.parse("June 15 2006 at 5:45 AM")
153
- => Thu, 15 Jun 2006 05:45:00 UTC +00:00
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
- ## LIMITATIONS
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
- ## CONTRIBUTE
157
+ ## Contribute
165
158
 
166
159
  If you'd like to hack on Chronic, start by forking the repo on GitHub:
167
160
 
@@ -1,6 +1,42 @@
1
1
  require 'time'
2
2
  require 'date'
3
3
 
4
+ require 'chronic/parser'
5
+
6
+ require 'chronic/handler'
7
+ require 'chronic/handlers'
8
+ require 'chronic/mini_date'
9
+ require 'chronic/tag'
10
+ require 'chronic/span'
11
+ require 'chronic/token'
12
+ require 'chronic/grabber'
13
+ require 'chronic/pointer'
14
+ require 'chronic/scalar'
15
+ require 'chronic/ordinal'
16
+ require 'chronic/ordinal'
17
+ require 'chronic/separator'
18
+ require 'chronic/time_zone'
19
+ require 'chronic/numerizer'
20
+ require 'chronic/season'
21
+
22
+ require 'chronic/repeater'
23
+ require 'chronic/repeaters/repeater_year'
24
+ require 'chronic/repeaters/repeater_season'
25
+ require 'chronic/repeaters/repeater_season_name'
26
+ require 'chronic/repeaters/repeater_month'
27
+ require 'chronic/repeaters/repeater_month_name'
28
+ require 'chronic/repeaters/repeater_fortnight'
29
+ require 'chronic/repeaters/repeater_week'
30
+ require 'chronic/repeaters/repeater_weekend'
31
+ require 'chronic/repeaters/repeater_weekday'
32
+ require 'chronic/repeaters/repeater_day'
33
+ require 'chronic/repeaters/repeater_day_name'
34
+ require 'chronic/repeaters/repeater_day_portion'
35
+ require 'chronic/repeaters/repeater_hour'
36
+ require 'chronic/repeaters/repeater_minute'
37
+ require 'chronic/repeaters/repeater_second'
38
+ require 'chronic/repeaters/repeater_time'
39
+
4
40
  # Parse natural language dates and times into Time or Chronic::Span objects.
5
41
  #
6
42
  # Examples:
@@ -14,20 +50,8 @@ require 'date'
14
50
  #
15
51
  # Chronic.parse('monday', :context => :past)
16
52
  # #=> Mon Aug 21 12:00:00 PDT 2006
17
- #
18
- # Chronic.parse('this tuesday 5:00')
19
- # #=> Tue Aug 29 17:00:00 PDT 2006
20
- #
21
- # Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
22
- # #=> Tue Aug 29 05:00:00 PDT 2006
23
- #
24
- # Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
25
- # #=> Sat May 27 12:00:00 PDT 2000
26
- #
27
- # Chronic.parse('may 27th', :guess => false)
28
- # #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
29
53
  module Chronic
30
- VERSION = "0.8.0"
54
+ VERSION = "0.9.0"
31
55
 
32
56
  class << self
33
57
 
@@ -46,72 +70,76 @@ module Chronic
46
70
  #
47
71
  # Returns The Time class Chronic uses internally.
48
72
  attr_accessor :time_class
49
-
50
- # The current Time Chronic is using to base from.
51
- #
52
- # Examples:
53
- #
54
- # Time.now #=> 2011-06-06 14:13:43 +0100
55
- # Chronic.parse('yesterday') #=> 2011-06-05 12:00:00 +0100
56
- #
57
- # now = Time.local(2025, 12, 24)
58
- # Chronic.parse('tomorrow', :now => now) #=> 2025-12-25 12:00:00 +0000
59
- #
60
- # Returns a Time object.
61
- attr_accessor :now
62
73
  end
63
74
 
64
75
  self.debug = false
65
76
  self.time_class = Time
66
77
 
67
- autoload :Handler, 'chronic/handler'
68
- autoload :Handlers, 'chronic/handlers'
69
- autoload :MiniDate, 'chronic/mini_date'
70
- autoload :Tag, 'chronic/tag'
71
- autoload :Span, 'chronic/span'
72
- autoload :Token, 'chronic/token'
73
- autoload :Grabber, 'chronic/grabber'
74
- autoload :Pointer, 'chronic/pointer'
75
- autoload :Scalar, 'chronic/scalar'
76
- autoload :Ordinal, 'chronic/ordinal'
77
- autoload :OrdinalDay, 'chronic/ordinal'
78
- autoload :Separator, 'chronic/separator'
79
- autoload :TimeZone, 'chronic/time_zone'
80
- autoload :Numerizer, 'chronic/numerizer'
81
- autoload :Season, 'chronic/season'
82
-
83
- autoload :Repeater, 'chronic/repeater'
84
- autoload :RepeaterYear, 'chronic/repeaters/repeater_year'
85
- autoload :RepeaterSeason, 'chronic/repeaters/repeater_season'
86
- autoload :RepeaterSeasonName, 'chronic/repeaters/repeater_season_name'
87
- autoload :RepeaterMonth, 'chronic/repeaters/repeater_month'
88
- autoload :RepeaterMonthName, 'chronic/repeaters/repeater_month_name'
89
- autoload :RepeaterFortnight, 'chronic/repeaters/repeater_fortnight'
90
- autoload :RepeaterWeek, 'chronic/repeaters/repeater_week'
91
- autoload :RepeaterWeekend, 'chronic/repeaters/repeater_weekend'
92
- autoload :RepeaterWeekday, 'chronic/repeaters/repeater_weekday'
93
- autoload :RepeaterDay, 'chronic/repeaters/repeater_day'
94
- autoload :RepeaterDayName, 'chronic/repeaters/repeater_day_name'
95
- autoload :RepeaterDayPortion, 'chronic/repeaters/repeater_day_portion'
96
- autoload :RepeaterHour, 'chronic/repeaters/repeater_hour'
97
- autoload :RepeaterMinute, 'chronic/repeaters/repeater_minute'
98
- autoload :RepeaterSecond, 'chronic/repeaters/repeater_second'
99
- autoload :RepeaterTime, 'chronic/repeaters/repeater_time'
100
78
 
101
- end
79
+ # Parses a string containing a natural language date or time.
80
+ #
81
+ # If the parser can find a date or time, either a Time or Chronic::Span
82
+ # will be returned (depending on the value of `:guess`). If no
83
+ # date or time can be found, `nil` will be returned.
84
+ #
85
+ # text - The String text to parse.
86
+ # opts - An optional Hash of configuration options passed to Parser::new.
87
+ def self.parse(text, options = {})
88
+ Parser.new(options).parse(text)
89
+ end
102
90
 
103
- require 'chronic/chronic'
91
+ # Construct a new time object determining possible month overflows
92
+ # and leap years.
93
+ #
94
+ # year - Integer year.
95
+ # month - Integer month.
96
+ # day - Integer day.
97
+ # hour - Integer hour.
98
+ # minute - Integer minute.
99
+ # second - Integer second.
100
+ #
101
+ # Returns a new Time object constructed from these params.
102
+ def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
103
+ if second >= 60
104
+ minute += second / 60
105
+ second = second % 60
106
+ end
104
107
 
105
- class Time
108
+ if minute >= 60
109
+ hour += minute / 60
110
+ minute = minute % 60
111
+ end
106
112
 
107
- def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
108
- warn "Time.construct will be deprecated in version 0.7.0. Please use Chronic.construct instead"
109
- Chronic.construct(year, month, day, hour, minute, second)
110
- end
113
+ if hour >= 24
114
+ day += hour / 24
115
+ hour = hour % 24
116
+ end
117
+
118
+ # determine if there is a day overflow. this is complicated by our crappy calendar
119
+ # system (non-constant number of days per month)
120
+ day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
121
+ if day > 28
122
+ # no month ever has fewer than 28 days, so only do this if necessary
123
+ leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
124
+ common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
125
+ days_this_month = Date.leap?(year) ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
126
+ if day > days_this_month
127
+ month += day / days_this_month
128
+ day = day % days_this_month
129
+ end
130
+ end
131
+
132
+ if month > 12
133
+ if month % 12 == 0
134
+ year += (month - 12) / 12
135
+ month = 12
136
+ else
137
+ year += month / 12
138
+ month = month % 12
139
+ end
140
+ end
111
141
 
112
- def to_minidate
113
- warn "Time.to_minidate will be deprecated in version 0.7.0. Please use Chronic::MiniDate.from_time(time) instead"
114
- Chronic::MiniDate.from_time(self)
142
+ Chronic.time_class.local(year, month, day, hour, minute, second)
115
143
  end
116
144
 
117
145
  end
@@ -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 Symbole representing the method to be invoked
10
- # when patterns are matched.
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 ChronicPain, "Invalid subset #{name} specified"
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 ChronicPain, "Invalid match type: #{element.class}"
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
- Handlers.send(@handler_method, tokens, options)
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
@@ -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 = Chronic.now
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?(Chronic.now.year, month.index, day)
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?(Chronic.now.year, month.index, day)
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?(Chronic.now.year, month.index, day)
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?(Chronic.now.year, month.index, day)
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?(Chronic.now.year, month.index, day)
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?(Chronic.now.year, month.index, day)
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 = Chronic.now.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
@@ -274,14 +275,46 @@ module Chronic
274
275
  def handle_rdn_rmn_od(tokens, options)
275
276
  month = tokens[1].get_tag(RepeaterMonthName)
276
277
  day = tokens[2].get_tag(OrdinalDay).type
277
- year = Chronic.now.year
278
+ time_tokens = tokens.last(tokens.size - 3)
279
+ year = self.now.year
278
280
 
279
281
  return if month_overflow?(year, month.index, day)
280
282
 
281
283
  begin
282
- start_time = Chronic.time_class.local(year, month.index, day)
283
- end_time = time_with_rollover(year, month.index, day + 1)
284
- Span.new(start_time, end_time)
284
+ if time_tokens.empty?
285
+ start_time = Chronic.time_class.local(year, month.index, day)
286
+ end_time = time_with_rollover(year, month.index, day + 1)
287
+ Span.new(start_time, end_time)
288
+ else
289
+ day_start = Chronic.time_class.local(year, month.index, day)
290
+ day_or_time(day_start, time_tokens, options)
291
+ end
292
+ rescue ArgumentError
293
+ nil
294
+ end
295
+ end
296
+
297
+ # Handle RepeaterDayName OrdinalDay
298
+ def handle_rdn_od(tokens, options)
299
+ day = tokens[1].get_tag(OrdinalDay).type
300
+ time_tokens = tokens.last(tokens.size - 2)
301
+ year = self.now.year
302
+ month = self.now.month
303
+ if options[:context] == :future
304
+ self.now.day > day ? month += 1 : month
305
+ end
306
+
307
+ return if month_overflow?(year, month, day)
308
+
309
+ begin
310
+ if time_tokens.empty?
311
+ start_time = Chronic.time_class.local(year, month, day)
312
+ end_time = time_with_rollover(year, month, day + 1)
313
+ Span.new(start_time, end_time)
314
+ else
315
+ day_start = Chronic.time_class.local(year, month, day)
316
+ day_or_time(day_start, time_tokens, options)
317
+ end
285
318
  rescue ArgumentError
286
319
  nil
287
320
  end
@@ -291,14 +324,20 @@ module Chronic
291
324
  def handle_rdn_rmn_sd(tokens, options)
292
325
  month = tokens[1].get_tag(RepeaterMonthName)
293
326
  day = tokens[2].get_tag(ScalarDay).type
294
- year = Chronic.now.year
327
+ time_tokens = tokens.last(tokens.size - 3)
328
+ year = self.now.year
295
329
 
296
330
  return if month_overflow?(year, month.index, day)
297
331
 
298
332
  begin
299
- start_time = Chronic.time_class.local(year, month.index, day)
300
- end_time = time_with_rollover(year, month.index, day + 1)
301
- Span.new(start_time, end_time)
333
+ if time_tokens.empty?
334
+ start_time = Chronic.time_class.local(year, month.index, day)
335
+ end_time = time_with_rollover(year, month.index, day + 1)
336
+ Span.new(start_time, end_time)
337
+ else
338
+ day_start = Chronic.time_class.local(year, month.index, day)
339
+ day_or_time(day_start, time_tokens, options)
340
+ end
302
341
  rescue ArgumentError
303
342
  nil
304
343
  end
@@ -332,7 +371,8 @@ module Chronic
332
371
  end_time = Chronic.time_class.local(year, month, day + 1, h, m, s)
333
372
  else
334
373
  time = Chronic.time_class.local(year, month, day)
335
- end_time = Chronic.time_class.local(year, month, day + 1)
374
+ day += 1 unless day >= 31
375
+ end_time = Chronic.time_class.local(year, month, day)
336
376
  end
337
377
  Span.new(time, end_time)
338
378
  end
@@ -365,7 +405,7 @@ module Chronic
365
405
  # Handle scalar/repeater/pointer
366
406
  def handle_s_r_p(tokens, options)
367
407
  repeater = tokens[1].get_tag(Repeater)
368
- span = Span.new(Chronic.now, Chronic.now + 1)
408
+ span = Span.new(self.now, self.now + 1)
369
409
 
370
410
  handle_srp(tokens, span, options)
371
411
  end
@@ -382,6 +422,13 @@ module Chronic
382
422
  handle_srp(tokens, anchor_span, options)
383
423
  end
384
424
 
425
+ def handle_s_r_a_s_r_p_a(tokens, options)
426
+ anchor_span = get_anchor(tokens[4..tokens.size - 1], options)
427
+
428
+ span = handle_srp(tokens[0..1]+tokens[4..6], anchor_span, options)
429
+ handle_srp(tokens[2..3]+tokens[4..6], span, options)
430
+ end
431
+
385
432
  # narrows
386
433
 
387
434
  # Handle oridinal repeaters
@@ -421,7 +468,7 @@ module Chronic
421
468
  outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
422
469
 
423
470
  if !time_tokens.empty?
424
- Chronic.now = outer_span.begin
471
+ self.now = outer_span.begin
425
472
  get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
426
473
  else
427
474
  outer_span
@@ -439,7 +486,7 @@ module Chronic
439
486
  end
440
487
 
441
488
  head = repeaters.shift
442
- head.start = Chronic.now
489
+ head.start = self.now
443
490
 
444
491
  case grabber.type
445
492
  when :last
@@ -453,7 +500,7 @@ module Chronic
453
500
  when :next
454
501
  outer_span = head.next(:future)
455
502
  else
456
- raise ChronicPain, "Invalid grabber"
503
+ raise "Invalid grabber"
457
504
  end
458
505
 
459
506
  if Chronic.debug