Module: Chronic

Defined in:
lib/chronic.rb,
lib/chronic/tag.rb,
lib/chronic/span.rb,
lib/chronic/token.rb,
lib/chronic/scalar.rb,
lib/chronic/chronic.rb,
lib/chronic/grabber.rb,
lib/chronic/ordinal.rb,
lib/chronic/pointer.rb,
lib/chronic/handlers.rb,
lib/chronic/repeater.rb,
lib/chronic/mini_date.rb,
lib/chronic/numerizer.rb,
lib/chronic/separator.rb,
lib/chronic/time_zone.rb,
lib/chronic/repeaters/repeater_day.rb,
lib/chronic/repeaters/repeater_hour.rb,
lib/chronic/repeaters/repeater_time.rb,
lib/chronic/repeaters/repeater_week.rb,
lib/chronic/repeaters/repeater_year.rb,
lib/chronic/repeaters/repeater_month.rb,
lib/chronic/repeaters/repeater_minute.rb,
lib/chronic/repeaters/repeater_season.rb,
lib/chronic/repeaters/repeater_second.rb,
lib/chronic/repeaters/repeater_weekday.rb,
lib/chronic/repeaters/repeater_weekend.rb,
lib/chronic/repeaters/repeater_day_name.rb,
lib/chronic/repeaters/repeater_fortnight.rb,
lib/chronic/repeaters/repeater_month_name.rb,
lib/chronic/repeaters/repeater_day_portion.rb,
lib/chronic/repeaters/repeater_season_name.rb

Overview

Name:       Chronic
Author:     Tom Preston-Werner
Purpose:    Parse natural language dates and times into Time or
            Chronic::Span objects

Defined Under Namespace

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

Constant Summary collapse

VERSION =
"0.4.0"
DEFAULT_OPTIONS =
{
  :context => :future,
  :now => nil,
  :guess => true,
  :ambiguous_time_range => 6,
  :endian_precedence    => [:middle, :little],
  :ambiguous_year_future_bias => 50
}

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.debugObject

Returns the value of attribute debug.



14
15
16
# File 'lib/chronic.rb', line 14

def debug
  @debug
end

.time_classObject

Returns the value of attribute time_class.



15
16
17
# File 'lib/chronic.rb', line 15

def time_class
  @time_class
end

Class Method Details

.apply_endian_precedences(precedences) ⇒ Object




119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/chronic/handlers.rb', line 119

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

.day_or_time(day_start, time_tokens, options) ⇒ Object



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

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
    get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
  else
    outer_span
  end
end

.dealias_and_disambiguate_times(tokens, options) ⇒ Object

:nodoc:



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
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/chronic/handlers.rb', line 431

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
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/chronic/handlers.rb', line 5

def definitions(options={}) #:nodoc:
  options[:endian_precedence] ||= [:middle, :little]
  # 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, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
      Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
      Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_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



132
133
134
# File 'lib/chronic/handlers.rb', line 132

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



418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/chronic/handlers.rb', line 418

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)
    find_within(rest, h, pointer)
  end
end

.get_anchor(tokens, options) ⇒ Object

support methods



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/chronic/handlers.rb', line 372

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 options[:context] != :past and 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:



405
406
407
408
409
410
411
412
413
# File 'lib/chronic/handlers.rb', line 405

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



137
138
139
140
141
142
143
144
# File 'lib/chronic/chronic.rb', line 137

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




152
153
154
155
156
157
158
159
# File 'lib/chronic/handlers.rb', line 152

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:



365
366
367
368
# File 'lib/chronic/handlers.rb', line 365

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:



360
361
362
363
# File 'lib/chronic/handlers.rb', line 360

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



345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/chronic/handlers.rb', line 345

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:



333
334
335
336
# File 'lib/chronic/handlers.rb', line 333

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



293
294
295
296
# File 'lib/chronic/handlers.rb', line 293

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:



298
299
300
301
# File 'lib/chronic/handlers.rb', line 298

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:



204
205
206
207
# File 'lib/chronic/handlers.rb', line 204

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:



173
174
175
# File 'lib/chronic/handlers.rb', line 173

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:



177
178
179
180
181
182
183
# File 'lib/chronic/handlers.rb', line 177

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_od_sy(tokens, options) ⇒ Object

:nodoc:



224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/chronic/handlers.rb', line 224

def handle_rmn_od_sy(tokens, options) #:nodoc:
  month = tokens[0].get_tag(RepeaterMonthName).index
  day = tokens[1].get_tag(OrdinalDay).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_sd(tokens, options) ⇒ Object

:nodoc:



161
162
163
# File 'lib/chronic/handlers.rb', line 161

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:



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

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:



209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/chronic/handlers.rb', line 209

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:



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/chronic/handlers.rb', line 185

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:



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/chronic/handlers.rb', line 313

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:



338
339
340
341
# File 'lib/chronic/handlers.rb', line 338

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:



239
240
241
242
243
# File 'lib/chronic/handlers.rb', line 239

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:



260
261
262
263
264
# File 'lib/chronic/handlers.rb', line 260

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:



245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/chronic/handlers.rb', line 245

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:



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/chronic/handlers.rb', line 272

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



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

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:



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

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)



124
125
126
# File 'lib/chronic/chronic.rb', line 124

def numericize_numbers(text) #:nodoc:
  Numerizer.numerize(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.

:endian_precedence

Array (defaults to [:middle, :little])

By default, Chronic will parse “03/04/2011” as the fourth day of the third month. Alternatively you can tell Chronic to parse this as the third day of the fourth month by altering the :endian_precedence to [:little, :middle].



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
# File 'lib/chronic/chronic.rb', line 59

def parse(text, specified_options = {})
  @text = text
  options = DEFAULT_OPTIONS.merge specified_options

  # ensure the specified options are valid
  (specified_options.keys-DEFAULT_OPTIONS.keys).each {|key| raise(InvalidArgumentException, "#{key} is not a valid option key.")}

  [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")

  options[:now] ||= Chronic.time_class.now
  @now = options[:now]

  # put the text into a normal format to ease scanning
  text = pre_normalize(text)

  # tokenize words
  @tokens = tokenize(text, options)

  if Chronic.debug
    puts "+---------------------------------------------------"
    puts "| " + @tokens.to_s
    puts "+---------------------------------------------------"
  end

  span = tokens_to_span(@tokens, options)

  if options[:guess]
    guess span
  else
    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)



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/chronic/chronic.rb', line 96

def pre_normalize(text) #:nodoc:
  normalized_text = text.to_s.downcase
  normalized_text.gsub!(/['"\.,]/, '')
  normalized_text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
  normalized_text = numericize_numbers(normalized_text)
  normalized_text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
  normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
  normalized_text.gsub!(/\b0(\d+:\d+\s*pm?\b)/, '\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
end

.swap(arr, a, b) ⇒ Object

exchange two elements in an array



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

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

.tokenize(text, options) ⇒ Object

:nodoc:



128
129
130
131
132
133
134
# File 'lib/chronic/chronic.rb', line 128

def tokenize(text, options) #:nodoc:
  tokens = text.split(' ').map { |word| Token.new(word) }
  [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
    tokens = tok.scan(tokens, options)
  end
  tokens.delete_if { |token| !token.tagged? }
end

.tokens_to_span(tokens, options) ⇒ Object

:nodoc:



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
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/chronic/handlers.rb', line 70

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