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, MiniDate, 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.3.0.2"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.debugObject

Returns the value of attribute debug.



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

def debug
  @debug
end

.time_classObject

Returns the value of attribute time_class.



52
53
54
# File 'lib/chronic.rb', line 52

def time_class
  @time_class
end

Class Method Details

.apply_endian_precedences(precedences) ⇒ Object




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

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



177
178
179
# File 'lib/chronic/chronic.rb', line 177

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

.day_or_time(day_start, time_tokens, options) ⇒ Object



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

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:



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

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

.default_options(specified_options) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/chronic/chronic.rb', line 106

def default_options(specified_options)
	# get options and set defaults if necessary
	default_options = {:context => :future,
		:now => Chronic.time_class.now,
		:guess => true,
		:guess_how => :middle,
		: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.")
	["start", "middle", "end", true, false].include?(options[:guess]) || validate_percentness_of(options[:guess]) || raise(InvalidArgumentException, "Invalid value ':#{options[:guess]}' for :guess how specified. Valid values are true, false, \"start\", \"middle\", and \"end\".  true will default to \"middle\". :guess can also be a percent(0.60)")

	return options
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
# 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_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
				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, :repeater, :grabber, :repeater], :handle_r_r_g_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



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

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



414
415
416
417
418
419
420
421
422
423
424
425
426
427
# File 'lib/chronic/handlers.rb', line 414

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.include?(h.begin) || span.include?(h.end)
		return find_within(rest, h, pointer)
	else
		return nil
	end
end

.get_anchor(tokens, options) ⇒ Object

support methods



368
369
370
371
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
# File 'lib/chronic/handlers.rb', line 368

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:



401
402
403
404
405
406
407
408
409
# File 'lib/chronic/handlers.rb', line 401

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, guess = true) ⇒ Object

Guess a specific time within the given span



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

def guess(span, guess=true) #:nodoc:
	return nil if span.nil?
	if span.width > 1
		# Account for a timezone difference between the start and end of the range.
		# This most likely will happen when dealing with a Daylight Saving Time start
		# or end day.
		gmt_offset_diff = span.begin.gmt_offset - span.end.gmt_offset
		case guess
		when "start"
			span.begin
		when true, "middle"
			span.begin + ((span.width - gmt_offset_diff) / 2)
		when "end"
			span.begin + (span.width - gmt_offset_diff)
		else
			span.begin + ((span.width - gmt_offset_diff) * guess)
		end
	else
		span.begin
	end
end

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




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

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:



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

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:



356
357
358
359
# File 'lib/chronic/handlers.rb', line 356

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



341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/chronic/handlers.rb', line 341

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:



329
330
331
332
# File 'lib/chronic/handlers.rb', line 329

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



286
287
288
289
# File 'lib/chronic/handlers.rb', line 286

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:



291
292
293
# File 'lib/chronic/handlers.rb', line 291

def handle_r_g_r(tokens, options) #:nodoc:
	self.handle_r(tokens.values_at(1,0,2), options)
end

.handle_r_r_g_r(tokens, options) ⇒ Object

:nodoc:



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

def handle_r_r_g_r(tokens, options) #:nodoc:
	self.handle_r(tokens.values_at(2,3,0,1), options)
end

.handle_rdn_rmn_sd_t_tz_sy(tokens, options) ⇒ Object

:nodoc:



212
213
214
215
# File 'lib/chronic/handlers.rb', line 212

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:



165
166
167
# File 'lib/chronic/handlers.rb', line 165

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:



185
186
187
188
189
190
191
# File 'lib/chronic/handlers.rb', line 185

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:



169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/chronic/handlers.rb', line 169

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:



153
154
155
# File 'lib/chronic/handlers.rb', line 153

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:



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

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:



217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/chronic/handlers.rb', line 217

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:



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/chronic/handlers.rb', line 193

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:



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/chronic/handlers.rb', line 309

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:



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

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:



232
233
234
235
236
# File 'lib/chronic/handlers.rb', line 232

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:



253
254
255
256
257
# File 'lib/chronic/handlers.rb', line 253

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:



238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/chronic/handlers.rb', line 238

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:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/chronic/handlers.rb', line 265

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



301
302
303
304
305
306
307
# File 'lib/chronic/handlers.rb', line 301

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:



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

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)



166
167
168
# File 'lib/chronic/chronic.rb', line 166

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

.numericize_ordinals(text) ⇒ Object

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



171
172
173
# File 'lib/chronic/chronic.rb', line 171

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, false, “start”, “middle”, and “end” (defaults to true)

By default, the parser will guess a single point in time for the given date or time. :guess => true or “middle” will return the middle value of the range. If “start” is specified, Chronic::Span will return the beginning of the range. If “end” is specified, the last value in Chronic::Span will be returned. 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.



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

def parse(text, specified_options = {})
	# strip any non-tagged tokens
	@tokens = tokenize(text, specified_options).select { |token| token.tagged? }

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

	options = default_options(specified_options)

	# 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, options[:guess])
	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)



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/chronic/chronic.rb', line 135

def pre_normalize(text) #:nodoc:
	normalized_text = text.to_s
	normalized_text = numericize_numbers(normalized_text)
	normalized_text.gsub!(/['",]/, '')
	normalized_text.gsub!(/(\d+\:\d+)\.(\d+)/, '\1\2')
	normalized_text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
	normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
	normalized_text.gsub!(/\btoday\b/i, 'this day')
	normalized_text.gsub!(/\btomm?orr?ow\b/i, 'next day')
	normalized_text.gsub!(/\byesterday\b/i, 'last day')
	normalized_text.gsub!(/\bnoon\b/i, '12:00')
	normalized_text.gsub!(/\bmidnight\b/i, '24:00')
	normalized_text.gsub!(/\bbefore now\b/i, 'past')
	normalized_text.gsub!(/\bnow\b/i, 'this second')
	normalized_text.gsub!(/\b(ago|before)\b/i, 'past')
	normalized_text.gsub!(/\bthis past\b/i, 'last')
	normalized_text.gsub!(/\bthis last\b/i, 'last')
	normalized_text.gsub!(/\b(?:in|during) the (morning)\b/i, '\1')
	normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/i, '\1')
	normalized_text.gsub!(/\btonight\b/i, 'this night')
	normalized_text.gsub!(/\b\d+:?\d*[ap]\b/i,'\0m')
	normalized_text.gsub!(/(\d)([ap]m|oclock)\b/i, '\1 \2')
	normalized_text.gsub!(/\b(hence|after|from)\b/i, 'future')
	normalized_text.gsub!(/\bh[ou]{0,2}rs?\b/i, 'hour')
	#not needed - see test_parse_before_now (test_parsing.rb +726)
	#normalized_text.gsub!(/\bbefore now\b/, 'past')

	normalized_text = numericize_ordinals(normalized_text)
end

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



72
73
74
75
# File 'lib/chronic/chronic.rb', line 72

def strip_tokens(text, specified_options = {})
	# strip any tagged tokens
	return tokenize(text, specified_options).select { |token| !token.tagged? }.map { |token| token.word }.join(' ')
end

.swap(arr, a, b) ⇒ Object

exchange two elements in an array



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

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

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

Returns an array with text tokenized by the respective classes



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 78

def tokenize(text, specified_options = {})
	@text = text

	options = default_options(specified_options)
	# store now for later =)
	@now = options[:now]

	# put the text into a normal format to ease scanning
	puts "+++ text = #{text}" if Chronic.debug
	text = self.pre_normalize(text)
	puts "--- text = #{text}" if Chronic.debug

	# get base tokens for each word
	@tokens = self.base_tokenize(text)
	puts @tokens if Chronic.debug

	# 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

	return @tokens
end

.tokens_to_span(tokens, options) ⇒ Object

:nodoc:



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
105
106
# File 'lib/chronic/handlers.rb', line 61

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

.validate_percentness_of(number) ⇒ Object

Validates numericality of something



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

def validate_percentness_of(number) #:nodoc:
	number.to_s.to_f == number && number >= 0 && number <= 1
end