Parse natural language dates and times into Time or {Chronic::Span} objects
@example
require 'chronic' Time.now #=> Sun Aug 27 23:18:25 PDT 2006 Chronic.parse('tomorrow') #=> Mon Aug 28 12:00:00 PDT 2006 Chronic.parse('monday', :context => :past) #=> Mon Aug 21 12:00:00 PDT 2006 Chronic.parse('this tuesday 5:00') #=> Tue Aug 29 17:00:00 PDT 2006 Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none) #=> Tue Aug 29 05:00:00 PDT 2006 Chronic.parse('may 27th', :now => Time.local(2000, 1, 1)) #=> Sat May 27 12:00:00 PDT 2000 Chronic.parse('may 27th', :guess => false) #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
@author Tom Preston-Werner, Lee Jarvis
@return [Boolean] true when debug mode is enabled
@example
require 'chronic' require 'active_support/time' Time.zone = 'UTC' Chronic.time_class = Time.zone Chronic.parse('June 15 2006 at 5:54 AM') # => Thu, 15 Jun 2006 05:45:00 UTC +00:00
@return [Time] The time class Chronic uses internally
Construct a time Object
@return [Time]
# File lib/chronic/chronic.rb, line 234 def construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0) if second >= 60 minute += second / 60 second = second % 60 end if minute >= 60 hour += minute / 60 minute = minute % 60 end if hour >= 24 day += hour / 24 hour = hour % 24 end # determine if there is a day overflow. this is complicated by our crappy calendar # system (non-constant number of days per month) day <= 56 || raise("day must be no more than 56 (makes month resolution easier)") if day > 28 # no month ever has fewer than 28 days, so only do this if necessary leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] days_this_month = Date.leap?(year) ? leap_year_month_days[month - 1] : common_year_month_days[month - 1] if day > days_this_month month += day / days_this_month day = day % days_this_month end end if month > 12 if month % 12 == 0 year += (month - 12) / 12 month = 12 else year += month / 12 month = month % 12 end end Chronic.time_class.local(year, month, day, hour, minute, second) end
List of {Handler} definitions. See {parse} for a list of options this method accepts
@see parse @return [Hash] A Hash of Handler definitions
# File lib/chronic/chronic.rb, line 162 def definitions(options={}) options[:endian_precedence] ||= [:middle, :little] @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_day_name, :repeater_month_name, :scalar_day], :handle_rdn_rmn_sd), Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day], :handle_rdn_rmn_od), Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :repeater_time, :time_zone], :handle_sy_sm_sd_t_tz), 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([:ordinal_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_od_rmn_sy), Handler.new([:ordinal_day, :repeater_month_name, :separator_at?, 'time?'], :handle_od_rmn), Handler.new([:scalar_year, :repeater_month_name, :ordinal_day], :handle_sy_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), Handler.new([:scalar_day, :repeater_month_name, :separator_at?, 'time?'], :handle_sd_rmn), 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) ] } endians = [ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy), Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy) ] case endian = Array(options[:endian_precedence]).first when :little @definitions[:endian] = endians.reverse when :middle @definitions[:endian] = endians else raise ArgumentError, "Unknown endian option '#{endian}'" end @definitions end
Guess a specific time within the given span
@param [Span] span @return [Time]
# File lib/chronic/chronic.rb, line 149 def guess(span) if span.width > 1 span.begin + (span.width / 2) else span.begin end end
Convert number words to numbers (three => 3, fourth => 4th)
@see Chronic::Numerizer.numerize @param [String] text The string to convert @return [String] A new string with words converted to numbers
# File lib/chronic/chronic.rb, line 140 def numericize_numbers(text) warn "Chronic.numericize_numbers will be deprecated in version 0.7.0. Please use Chronic::Numerizer.numerize instead" Numerizer.numerize(text) end
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
@param [String] text The text to parse
@option opts [Symbol] :context (: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.
@option opts [Object] :now (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`
@option opts [Boolean] :guess (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
@option opts [Integer] :ambiguous_time_range (6)
* 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
@option opts [Array] :endian_precedence ([: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]`
@option opts [Integer] :ambiguous_year_future_bias (50)
* When parsing two digit years (ie 79) unlike Rubys Time class, Chronic will attempt to assume the full year using this figure. Chronic will look x amount of years into the future and past. If the two digit year is %xnow + x years` it's assumed to be the future, `now - x years` is assumed to be the past
@return [Time, Chronic::Span, nil]
# File lib/chronic/chronic.rb, line 61 def parse(text, opts={}) options = DEFAULT_OPTIONS.merge opts # ensure the specified options are valid (opts.keys - DEFAULT_OPTIONS.keys).each do |key| raise ArgumentError, "#{key} is not a valid option key." end unless [:past, :future, :none].include?(options[:context]) raise ArgumentError, "Invalid context, :past/:future only" end options[:text] = text Chronic.now = options[:now] || Chronic.time_class.now # tokenize words tokens = tokenize(text, options) if Chronic.debug puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}" end span = tokens_to_span(tokens, options) if span options[:guess] ? guess(span) : span end end
Clean up the specified text ready for parsing
Clean up the string 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)
@example
Chronic.pre_normalize('first day in May') #=> "1st day in may" Chronic.pre_normalize('tomorrow after noon') #=> "next day future 12:00" Chronic.pre_normalize('one hundred and thirty six days from now') #=> "136 days future this second"
@param [String] text The string to normalize @return [String] A new string ready for Chronic to parse
# File lib/chronic/chronic.rb, line 109 def pre_normalize(text) text = text.to_s.downcase text.gsub!(%r['"\.]/, '') text.gsub!(%r,/, ' ') text.gsub!(%r\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1') text = Numerizer.numerize(text) text.gsub!(%r \-(\d{4})\b/, ' tzminus\1') text.gsub!(%r([\/\-\,\@])/) { ' ' + $1 + ' ' } text.gsub!(%r(?:^|\s)0(\d+:\d+\s*pm?\b)/, '\1') text.gsub!(%r\btoday\b/, 'this day') text.gsub!(%r\btomm?orr?ow\b/, 'next day') text.gsub!(%r\byesterday\b/, 'last day') text.gsub!(%r\bnoon\b/, '12:00pm') text.gsub!(%r\bmidnight\b/, '24:00') text.gsub!(%r\bnow\b/, 'this second') text.gsub!(%r\b(?:ago|before(?: now)?)\b/, 'past') text.gsub!(%r\bthis (?:last|past)\b/, 'last') text.gsub!(%r\b(?:in|during) the (morning)\b/, '\1') text.gsub!(%r\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1') text.gsub!(%r\btonight\b/, 'this night') text.gsub!(%r\b\d+:?\d*[ap]\b/,'\0m') text.gsub!(%r(\d)([ap]m|oclock)\b/, '\1 \2') text.gsub!(%r\b(hence|after|from)\b/, 'future') text end