Module: Chronic

Defined in:
lib/chronic.rb,
lib/chronic/scalar.rb,
lib/chronic/chronic.rb,
lib/chronic/ordinal.rb,
lib/chronic/pointer.rb,
lib/chronic/handlers.rb,
lib/chronic/separator.rb,
lib/chronic/time_zone.rb

Defined Under Namespace

Classes: ChronicPain, Grabber, Handler, InvalidArgumentException, Ordinal, OrdinalDay, Pointer, Repeater, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterFortnight, RepeaterHour, RepeaterMinute, RepeaterMonth, RepeaterMonthName, RepeaterSeason, RepeaterSeasonName, RepeaterSecond, RepeaterTime, RepeaterWeek, RepeaterWeekday, RepeaterWeekend, RepeaterYear, Scalar, ScalarDay, ScalarMonth, ScalarYear, Separator, SeparatorAt, SeparatorComma, SeparatorIn, SeparatorOn, SeparatorSlashOrDash, Span, Tag, TimeZone, Token

Constant Summary collapse

VERSION =
"0.3.9"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.debugObject

Returns the value of attribute debug


48
49
50
# File 'lib/chronic.rb', line 48

def debug
  @debug
end

Class Method Details

.apply_endian_precedences(precedences) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/chronic/handlers.rb', line 108

def apply_endian_precedences(precedences)
  date_defs = @definitions[:date]

  # map the precedence array to indices on @definitions[:date]
  indices = precedences.map { |e|
    handler = instance_variable_get(endian_variable_name_for(e))
    date_defs.index(handler)
  }

  # swap the handlers if we discover they are at odds with the desired preferences
  swap(date_defs, indices.first, indices.last) if indices.first > indices.last
end

.base_tokenize(text) ⇒ Object

Split the text on spaces and convert each word into a Token


155
156
157
# File 'lib/chronic/chronic.rb', line 155

def base_tokenize(text) #:nodoc:
  text.split(' ').map { |word| Token.new(word) }
end

.day_or_time(day_start, time_tokens, options) ⇒ Object


128
129
130
131
132
133
134
135
136
137
138
# File 'lib/chronic/handlers.rb', line 128

def day_or_time(day_start, time_tokens, options)
  outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
  
  if !time_tokens.empty?
    @now = outer_span.begin
    time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
    return time
  else
    return outer_span
  end
end

.dealias_and_disambiguate_times(tokens, options) ⇒ Object

:nodoc:


408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/chronic/handlers.rb', line 408

def dealias_and_disambiguate_times(tokens, options) #:nodoc:
  # handle aliases of am/pm
  # 5:00 in the morning -> 5:00 am
  # 7:00 in the evening -> 7:00 pm
  
  day_portion_index = nil
  tokens.each_with_index do |t, i|
    if t.get_tag(RepeaterDayPortion)
      day_portion_index = i
      break
    end
  end
   
  time_index = nil
  tokens.each_with_index do |t, i|
    if t.get_tag(RepeaterTime)
      time_index = i
      break
    end
  end
  
  if (day_portion_index && time_index)
    t1 = tokens[day_portion_index]
    t1tag = t1.get_tag(RepeaterDayPortion)
  
    if [:morning].include?(t1tag.type)
      puts '--morning->am' if Chronic.debug
      t1.untag(RepeaterDayPortion)
      t1.tag(RepeaterDayPortion.new(:am))
    elsif [:afternoon, :evening, :night].include?(t1tag.type)
      puts "--#{t1tag.type}->pm" if Chronic.debug
      t1.untag(RepeaterDayPortion)
      t1.tag(RepeaterDayPortion.new(:pm))
    end
  end
  
  # tokens.each_with_index do |t0, i|
  #   t1 = tokens[i + 1]
  #   if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
  #     if [:morning].include?(t1tag.type)
  #       puts '--morning->am' if Chronic.debug
  #       t1.untag(RepeaterDayPortion)
  #       t1.tag(RepeaterDayPortion.new(:am))
  #     elsif [:afternoon, :evening, :night].include?(t1tag.type)
  #       puts "--#{t1tag.type}->pm" if Chronic.debug
  #       t1.untag(RepeaterDayPortion)
  #       t1.tag(RepeaterDayPortion.new(:pm))
  #     end
  #   end
  # end
        
  # handle ambiguous times if :ambiguous_time_range is specified
  if options[:ambiguous_time_range] != :none
    ttokens = []
    tokens.each_with_index do |t0, i|
      ttokens << t0
      t1 = tokens[i + 1]
      if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
        distoken = Token.new('disambiguator')
        distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
        ttokens << distoken
      end
    end
    tokens = ttokens
  end
  
  tokens
end

.definitions(options = {}) ⇒ Object

:nodoc:

Raises:


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/chronic/handlers.rb', line 5

def definitions(options={}) #:nodoc:
  options[:endian_precedence] = [:middle, :little] if options[:endian_precedence].nil?
  
  # ensure the endian precedence is exactly two elements long
  raise ChronicPain, "More than two elements specified for endian precedence array" unless options[:endian_precedence].length == 2

  # handler for dd/mm/yyyy
  @little_endian_handler ||= Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)

  # handler for mm/dd/yyyy
  @middle_endian_handler ||= Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy)

  # ensure we have valid endian values
  options[:endian_precedence].each do |e|
    raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e))
  end

  @definitions ||= 
  {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
    
   :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
             Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
             Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
             Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
             Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
             Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
             Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
             Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
             Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
             @middle_endian_handler,
             @little_endian_handler,
             Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
             Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
             
   # tonight at 7pm
   :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
               Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
               Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
               
   # 3 weeks from now, in 2 months
   :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
              Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
              Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
              
   # 3rd week in march
   :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
               Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
  }
  
  apply_endian_precedences(options[:endian_precedence])
  
  @definitions
end

.endian_variable_name_for(e) ⇒ Object


121
122
123
# File 'lib/chronic/handlers.rb', line 121

def endian_variable_name_for(e)
  "@#{e.to_s}_endian_handler".to_sym
end

.find_within(tags, span, pointer) ⇒ Object

Recursively finds repeaters within other repeaters. Returns a Span representing the innermost time span or nil if no repeater union could be found


393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/chronic/handlers.rb', line 393

def find_within(tags, span, pointer) #:nodoc:
  puts "--#{span}" if Chronic.debug
  return span if tags.empty?

  head, *rest = tags
  head.start = pointer == :future ? span.begin : span.end
  h = head.this(:none)

  if span.cover?(h.begin) || span.cover?(h.end)
    return find_within(rest, h, pointer)
  else
    return nil
  end
end

.get_anchor(tokens, options) ⇒ Object

support methods


347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/chronic/handlers.rb', line 347

def get_anchor(tokens, options) #:nodoc:
  grabber = Grabber.new(:this)
  pointer = :future
  
  repeaters = self.get_repeaters(tokens)
  repeaters.size.times { tokens.pop }
                
  if tokens.first && tokens.first.get_tag(Grabber)
    grabber = tokens.first.get_tag(Grabber)
    tokens.pop
  end
  
  head = repeaters.shift
  head.start = @now
              
  case grabber.type
    when :last
      outer_span = head.next(:past)
    when :this
      if repeaters.size > 0
        outer_span = head.this(:none)
      else
        outer_span = head.this(options[:context])
      end
    when :next
      outer_span = head.next(:future)
    else raise(ChronicPain, "Invalid grabber")
  end
  
  puts "--#{outer_span}" if Chronic.debug
  anchor = find_within(repeaters, outer_span, pointer)
end

.get_repeaters(tokens) ⇒ Object

:nodoc:


380
381
382
383
384
385
386
387
388
# File 'lib/chronic/handlers.rb', line 380

def get_repeaters(tokens) #:nodoc:
  repeaters = []
  tokens.each do |token|
    if t = token.get_tag(Repeater)
      repeaters << t
    end
  end
  repeaters.sort.reverse
end

.guess(span) ⇒ Object

Guess a specific time within the given span


160
161
162
163
164
165
166
167
# File 'lib/chronic/chronic.rb', line 160

def guess(span) #:nodoc:
  return nil if span.nil?
  if span.width > 1
    span.begin + (span.width / 2)
  else
    span.begin
  end
end

.handle_m_d(month, day, time_tokens, options) ⇒ Object



142
143
144
145
146
147
148
149
# File 'lib/chronic/handlers.rb', line 142

def handle_m_d(month, day, time_tokens, options) #:nodoc:
  month.start = @now
  span = month.this(options[:context])
  
  day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day)
  
  day_or_time(day_start, time_tokens, options)
end

.handle_o_r_g_r(tokens, options) ⇒ Object

:nodoc:


340
341
342
343
# File 'lib/chronic/handlers.rb', line 340

def handle_o_r_g_r(tokens, options) #:nodoc:
  outer_span = get_anchor(tokens[2..3], options)
  handle_orr(tokens[0..1], outer_span, options)
end

.handle_o_r_s_r(tokens, options) ⇒ Object

:nodoc:


335
336
337
338
# File 'lib/chronic/handlers.rb', line 335

def handle_o_r_s_r(tokens, options) #:nodoc:
  outer_span = get_anchor([tokens[3]], options)
  handle_orr(tokens[0..1], outer_span, options)
end

.handle_orr(tokens, outer_span, options) ⇒ Object

narrows


320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/chronic/handlers.rb', line 320

def handle_orr(tokens, outer_span, options) #:nodoc:
  repeater = tokens[1].get_tag(Repeater)
  repeater.start = outer_span.begin - 1
  ordinal = tokens[0].get_tag(Ordinal).type
  span = nil
  ordinal.times do
    span = repeater.next(:future)
    if span.begin > outer_span.end
      span = nil
      break
    end
  end
  span
end

.handle_p_s_r(tokens, options) ⇒ Object

:nodoc:


308
309
310
311
# File 'lib/chronic/handlers.rb', line 308

def handle_p_s_r(tokens, options) #:nodoc:
  new_tokens = [tokens[1], tokens[2], tokens[0]]
  self.handle_s_r_p(new_tokens, options)
end

.handle_r(tokens, options) ⇒ Object

anchors


268
269
270
271
# File 'lib/chronic/handlers.rb', line 268

def handle_r(tokens, options) #:nodoc:
  dd_tokens = dealias_and_disambiguate_times(tokens, options)
  self.get_anchor(dd_tokens, options)
end

.handle_r_g_r(tokens, options) ⇒ Object

:nodoc:


273
274
275
276
# File 'lib/chronic/handlers.rb', line 273

def handle_r_g_r(tokens, options) #:nodoc:
  new_tokens = [tokens[1], tokens[0], tokens[2]]
  self.handle_r(new_tokens, options)
end

.handle_rdn_rmn_sd_t_tz_sy(tokens, options) ⇒ Object

:nodoc:


194
195
196
197
# File 'lib/chronic/handlers.rb', line 194

def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
  t = Chronic.time_class.parse(@text)
  Span.new(t, t + 1)
end

.handle_rmn_od(tokens, options) ⇒ Object

:nodoc:


163
164
165
# File 'lib/chronic/handlers.rb', line 163

def handle_rmn_od(tokens, options) #:nodoc:
  handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
end

.handle_rmn_od_on(tokens, options) ⇒ Object

:nodoc:


167
168
169
170
171
172
173
# File 'lib/chronic/handlers.rb', line 167

def handle_rmn_od_on(tokens, options) #:nodoc:
  if tokens.size > 3
    handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(OrdinalDay).type, tokens[0..1], options)
  else
    handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(OrdinalDay).type, tokens[0..0], options)
  end
end

.handle_rmn_sd(tokens, options) ⇒ Object

:nodoc:


151
152
153
# File 'lib/chronic/handlers.rb', line 151

def handle_rmn_sd(tokens, options) #:nodoc:
  handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
end

.handle_rmn_sd_on(tokens, options) ⇒ Object

:nodoc:


155
156
157
158
159
160
161
# File 'lib/chronic/handlers.rb', line 155

def handle_rmn_sd_on(tokens, options) #:nodoc:
  if tokens.size > 3
    handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(ScalarDay).type, tokens[0..1], options)
  else
    handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(ScalarDay).type, tokens[0..0], options)
  end
end

.handle_rmn_sd_sy(tokens, options) ⇒ Object

:nodoc:


199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/chronic/handlers.rb', line 199

def handle_rmn_sd_sy(tokens, options) #:nodoc:
  month = tokens[0].get_tag(RepeaterMonthName).index
  day = tokens[1].get_tag(ScalarDay).type
  year = tokens[2].get_tag(ScalarYear).type
  
  time_tokens = tokens.last(tokens.size - 3)
  
  begin
    day_start = Chronic.time_class.local(year, month, day)
    day_or_time(day_start, time_tokens, options)
  rescue ArgumentError
    nil
  end
end

.handle_rmn_sy(tokens, options) ⇒ Object

:nodoc:


175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/chronic/handlers.rb', line 175

def handle_rmn_sy(tokens, options) #:nodoc:
  month = tokens[0].get_tag(RepeaterMonthName).index
  year = tokens[1].get_tag(ScalarYear).type
  
  if month == 12
    next_month_year = year + 1
    next_month_month = 1
  else
    next_month_year = year
    next_month_month = month + 1
  end
  
  begin
    Span.new(Chronic.time_class.local(year, month), Chronic.time_class.local(next_month_year, next_month_month))
  rescue ArgumentError
    nil
  end
end

.handle_s_r_p(tokens, options) ⇒ Object

:nodoc:


288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/chronic/handlers.rb', line 288

def handle_s_r_p(tokens, options) #:nodoc:
  repeater = tokens[1].get_tag(Repeater)
        
  # span = 
  # case true
  # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
  #   self.parse("this hour", :guess => false, :now => @now)
  # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
  #   self.parse("this minute", :guess => false, :now => @now)
  # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
  #   self.parse("this second", :guess => false, :now => @now)
  # else
  #   raise(ChronicPain, "Invalid repeater: #{repeater.class}")
  # end
  
  span = self.parse("this second", :guess => false, :now => @now)
  
  self.handle_srp(tokens, span, options)
end

.handle_s_r_p_a(tokens, options) ⇒ Object

:nodoc:


313
314
315
316
# File 'lib/chronic/handlers.rb', line 313

def handle_s_r_p_a(tokens, options) #:nodoc:
  anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
  self.handle_srp(tokens, anchor_span, options)
end

.handle_sd_rmn_sy(tokens, options) ⇒ Object

:nodoc:


214
215
216
217
218
# File 'lib/chronic/handlers.rb', line 214

def handle_sd_rmn_sy(tokens, options) #:nodoc:
  new_tokens = [tokens[1], tokens[0], tokens[2]]
  time_tokens = tokens.last(tokens.size - 3)
  self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
end

.handle_sd_sm_sy(tokens, options) ⇒ Object

:nodoc:


235
236
237
238
239
# File 'lib/chronic/handlers.rb', line 235

def handle_sd_sm_sy(tokens, options) #:nodoc:
  new_tokens = [tokens[1], tokens[0], tokens[2]]
  time_tokens = tokens.last(tokens.size - 3)
  self.handle_sm_sd_sy(new_tokens + time_tokens, options)
end

.handle_sm_sd_sy(tokens, options) ⇒ Object

:nodoc:


220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/chronic/handlers.rb', line 220

def handle_sm_sd_sy(tokens, options) #:nodoc:
  month = tokens[0].get_tag(ScalarMonth).type
  day = tokens[1].get_tag(ScalarDay).type
  year = tokens[2].get_tag(ScalarYear).type
  
  time_tokens = tokens.last(tokens.size - 3)
  
  begin
    day_start = Chronic.time_class.local(year, month, day) #:nodoc:
    day_or_time(day_start, time_tokens, options)
  rescue ArgumentError
    nil
  end
end

.handle_sm_sy(tokens, options) ⇒ Object

:nodoc:


247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/chronic/handlers.rb', line 247

def handle_sm_sy(tokens, options) #:nodoc:
  month = tokens[0].get_tag(ScalarMonth).type
  year = tokens[1].get_tag(ScalarYear).type
  
  if month == 12
    next_month_year = year + 1
    next_month_month = 1
  else
    next_month_year = year
    next_month_month = month + 1
  end
  
  begin
    Span.new(Chronic.time_class.local(year, month), Chronic.time_class.local(next_month_year, next_month_month))
  rescue ArgumentError
    nil
  end
end

.handle_srp(tokens, span, options) ⇒ Object

arrows


280
281
282
283
284
285
286
# File 'lib/chronic/handlers.rb', line 280

def handle_srp(tokens, span, options) #:nodoc:
  distance = tokens[0].get_tag(Scalar).type
  repeater = tokens[1].get_tag(Repeater)
  pointer = tokens[2].get_tag(Pointer).type
  
  repeater.offset(span, distance, pointer)
end

.handle_sy_sm_sd(tokens, options) ⇒ Object

:nodoc:


241
242
243
244
245
# File 'lib/chronic/handlers.rb', line 241

def handle_sy_sm_sd(tokens, options) #:nodoc:
  new_tokens = [tokens[1], tokens[2], tokens[0]]
  time_tokens = tokens.last(tokens.size - 3)
  self.handle_sm_sd_sy(new_tokens + time_tokens, options)
end

.numericize_numbers(text) ⇒ Object

Convert number words to numbers (three => 3)


144
145
146
# File 'lib/chronic/chronic.rb', line 144

def numericize_numbers(text) #:nodoc:
  Numerizer.numerize(text)
end

.numericize_ordinals(text) ⇒ Object

Convert ordinal words to numeric ordinals (third => 3rd)


149
150
151
# File 'lib/chronic/chronic.rb', line 149

def numericize_ordinals(text) #:nodoc:
  text
end

.parse(text, specified_options = {}) ⇒ Object

Parses a string containing a natural language date or time. If the parser can find a date or time, either a Time or Chronic::Span will be returned (depending on the value of :guess). If no date or time can be found, nil will be returned.

Options are:

:context

:past or :future (defaults to :future)

If your string represents a birthday, you can set :context to :past and if an ambiguous string is given, it will assume it is in the past. Specify :future or omit to set a future context.

:now

Time (defaults to Time.now)

By setting :now to a Time, all computations will be based off of that time instead of Time.now. If set to nil, Chronic will use Time.now.

:guess

true or false (defaults to true)

By default, the parser will guess a single point in time for the given date or time. If you'd rather have the entire time span returned, set :guess to false and a Chronic::Span will be returned.

:ambiguous_time_range

Integer or :none (defaults to 6 (6am-6pm))

If an Integer is given, ambiguous times (like 5:00) will be assumed to be within the range of that time in the AM to that time in the PM. For example, if you set it to 7, then the parser will look for the time between 7am and 7pm. In the case of 5:00, it would assume that means 5:00pm. If :none is given, no assumption will be made, and the first matching instance of that time will be used.


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/chronic/chronic.rb', line 41

def parse(text, specified_options = {})
  @text = text
  
  # get options and set defaults if necessary
  default_options = {:context => :future,
                     :now => Chronic.time_class.now,
                     :guess => true,
                     :ambiguous_time_range => 6,
                     :endian_precedence => nil}
  options = default_options.merge specified_options
  
  # handle options that were set to nil
  options[:context] = :future unless options[:context]
  options[:now] = Chronic.time_class.now unless options[:context]
  options[:ambiguous_time_range] = 6 unless options[:ambiguous_time_range]
        
  # ensure the specified options are valid
  specified_options.keys.each do |key|
    default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
  end
  [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
  
  # store now for later =)
  @now = options[:now]
  
  # put the text into a normal format to ease scanning
  text = self.pre_normalize(text)
      
  # get base tokens for each word
  @tokens = self.base_tokenize(text)

  # scan the tokens with each token scanner
  [Repeater].each do |tokenizer|
    @tokens = tokenizer.scan(@tokens, options)
  end
  
  [Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
    @tokens = tokenizer.scan(@tokens)
  end
  
  # strip any non-tagged tokens
  @tokens = @tokens.select { |token| token.tagged? }
  
  if Chronic.debug
    puts "+---------------------------------------------------"
    puts "| " + @tokens.to_s
    puts "+---------------------------------------------------"
  end
  
  # do the heavy lifting
  begin
    span = self.tokens_to_span(@tokens, options)
  rescue
    raise
    return nil
  end
  
  # guess a time within a span if required
  if options[:guess]
    return self.guess(span)
  else
    return span
  end
end

.pre_normalize(text) ⇒ Object

Clean up the specified input text by stripping unwanted characters, converting idioms to their canonical form, converting number words to numbers (three => 3), and converting ordinal words to numeric ordinals (third => 3rd)


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/chronic/chronic.rb', line 110

def pre_normalize(text) #:nodoc:
  normalized_text = text.to_s.downcase
  normalized_text = numericize_numbers(normalized_text)
  # completely removing periods breaks decimal minutes, etc.
  # tests indicate a period should really act as a : in time
  # and a - in the date.  Not exactly sure what to do with that.
  # If between numbers, assume time and make it a colon.
  # Will not work for a date like 10.15.2010
  normalized_text.gsub!(/([0-9])[\.]([0-9])/, '\1:\2')
  
  # probably not time now, so let's make the rest a space
  normalized_text.gsub!(/['"\.,]/, ' ')
  normalized_text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
  normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
  normalized_text.gsub!(/\btoday\b/, 'this day')
  normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
  normalized_text.gsub!(/\byesterday\b/, 'last day')
  normalized_text.gsub!(/\bnoon\b/, '12:00')
  normalized_text.gsub!(/\bmidnight\b/, '24:00')
  normalized_text.gsub!(/\bbefore now\b/, 'past')
  normalized_text.gsub!(/\bnow\b/, 'this second')
  normalized_text.gsub!(/\b(ago|before)\b/, 'past')
  normalized_text.gsub!(/\bthis past\b/, 'last')
  normalized_text.gsub!(/\bthis last\b/, 'last')
  normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
  normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
  normalized_text.gsub!(/\btonight\b/, 'this night')
  normalized_text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
  normalized_text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
  normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
  normalized_text = numericize_ordinals(normalized_text)
end

.swap(arr, a, b) ⇒ Object

exchange two elements in an array


126
# File 'lib/chronic/handlers.rb', line 126

def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end

.time_classObject


50
51
52
# File 'lib/chronic.rb', line 50

def time_class
  Thread.current[:chronic_time_class] ||= Time
end

.time_class=(klass) ⇒ Object


54
55
56
# File 'lib/chronic.rb', line 54

def time_class=(klass)
  Thread.current[:chronic_time_class] = klass
end

.tokens_to_span(tokens, options) ⇒ Object

:nodoc:


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/chronic/handlers.rb', line 59

def tokens_to_span(tokens, options) #:nodoc:
  # maybe it's a specific date
  
  definitions = self.definitions(options)
  definitions[:date].each do |handler|
    if handler.match(tokens, definitions)
      puts "-date" if Chronic.debug
      good_tokens = tokens.select { |o| !o.get_tag Separator }
      return self.send(handler.handler_method, good_tokens, options)
    end
  end
        
  # I guess it's not a specific date, maybe it's just an anchor
        
  definitions[:anchor].each do |handler|
    if handler.match(tokens, definitions)
      puts "-anchor" if Chronic.debug
      good_tokens = tokens.select { |o| !o.get_tag Separator }
      return self.send(handler.handler_method, good_tokens, options)
    end
  end
        
  # not an anchor, perhaps it's an arrow
  
  definitions[:arrow].each do |handler|
    if handler.match(tokens, definitions)
      puts "-arrow" if Chronic.debug
      good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
      return self.send(handler.handler_method, good_tokens, options)
    end
  end
  
  # not an arrow, let's hope it's a narrow
  
  definitions[:narrow].each do |handler|
    if handler.match(tokens, definitions)
      puts "-narrow" if Chronic.debug
      #good_tokens = tokens.select { |o| !o.get_tag Separator }
      return self.send(handler.handler_method, tokens, options)
    end
  end
  
  # I guess you're out of luck!
  puts "-none" if Chronic.debug
  return nil
end