+<?php
+/**
+ *
+ */
+class Horde_Date_Parser
+{
+ public static function factory($args = array())
+ {
+ $locale = isset($args['locale']) ? $args['locale'] : null;
+ if ($locale && strtolower($locale) != 'base') {
+ $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
+ $class = 'Horde_Date_Parser_Locale_' . $locale;
+ if (class_exists($class)) {
+ return new $class($args);
+ }
-alias p_orig p
+ $language = array_shift(explode('_', $locale));
+ if ($language != $locale) {
+ $class = 'Horde_Date_Parser_Locale_' . $language;
+ if (class_exists($class)) {
+ return new $class($args);
+ }
+ }
+ }
-def p(val)
- p_orig val
- puts
-end
+ return new Horde_Date_Parser_Locale_Base($args);
+ }
-class Time
- def self.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 = (year % 4 == 0) && !(year % 100 == 0)
- 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 = 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
-
- Time.local(year, month, day, hour, minute, second)
- end
-end
+}
--- /dev/null
+<?php
+class Horde_Date_Parser_Exception extends Exception
+{
+}
+++ /dev/null
-#module Chronic
-
- class Chronic::Grabber < Chronic::Tag #:nodoc:
- def self.scan(tokens)
- tokens.each_index do |i|
- if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
- end
- tokens
- end
-
- def self.scan_for_all(token)
- scanner = {/last/ => :last,
- /this/ => :this,
- /next/ => :next}
- scanner.keys.each do |scanner_item|
- return self.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def to_s
- 'grabber-' << @type.to_s
- end
- end
-
-#end
\ No newline at end of file
+++ /dev/null
-module Chronic
-
- class << self
-
- def definitions #:nodoc:
- @definitions ||=
- {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
-
- :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :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, :separator_at?, 'time?'], :handle_rmn_od),
- 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_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),
- 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)]
- }
- end
-
- def tokens_to_span(tokens, options) #:nodoc:
- # maybe it's a specific date
-
- self.definitions[:date].each do |handler|
- if handler.match(tokens, self.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
-
- self.definitions[:anchor].each do |handler|
- if handler.match(tokens, self.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
-
- self.definitions[:arrow].each do |handler|
- if handler.match(tokens, self.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
-
- self.definitions[:narrow].each do |handler|
- if handler.match(tokens, self.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
-
- #--------------
-
- 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
-
- #--------------
-
- def handle_m_d(month, day, time_tokens, options) #:nodoc:
- month.start = @now
- span = month.this(options[:context])
-
- day_start = Time.local(span.begin.year, span.begin.month, day)
-
- day_or_time(day_start, time_tokens, options)
- end
-
- 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
-
- 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
-
- 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(Time.local(year, month), Time.local(next_month_year, next_month_month))
- rescue ArgumentError
- nil
- end
- end
-
- def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
- month = tokens[1].get_tag(RepeaterMonthName).index
- day = tokens[2].get_tag(ScalarDay).type
- year = tokens[5].get_tag(ScalarYear).type
-
- begin
- day_start = Time.local(year, month, day)
- day_or_time(day_start, [tokens[3]], options)
- rescue ArgumentError
- nil
- end
- end
-
- 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 = Time.local(year, month, day)
- day_or_time(day_start, time_tokens, options)
- rescue ArgumentError
- nil
- end
- end
-
- 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
-
- 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 = Time.local(year, month, day) #:nodoc:
- day_or_time(day_start, time_tokens, options)
- rescue ArgumentError
- nil
- end
- end
-
- 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
-
- 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
-
- 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(Time.local(year, month), Time.local(next_month_year, next_month_month))
- rescue ArgumentError
- nil
- end
- end
-
- # anchors
-
- def handle_r(tokens, options) #:nodoc:
- dd_tokens = dealias_and_disambiguate_times(tokens, options)
- self.get_anchor(dd_tokens, options)
- end
-
- def handle_r_g_r(tokens, options) #:nodoc:
- new_tokens = [tokens[1], tokens[0], tokens[2]]
- self.handle_r(new_tokens, options)
- end
-
- # arrows
-
- 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
-
- 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
-
- 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
-
- 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
-
- # narrows
-
- 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
-
- 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
-
- 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
-
- # support methods
-
- 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
-
- def get_repeaters(tokens) #:nodoc:
- repeaters = []
- tokens.each do |token|
- if t = token.get_tag(Repeater)
- repeaters << t
- end
- end
- repeaters.sort.reverse
- end
-
- # Recursively finds repeaters within other repeaters.
- # Returns a Span representing the innermost time span
- # or nil if no repeater union could be found
- 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
-
- 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
-
- end
-
- class Handler #:nodoc:
- attr_accessor :pattern, :handler_method
-
- def initialize(pattern, handler_method)
- @pattern = pattern
- @handler_method = handler_method
- end
-
- def constantize(name)
- camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
- ::Chronic.module_eval(camel, __FILE__, __LINE__)
- end
-
- def match(tokens, definitions)
- token_index = 0
- @pattern.each do |element|
- name = element.to_s
- optional = name.reverse[0..0] == '?'
- name = name.chop if optional
- if element.instance_of? Symbol
- klass = constantize(name)
- match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
- return false if !match && !optional
- (token_index += 1; next) if match
- next if !match && optional
- elsif element.instance_of? String
- return true if optional && token_index == tokens.size
- sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
- sub_handlers.each do |sub_handler|
- return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
- end
- return false
- else
- raise(ChronicPain, "Invalid match type: #{element.class}")
- end
- end
- return false if token_index != tokens.size
- return true
- end
- end
-
-end
\ No newline at end of file
--- /dev/null
+module Chronic
+ class << self
+
+ # 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 <tt>:guess</tt>). If no date or time can be found,
+ # +nil+ will be returned.
+ #
+ # Options are:
+ #
+ # [<tt>:context</tt>]
+ # <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
+ #
+ # If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
+ # and if an ambiguous string is given, it will assume it is in the
+ # past. Specify <tt>:future</tt> or omit to set a future context.
+ #
+ # [<tt>:now</tt>]
+ # Time (defaults to Time.now)
+ #
+ # By setting <tt>:now</tt> to a Time, all computations will be based off
+ # of that time instead of Time.now
+ #
+ # [<tt>:guess</tt>]
+ # +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 <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
+ #
+ # [<tt>:ambiguous_time_range</tt>]
+ # Integer or <tt>:none</tt> (defaults to <tt>6</tt> (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 <tt>7</tt>, 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 <tt>:none</tt> is given, no assumption
+ # will be made, and the first matching instance of that time will
+ # be used.
+ def parse(text, specified_options = {})
+ # get options and set defaults if necessary
+ default_options = {:context => :future,
+ :now => Time.now,
+ :guess => true,
+ :ambiguous_time_range => 6}
+ options = default_options.merge specified_options
+
+ # 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
+
+ # 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)
+ def pre_normalize(text) #:nodoc:
+ normalized_text = text.to_s.downcase
+ normalized_text = numericize_numbers(normalized_text)
+ normalized_text.gsub!(/['"\.]/, '')
+ 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!(/(?=\w)([ap]m|oclock)\b/, ' \1')
+ normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
+ normalized_text = numericize_ordinals(normalized_text)
+ end
+
+ # Convert number words to numbers (three => 3)
+ def numericize_numbers(text) #:nodoc:
+ Numerizer.numerize(text)
+ end
+
+ # Convert ordinal words to numeric ordinals (third => 3rd)
+ def numericize_ordinals(text) #:nodoc:
+ text
+ end
+
+ # Split the text on spaces and convert each word into
+ # a Token
+ def base_tokenize(text) #:nodoc:
+ text.split(' ').map { |word| Token.new(word) }
+ end
+
+ # Guess a specific time within the given span
+ def guess(span) #:nodoc:
+ return nil if span.nil?
+ if span.width > 1
+ span.begin + (span.width / 2)
+ else
+ span.begin
+ end
+ end
+ end
+
+end
--- /dev/null
+#module Chronic
+
+ class Chronic::Grabber < Chronic::Tag #:nodoc:
+ def self.scan(tokens)
+ tokens.each_index do |i|
+ if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
+ end
+ tokens
+ end
+
+ def self.scan_for_all(token)
+ scanner = {/last/ => :last,
+ /this/ => :this,
+ /next/ => :next}
+ scanner.keys.each do |scanner_item|
+ return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def to_s
+ 'grabber-' << @type.to_s
+ end
+ end
+
+#end
\ No newline at end of file
--- /dev/null
+module Chronic
+
+ class << self
+
+ def definitions #:nodoc:
+ @definitions ||=
+ {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
+
+ :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :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, :separator_at?, 'time?'], :handle_rmn_od),
+ 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_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),
+ 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)]
+ }
+ end
+
+ def tokens_to_span(tokens, options) #:nodoc:
+ # maybe it's a specific date
+
+ self.definitions[:date].each do |handler|
+ if handler.match(tokens, self.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
+
+ self.definitions[:anchor].each do |handler|
+ if handler.match(tokens, self.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
+
+ self.definitions[:arrow].each do |handler|
+ if handler.match(tokens, self.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
+
+ self.definitions[:narrow].each do |handler|
+ if handler.match(tokens, self.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
+
+ #--------------
+
+ 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
+
+ #--------------
+
+ def handle_m_d(month, day, time_tokens, options) #:nodoc:
+ month.start = @now
+ span = month.this(options[:context])
+
+ day_start = Time.local(span.begin.year, span.begin.month, day)
+
+ day_or_time(day_start, time_tokens, options)
+ end
+
+ 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
+
+ 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
+
+ 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(Time.local(year, month), Time.local(next_month_year, next_month_month))
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
+ month = tokens[1].get_tag(RepeaterMonthName).index
+ day = tokens[2].get_tag(ScalarDay).type
+ year = tokens[5].get_tag(ScalarYear).type
+
+ begin
+ day_start = Time.local(year, month, day)
+ day_or_time(day_start, [tokens[3]], options)
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ 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 = Time.local(year, month, day)
+ day_or_time(day_start, time_tokens, options)
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ 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
+
+ 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 = Time.local(year, month, day) #:nodoc:
+ day_or_time(day_start, time_tokens, options)
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ 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
+
+ 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
+
+ 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(Time.local(year, month), Time.local(next_month_year, next_month_month))
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ # anchors
+
+ def handle_r(tokens, options) #:nodoc:
+ dd_tokens = dealias_and_disambiguate_times(tokens, options)
+ self.get_anchor(dd_tokens, options)
+ end
+
+ def handle_r_g_r(tokens, options) #:nodoc:
+ new_tokens = [tokens[1], tokens[0], tokens[2]]
+ self.handle_r(new_tokens, options)
+ end
+
+ # arrows
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ # narrows
+
+ 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
+
+ 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
+
+ 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
+
+ # support methods
+
+ 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
+
+ def get_repeaters(tokens) #:nodoc:
+ repeaters = []
+ tokens.each do |token|
+ if t = token.get_tag(Repeater)
+ repeaters << t
+ end
+ end
+ repeaters.sort.reverse
+ end
+
+ # Recursively finds repeaters within other repeaters.
+ # Returns a Span representing the innermost time span
+ # or nil if no repeater union could be found
+ 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
+
+ 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
+
+ end
+
+ class Handler #:nodoc:
+ attr_accessor :pattern, :handler_method
+
+ def initialize(pattern, handler_method)
+ @pattern = pattern
+ @handler_method = handler_method
+ end
+
+ def constantize(name)
+ camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
+ ::Chronic.module_eval(camel, __FILE__, __LINE__)
+ end
+
+ def match(tokens, definitions)
+ token_index = 0
+ @pattern.each do |element|
+ name = element.to_s
+ optional = name.reverse[0..0] == '?'
+ name = name.chop if optional
+ if element.instance_of? Symbol
+ klass = constantize(name)
+ match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
+ return false if !match && !optional
+ (token_index += 1; next) if match
+ next if !match && optional
+ elsif element.instance_of? String
+ return true if optional && token_index == tokens.size
+ sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
+ sub_handlers.each do |sub_handler|
+ return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
+ end
+ return false
+ else
+ raise(ChronicPain, "Invalid match type: #{element.class}")
+ end
+ end
+ return false if token_index != tokens.size
+ return true
+ end
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Chronic
+
+ class Ordinal < Tag #:nodoc:
+ def self.scan(tokens)
+ # for each token
+ tokens.each_index do |i|
+ if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
+ if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
+ end
+ tokens
+ end
+
+ def self.scan_for_ordinals(token)
+ if token.word =~ /^(\d*)(st|nd|rd|th)$/
+ return Ordinal.new($1.to_i)
+ end
+ return nil
+ end
+
+ def self.scan_for_days(token)
+ if token.word =~ /^(\d*)(st|nd|rd|th)$/
+ unless $1.to_i > 31
+ return OrdinalDay.new(token.word.to_i)
+ end
+ end
+ return nil
+ end
+
+ def to_s
+ 'ordinal'
+ end
+ end
+
+ class OrdinalDay < Ordinal #:nodoc:
+ def to_s
+ super << '-day-' << @type.to_s
+ end
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Chronic
+
+ class Pointer < Tag #:nodoc:
+ def self.scan(tokens)
+ # for each token
+ tokens.each_index do |i|
+ if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
+ end
+ tokens
+ end
+
+ def self.scan_for_all(token)
+ scanner = {/\bpast\b/ => :past,
+ /\bfuture\b/ => :future,
+ /\bin\b/ => :future}
+ scanner.keys.each do |scanner_item|
+ return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def to_s
+ 'pointer-' << @type.to_s
+ end
+ end
+
+end
\ No newline at end of file
--- /dev/null
+class Chronic::Repeater < Chronic::Tag #:nodoc:
+ def self.scan(tokens, options)
+ # for each token
+ tokens.each_index do |i|
+ if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
+ if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
+ if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
+ if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
+ if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
+ end
+ tokens
+ end
+
+ def self.scan_for_month_names(token)
+ scanner = {/^jan\.?(uary)?$/ => :january,
+ /^feb\.?(ruary)?$/ => :february,
+ /^mar\.?(ch)?$/ => :march,
+ /^apr\.?(il)?$/ => :april,
+ /^may$/ => :may,
+ /^jun\.?e?$/ => :june,
+ /^jul\.?y?$/ => :july,
+ /^aug\.?(ust)?$/ => :august,
+ /^sep\.?(t\.?|tember)?$/ => :september,
+ /^oct\.?(ober)?$/ => :october,
+ /^nov\.?(ember)?$/ => :november,
+ /^dec\.?(ember)?$/ => :december}
+ scanner.keys.each do |scanner_item|
+ return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def self.scan_for_day_names(token)
+ scanner = {/^m[ou]n(day)?$/ => :monday,
+ /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
+ /^tue$/ => :tuesday,
+ /^we(dnes|nds|nns)day$/ => :wednesday,
+ /^wed$/ => :wednesday,
+ /^th(urs|ers)day$/ => :thursday,
+ /^thu$/ => :thursday,
+ /^fr[iy](day)?$/ => :friday,
+ /^sat(t?[ue]rday)?$/ => :saturday,
+ /^su[nm](day)?$/ => :sunday}
+ scanner.keys.each do |scanner_item|
+ return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def self.scan_for_day_portions(token)
+ scanner = {/^ams?$/ => :am,
+ /^pms?$/ => :pm,
+ /^mornings?$/ => :morning,
+ /^afternoons?$/ => :afternoon,
+ /^evenings?$/ => :evening,
+ /^(night|nite)s?$/ => :night}
+ scanner.keys.each do |scanner_item|
+ return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def self.scan_for_times(token, options)
+ if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
+ return Chronic::RepeaterTime.new(token.word, options)
+ end
+ return nil
+ end
+
+ def self.scan_for_units(token)
+ scanner = {/^years?$/ => :year,
+ /^seasons?$/ => :season,
+ /^months?$/ => :month,
+ /^fortnights?$/ => :fortnight,
+ /^weeks?$/ => :week,
+ /^weekends?$/ => :weekend,
+ /^days?$/ => :day,
+ /^hours?$/ => :hour,
+ /^minutes?$/ => :minute,
+ /^seconds?$/ => :second}
+ scanner.keys.each do |scanner_item|
+ if scanner_item =~ token.word
+ klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
+ klass = eval(klass_name)
+ return klass.new(scanner[scanner_item])
+ end
+ end
+ return nil
+ end
+
+ def <=>(other)
+ width <=> other.width
+ end
+
+ # returns the width (in seconds or months) of this repeatable.
+ def width
+ raise("Repeatable#width must be overridden in subclasses")
+ end
+
+ # returns the next occurance of this repeatable.
+ def next(pointer)
+ !@now.nil? || raise("Start point must be set before calling #next")
+ [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
+ #raise("Repeatable#next must be overridden in subclasses")
+ end
+
+ def this(pointer)
+ !@now.nil? || raise("Start point must be set before calling #this")
+ [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
+ end
+
+ def to_s
+ 'repeater'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
+ DAY_SECONDS = 86_400 # (24 * 60 * 60)
+
+ def next(pointer)
+ super
+
+ if !@current_day_start
+ @current_day_start = Time.local(@now.year, @now.month, @now.day)
+ end
+
+ direction = pointer == :future ? 1 : -1
+ @current_day_start += direction * DAY_SECONDS
+
+ Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
+ end
+
+ def this(pointer = :future)
+ super
+
+ case pointer
+ when :future
+ day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+ day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
+ when :past
+ day_begin = Time.construct(@now.year, @now.month, @now.day)
+ day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+ when :none
+ day_begin = Time.construct(@now.year, @now.month, @now.day)
+ day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
+ end
+
+ Chronic::Span.new(day_begin, day_end)
+ end
+
+ def offset(span, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+ span + direction * amount * DAY_SECONDS
+ end
+
+ def width
+ DAY_SECONDS
+ end
+
+ def to_s
+ super << '-day'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
+ DAY_SECONDS = 86400 # (24 * 60 * 60)
+
+ def next(pointer)
+ super
+
+ direction = pointer == :future ? 1 : -1
+
+ if !@current_day_start
+ @current_day_start = Time.construct(@now.year, @now.month, @now.day)
+ @current_day_start += direction * DAY_SECONDS
+
+ day_num = symbol_to_number(@type)
+
+ while @current_day_start.wday != day_num
+ @current_day_start += direction * DAY_SECONDS
+ end
+ else
+ @current_day_start += direction * 7 * DAY_SECONDS
+ end
+
+ Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
+ end
+
+ def this(pointer = :future)
+ super
+
+ pointer = :future if pointer == :none
+ self.next(pointer)
+ end
+
+ def width
+ DAY_SECONDS
+ end
+
+ def to_s
+ super << '-dayname-' << @type.to_s
+ end
+
+ private
+
+ def symbol_to_number(sym)
+ lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
+ lookup[sym] || raise("Invalid symbol specified")
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
+ @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
+ @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
+ @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
+ @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
+
+ def initialize(type)
+ super
+
+ if type.kind_of? Integer
+ @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
+ else
+ lookup = {:am => 0..(12 * 60 * 60 - 1),
+ :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
+ :morning => @@morning,
+ :afternoon => @@afternoon,
+ :evening => @@evening,
+ :night => @@night}
+ @range = lookup[type]
+ lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
+ end
+ @range || raise("Range should have been set by now")
+ end
+
+ def next(pointer)
+ super
+
+ full_day = 60 * 60 * 24
+
+ if !@current_span
+ now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
+ if now_seconds < @range.begin
+ case pointer
+ when :future
+ range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+ when :past
+ range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
+ end
+ elsif now_seconds > @range.end
+ case pointer
+ when :future
+ range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
+ when :past
+ range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+ end
+ else
+ case pointer
+ when :future
+ range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
+ when :past
+ range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
+ end
+ end
+
+ @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
+ else
+ case pointer
+ when :future
+ @current_span += full_day
+ when :past
+ @current_span -= full_day
+ end
+ end
+ end
+
+ def this(context = :future)
+ super
+
+ range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+ @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
+ end
+
+ def offset(span, amount, pointer)
+ @now = span.begin
+ portion_span = self.next(pointer)
+ direction = pointer == :future ? 1 : -1
+ portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
+ end
+
+ def width
+ @range || raise("Range has not been set")
+ return @current_span.width if @current_span
+ if @type.kind_of? Integer
+ return (12 * 60 * 60)
+ else
+ @range.end - @range.begin
+ end
+ end
+
+ def to_s
+ super << '-dayportion-' << @type.to_s
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
+ FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
+
+ def next(pointer)
+ super
+
+ if !@current_fortnight_start
+ case pointer
+ when :future
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = @now
+ next_sunday_span = sunday_repeater.next(:future)
+ @current_fortnight_start = next_sunday_span.begin
+ when :past
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+ 2.times { sunday_repeater.next(:past) }
+ last_sunday_span = sunday_repeater.next(:past)
+ @current_fortnight_start = last_sunday_span.begin
+ end
+ else
+ direction = pointer == :future ? 1 : -1
+ @current_fortnight_start += direction * FORTNIGHT_SECONDS
+ end
+
+ Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
+ end
+
+ def this(pointer = :future)
+ super
+
+ pointer = :future if pointer == :none
+
+ case pointer
+ when :future
+ this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = @now
+ sunday_repeater.this(:future)
+ this_sunday_span = sunday_repeater.this(:future)
+ this_fortnight_end = this_sunday_span.begin
+ Chronic::Span.new(this_fortnight_start, this_fortnight_end)
+ when :past
+ this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = @now
+ last_sunday_span = sunday_repeater.next(:past)
+ this_fortnight_start = last_sunday_span.begin
+ Chronic::Span.new(this_fortnight_start, this_fortnight_end)
+ end
+ end
+
+ def offset(span, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+ span + direction * amount * FORTNIGHT_SECONDS
+ end
+
+ def width
+ FORTNIGHT_SECONDS
+ end
+
+ def to_s
+ super << '-fortnight'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
+ HOUR_SECONDS = 3600 # 60 * 60
+
+ def next(pointer)
+ super
+
+ if !@current_hour_start
+ case pointer
+ when :future
+ @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+ when :past
+ @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
+ end
+ else
+ direction = pointer == :future ? 1 : -1
+ @current_hour_start += direction * HOUR_SECONDS
+ end
+
+ Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
+ end
+
+ def this(pointer = :future)
+ super
+
+ case pointer
+ when :future
+ hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
+ hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+ when :past
+ hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+ hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+ when :none
+ hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+ hour_end = hour_begin + HOUR_SECONDS
+ end
+
+ Chronic::Span.new(hour_start, hour_end)
+ end
+
+ def offset(span, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+ span + direction * amount * HOUR_SECONDS
+ end
+
+ def width
+ HOUR_SECONDS
+ end
+
+ def to_s
+ super << '-hour'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
+ MINUTE_SECONDS = 60
+
+ def next(pointer = :future)
+ super
+
+ if !@current_minute_start
+ case pointer
+ when :future
+ @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
+ when :past
+ @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
+ end
+ else
+ direction = pointer == :future ? 1 : -1
+ @current_minute_start += direction * MINUTE_SECONDS
+ end
+
+ Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
+ end
+
+ def this(pointer = :future)
+ super
+
+ case pointer
+ when :future
+ minute_begin = @now
+ minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+ when :past
+ minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+ minute_end = @now
+ when :none
+ minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+ minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
+ end
+
+ Chronic::Span.new(minute_begin, minute_end)
+ end
+
+ def offset(span, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+ span + direction * amount * MINUTE_SECONDS
+ end
+
+ def width
+ MINUTE_SECONDS
+ end
+
+ def to_s
+ super << '-minute'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
+ MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
+ YEAR_MONTHS = 12
+
+ def next(pointer)
+ super
+
+ if !@current_month_start
+ @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
+ else
+ @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
+ end
+
+ Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
+ end
+
+ def this(pointer = :future)
+ super
+
+ case pointer
+ when :future
+ month_start = Time.construct(@now.year, @now.month, @now.day + 1)
+ month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
+ when :past
+ month_start = Time.construct(@now.year, @now.month)
+ month_end = Time.construct(@now.year, @now.month, @now.day)
+ when :none
+ month_start = Time.construct(@now.year, @now.month)
+ month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
+ end
+
+ Chronic::Span.new(month_start, month_end)
+ end
+
+ def offset(span, amount, pointer)
+ Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
+ end
+
+ def offset_by(time, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+
+ amount_years = direction * amount / YEAR_MONTHS
+ amount_months = direction * amount % YEAR_MONTHS
+
+ new_year = time.year + amount_years
+ new_month = time.month + amount_months
+ if new_month > YEAR_MONTHS
+ new_year += 1
+ new_month -= YEAR_MONTHS
+ end
+ Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
+ end
+
+ def width
+ MONTH_SECONDS
+ end
+
+ def to_s
+ super << '-month'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
+ MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
+
+ def next(pointer)
+ super
+
+ if !@current_month_begin
+ target_month = symbol_to_number(@type)
+ case pointer
+ when :future
+ if @now.month < target_month
+ @current_month_begin = Time.construct(@now.year, target_month)
+ else @now.month > target_month
+ @current_month_begin = Time.construct(@now.year + 1, target_month)
+ end
+ when :none
+ if @now.month <= target_month
+ @current_month_begin = Time.construct(@now.year, target_month)
+ else @now.month > target_month
+ @current_month_begin = Time.construct(@now.year + 1, target_month)
+ end
+ when :past
+ if @now.month > target_month
+ @current_month_begin = Time.construct(@now.year, target_month)
+ else @now.month < target_month
+ @current_month_begin = Time.construct(@now.year - 1, target_month)
+ end
+ end
+ @current_month_begin || raise("Current month should be set by now")
+ else
+ case pointer
+ when :future
+ @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
+ when :past
+ @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
+ end
+ end
+
+ cur_month_year = @current_month_begin.year
+ cur_month_month = @current_month_begin.month
+
+ if cur_month_month == 12
+ next_month_year = cur_month_year + 1
+ next_month_month = 1
+ else
+ next_month_year = cur_month_year
+ next_month_month = cur_month_month + 1
+ end
+
+ Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
+ end
+
+ def this(pointer = :future)
+ super
+
+ case pointer
+ when :past
+ self.next(pointer)
+ when :future, :none
+ self.next(:none)
+ end
+ end
+
+ def width
+ MONTH_SECONDS
+ end
+
+ def index
+ symbol_to_number(@type)
+ end
+
+ def to_s
+ super << '-monthname-' << @type.to_s
+ end
+
+ private
+
+ def symbol_to_number(sym)
+ lookup = {:january => 1,
+ :february => 2,
+ :march => 3,
+ :april => 4,
+ :may => 5,
+ :june => 6,
+ :july => 7,
+ :august => 8,
+ :september => 9,
+ :october => 10,
+ :november => 11,
+ :december => 12}
+ lookup[sym] || raise("Invalid symbol specified")
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
+ SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
+
+ def next(pointer)
+ super
+
+ raise 'Not implemented'
+ end
+
+ def this(pointer = :future)
+ super
+
+ raise 'Not implemented'
+ end
+
+ def width
+ SEASON_SECONDS
+ end
+
+ def to_s
+ super << '-season'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
+ @summer = ['jul 21', 'sep 22']
+ @autumn = ['sep 23', 'dec 21']
+ @winter = ['dec 22', 'mar 19']
+ @spring = ['mar 20', 'jul 20']
+
+ def next(pointer)
+ super
+ raise 'Not implemented'
+ end
+
+ def this(pointer = :future)
+ super
+ raise 'Not implemented'
+ end
+
+ def width
+ (91 * 24 * 60 * 60)
+ end
+
+ def to_s
+ super << '-season-' << @type.to_s
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
+ SECOND_SECONDS = 1 # haha, awesome
+
+ def next(pointer = :future)
+ super
+
+ direction = pointer == :future ? 1 : -1
+
+ if !@second_start
+ @second_start = @now + (direction * SECOND_SECONDS)
+ else
+ @second_start += SECOND_SECONDS * direction
+ end
+
+ Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
+ end
+
+ def this(pointer = :future)
+ super
+
+ Chronic::Span.new(@now, @now + 1)
+ end
+
+ def offset(span, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+ span + direction * amount * SECOND_SECONDS
+ end
+
+ def width
+ SECOND_SECONDS
+ end
+
+ def to_s
+ super << '-second'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
+ class Tick #:nodoc:
+ attr_accessor :time
+
+ def initialize(time, ambiguous = false)
+ @time = time
+ @ambiguous = ambiguous
+ end
+
+ def ambiguous?
+ @ambiguous
+ end
+
+ def *(other)
+ Tick.new(@time * other, @ambiguous)
+ end
+
+ def to_f
+ @time.to_f
+ end
+
+ def to_s
+ @time.to_s + (@ambiguous ? '?' : '')
+ end
+ end
+
+ def initialize(time, options = {})
+ t = time.gsub(/\:/, '')
+ @type =
+ if (1..2) === t.size
+ hours = t.to_i
+ hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
+ elsif t.size == 3
+ Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
+ elsif t.size == 4
+ ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
+ hours = t[0..1].to_i
+ hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
+ elsif t.size == 5
+ Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
+ elsif t.size == 6
+ ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
+ hours = t[0..1].to_i
+ hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
+ else
+ raise("Time cannot exceed six digits")
+ end
+ end
+
+ # Return the next past or future Span for the time that this Repeater represents
+ # pointer - Symbol representing which temporal direction to fetch the next day
+ # must be either :past or :future
+ def next(pointer)
+ super
+
+ half_day = 60 * 60 * 12
+ full_day = 60 * 60 * 24
+
+ first = false
+
+ unless @current_time
+ first = true
+ midnight = Time.local(@now.year, @now.month, @now.day)
+ yesterday_midnight = midnight - full_day
+ tomorrow_midnight = midnight + full_day
+
+ catch :done do
+ if pointer == :future
+ if @type.ambiguous?
+ [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
+ (@current_time = t; throw :done) if t >= @now
+ end
+ else
+ [midnight + @type, tomorrow_midnight + @type].each do |t|
+ (@current_time = t; throw :done) if t >= @now
+ end
+ end
+ else # pointer == :past
+ if @type.ambiguous?
+ [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
+ (@current_time = t; throw :done) if t <= @now
+ end
+ else
+ [midnight + @type, yesterday_midnight + @type].each do |t|
+ (@current_time = t; throw :done) if t <= @now
+ end
+ end
+ end
+ end
+
+ @current_time || raise("Current time cannot be nil at this point")
+ end
+
+ unless first
+ increment = @type.ambiguous? ? half_day : full_day
+ @current_time += pointer == :future ? increment : -increment
+ end
+
+ Chronic::Span.new(@current_time, @current_time + width)
+ end
+
+ def this(context = :future)
+ super
+
+ context = :future if context == :none
+
+ self.next(context)
+ end
+
+ def width
+ 1
+ end
+
+ def to_s
+ super << '-time-' << @type.to_s
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
+ WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
+
+ def next(pointer)
+ super
+
+ if !@current_week_start
+ case pointer
+ when :future
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = @now
+ next_sunday_span = sunday_repeater.next(:future)
+ @current_week_start = next_sunday_span.begin
+ when :past
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+ sunday_repeater.next(:past)
+ last_sunday_span = sunday_repeater.next(:past)
+ @current_week_start = last_sunday_span.begin
+ end
+ else
+ direction = pointer == :future ? 1 : -1
+ @current_week_start += direction * WEEK_SECONDS
+ end
+
+ Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
+ end
+
+ def this(pointer = :future)
+ super
+
+ case pointer
+ when :future
+ this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = @now
+ this_sunday_span = sunday_repeater.this(:future)
+ this_week_end = this_sunday_span.begin
+ Chronic::Span.new(this_week_start, this_week_end)
+ when :past
+ this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = @now
+ last_sunday_span = sunday_repeater.next(:past)
+ this_week_start = last_sunday_span.begin
+ Chronic::Span.new(this_week_start, this_week_end)
+ when :none
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+ sunday_repeater.start = @now
+ last_sunday_span = sunday_repeater.next(:past)
+ this_week_start = last_sunday_span.begin
+ Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
+ end
+ end
+
+ def offset(span, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+ span + direction * amount * WEEK_SECONDS
+ end
+
+ def width
+ WEEK_SECONDS
+ end
+
+ def to_s
+ super << '-week'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
+ WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
+
+ def next(pointer)
+ super
+
+ if !@current_week_start
+ case pointer
+ when :future
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+ saturday_repeater.start = @now
+ next_saturday_span = saturday_repeater.next(:future)
+ @current_week_start = next_saturday_span.begin
+ when :past
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+ saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+ last_saturday_span = saturday_repeater.next(:past)
+ @current_week_start = last_saturday_span.begin
+ end
+ else
+ direction = pointer == :future ? 1 : -1
+ @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
+ end
+
+ Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
+ end
+
+ def this(pointer = :future)
+ super
+
+ case pointer
+ when :future, :none
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+ saturday_repeater.start = @now
+ this_saturday_span = saturday_repeater.this(:future)
+ Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
+ when :past
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+ saturday_repeater.start = @now
+ last_saturday_span = saturday_repeater.this(:past)
+ Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
+ end
+ end
+
+ def offset(span, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+ weekend = Chronic::RepeaterWeekend.new(:weekend)
+ weekend.start = span.begin
+ start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
+ Chronic::Span.new(start, start + (span.end - span.begin))
+ end
+
+ def width
+ WEEKEND_SECONDS
+ end
+
+ def to_s
+ super << '-weekend'
+ end
+end
\ No newline at end of file
--- /dev/null
+class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
+
+ def next(pointer)
+ super
+
+ if !@current_year_start
+ case pointer
+ when :future
+ @current_year_start = Time.construct(@now.year + 1)
+ when :past
+ @current_year_start = Time.construct(@now.year - 1)
+ end
+ else
+ diff = pointer == :future ? 1 : -1
+ @current_year_start = Time.construct(@current_year_start.year + diff)
+ end
+
+ Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
+ end
+
+ def this(pointer = :future)
+ super
+
+ case pointer
+ when :future
+ this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
+ this_year_end = Time.construct(@now.year + 1, 1, 1)
+ when :past
+ this_year_start = Time.construct(@now.year, 1, 1)
+ this_year_end = Time.construct(@now.year, @now.month, @now.day)
+ when :none
+ this_year_start = Time.construct(@now.year, 1, 1)
+ this_year_end = Time.construct(@now.year + 1, 1, 1)
+ end
+
+ Chronic::Span.new(this_year_start, this_year_end)
+ end
+
+ def offset(span, amount, pointer)
+ direction = pointer == :future ? 1 : -1
+
+ sb = span.begin
+ new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
+
+ se = span.end
+ new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
+
+ Chronic::Span.new(new_begin, new_end)
+ end
+
+ def width
+ (365 * 24 * 60 * 60)
+ end
+
+ def to_s
+ super << '-year'
+ end
+end
\ No newline at end of file
--- /dev/null
+module Chronic
+
+ class Scalar < Tag #:nodoc:
+ def self.scan(tokens)
+ # for each token
+ tokens.each_index do |i|
+ if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+ if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+ if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+ if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+ end
+ tokens
+ end
+
+ def self.scan_for_scalars(token, post_token)
+ if token.word =~ /^\d*$/
+ unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
+ return Scalar.new(token.word.to_i)
+ end
+ end
+ return nil
+ end
+
+ def self.scan_for_days(token, post_token)
+ if token.word =~ /^\d\d?$/
+ unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
+ return ScalarDay.new(token.word.to_i)
+ end
+ end
+ return nil
+ end
+
+ def self.scan_for_months(token, post_token)
+ if token.word =~ /^\d\d?$/
+ unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
+ return ScalarMonth.new(token.word.to_i)
+ end
+ end
+ return nil
+ end
+
+ def self.scan_for_years(token, post_token)
+ if token.word =~ /^([1-9]\d)?\d\d?$/
+ unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
+ return ScalarYear.new(token.word.to_i)
+ end
+ end
+ return nil
+ end
+
+ def to_s
+ 'scalar'
+ end
+ end
+
+ class ScalarDay < Scalar #:nodoc:
+ def to_s
+ super << '-day-' << @type.to_s
+ end
+ end
+
+ class ScalarMonth < Scalar #:nodoc:
+ def to_s
+ super << '-month-' << @type.to_s
+ end
+ end
+
+ class ScalarYear < Scalar #:nodoc:
+ def to_s
+ super << '-year-' << @type.to_s
+ end
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Chronic
+
+ class Separator < Tag #:nodoc:
+ def self.scan(tokens)
+ tokens.each_index do |i|
+ if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
+ if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
+ if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
+ if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
+ end
+ tokens
+ end
+
+ def self.scan_for_commas(token)
+ scanner = {/^,$/ => :comma}
+ scanner.keys.each do |scanner_item|
+ return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def self.scan_for_slash_or_dash(token)
+ scanner = {/^-$/ => :dash,
+ /^\/$/ => :slash}
+ scanner.keys.each do |scanner_item|
+ return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def self.scan_for_at(token)
+ scanner = {/^(at|@)$/ => :at}
+ scanner.keys.each do |scanner_item|
+ return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def self.scan_for_in(token)
+ scanner = {/^in$/ => :in}
+ scanner.keys.each do |scanner_item|
+ return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def to_s
+ 'separator'
+ end
+ end
+
+ class SeparatorComma < Separator #:nodoc:
+ def to_s
+ super << '-comma'
+ end
+ end
+
+ class SeparatorSlashOrDash < Separator #:nodoc:
+ def to_s
+ super << '-slashordash-' << @type.to_s
+ end
+ end
+
+ class SeparatorAt < Separator #:nodoc:
+ def to_s
+ super << '-at'
+ end
+ end
+
+ class SeparatorIn < Separator #:nodoc:
+ def to_s
+ super << '-in'
+ end
+ end
+
+end
\ No newline at end of file
--- /dev/null
+module Chronic
+ class TimeZone < Tag #:nodoc:
+ def self.scan(tokens)
+ tokens.each_index do |i|
+ if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
+ end
+ tokens
+ end
+
+ def self.scan_for_all(token)
+ scanner = {/[PMCE][DS]T/i => :tz}
+ scanner.keys.each do |scanner_item|
+ return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+ end
+ return nil
+ end
+
+ def to_s
+ 'timezone'
+ end
+ end
+end
\ No newline at end of file
+++ /dev/null
-module Chronic
-
- class Ordinal < Tag #:nodoc:
- def self.scan(tokens)
- # for each token
- tokens.each_index do |i|
- if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
- if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
- end
- tokens
- end
-
- def self.scan_for_ordinals(token)
- if token.word =~ /^(\d*)(st|nd|rd|th)$/
- return Ordinal.new($1.to_i)
- end
- return nil
- end
-
- def self.scan_for_days(token)
- if token.word =~ /^(\d*)(st|nd|rd|th)$/
- unless $1.to_i > 31
- return OrdinalDay.new(token.word.to_i)
- end
- end
- return nil
- end
-
- def to_s
- 'ordinal'
- end
- end
-
- class OrdinalDay < Ordinal #:nodoc:
- def to_s
- super << '-day-' << @type.to_s
- end
- end
-
-end
\ No newline at end of file
+++ /dev/null
-module Chronic
- class << self
-
- # 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 <tt>:guess</tt>). If no date or time can be found,
- # +nil+ will be returned.
- #
- # Options are:
- #
- # [<tt>:context</tt>]
- # <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
- #
- # If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
- # and if an ambiguous string is given, it will assume it is in the
- # past. Specify <tt>:future</tt> or omit to set a future context.
- #
- # [<tt>:now</tt>]
- # Time (defaults to Time.now)
- #
- # By setting <tt>:now</tt> to a Time, all computations will be based off
- # of that time instead of Time.now
- #
- # [<tt>:guess</tt>]
- # +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 <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
- #
- # [<tt>:ambiguous_time_range</tt>]
- # Integer or <tt>:none</tt> (defaults to <tt>6</tt> (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 <tt>7</tt>, 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 <tt>:none</tt> is given, no assumption
- # will be made, and the first matching instance of that time will
- # be used.
- def parse(text, specified_options = {})
- # get options and set defaults if necessary
- default_options = {:context => :future,
- :now => Time.now,
- :guess => true,
- :ambiguous_time_range => 6}
- options = default_options.merge specified_options
-
- # 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
-
- # 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)
- def pre_normalize(text) #:nodoc:
- normalized_text = text.to_s.downcase
- normalized_text = numericize_numbers(normalized_text)
- normalized_text.gsub!(/['"\.]/, '')
- 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!(/(?=\w)([ap]m|oclock)\b/, ' \1')
- normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
- normalized_text = numericize_ordinals(normalized_text)
- end
-
- # Convert number words to numbers (three => 3)
- def numericize_numbers(text) #:nodoc:
- Numerizer.numerize(text)
- end
-
- # Convert ordinal words to numeric ordinals (third => 3rd)
- def numericize_ordinals(text) #:nodoc:
- text
- end
-
- # Split the text on spaces and convert each word into
- # a Token
- def base_tokenize(text) #:nodoc:
- text.split(' ').map { |word| Token.new(word) }
- end
-
- # Guess a specific time within the given span
- def guess(span) #:nodoc:
- return nil if span.nil?
- if span.width > 1
- span.begin + (span.width / 2)
- else
- span.begin
- end
- end
- end
-
- class Token #:nodoc:
- attr_accessor :word, :tags
-
- def initialize(word)
- @word = word
- @tags = []
- end
-
- # Tag this token with the specified tag
- def tag(new_tag)
- @tags << new_tag
- end
-
- # Remove all tags of the given class
- def untag(tag_class)
- @tags = @tags.select { |m| !m.kind_of? tag_class }
- end
-
- # Return true if this token has any tags
- def tagged?
- @tags.size > 0
- end
-
- # Return the Tag that matches the given class
- def get_tag(tag_class)
- matches = @tags.select { |m| m.kind_of? tag_class }
- #matches.size < 2 || raise("Multiple identical tags found")
- return matches.first
- end
-
- # Print this Token in a pretty way
- def to_s
- @word << '(' << @tags.join(', ') << ') '
- end
- end
-
- # A Span represents a range of time. Since this class extends
- # Range, you can use #begin and #end to get the beginning and
- # ending times of the span (they will be of class Time)
- class Span < Range
- # Returns the width of this span in seconds
- def width
- (self.end - self.begin).to_i
- end
-
- # Add a number of seconds to this span, returning the
- # resulting Span
- def +(seconds)
- Span.new(self.begin + seconds, self.end + seconds)
- end
-
- # Subtract a number of seconds to this span, returning the
- # resulting Span
- def -(seconds)
- self + -seconds
- end
-
- # Prints this span in a nice fashion
- def to_s
- '(' << self.begin.to_s << '..' << self.end.to_s << ')'
- end
- end
-
- # Tokens are tagged with subclassed instances of this class when
- # they match specific criteria
- class Tag #:nodoc:
- attr_accessor :type
-
- def initialize(type)
- @type = type
- end
-
- def start=(s)
- @now = s
- end
- end
-
- # Internal exception
- class ChronicPain < Exception #:nodoc:
-
- end
-
- # This exception is raised if an invalid argument is provided to
- # any of Chronic's methods
- class InvalidArgumentException < Exception
-
- end
-end
\ No newline at end of file
+++ /dev/null
-module Chronic
-
- class Pointer < Tag #:nodoc:
- def self.scan(tokens)
- # for each token
- tokens.each_index do |i|
- if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
- end
- tokens
- end
-
- def self.scan_for_all(token)
- scanner = {/\bpast\b/ => :past,
- /\bfuture\b/ => :future,
- /\bin\b/ => :future}
- scanner.keys.each do |scanner_item|
- return self.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def to_s
- 'pointer-' << @type.to_s
- end
- end
-
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::Repeater < Chronic::Tag #:nodoc:
- def self.scan(tokens, options)
- # for each token
- tokens.each_index do |i|
- if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
- if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
- if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
- if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
- if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
- end
- tokens
- end
-
- def self.scan_for_month_names(token)
- scanner = {/^jan\.?(uary)?$/ => :january,
- /^feb\.?(ruary)?$/ => :february,
- /^mar\.?(ch)?$/ => :march,
- /^apr\.?(il)?$/ => :april,
- /^may$/ => :may,
- /^jun\.?e?$/ => :june,
- /^jul\.?y?$/ => :july,
- /^aug\.?(ust)?$/ => :august,
- /^sep\.?(t\.?|tember)?$/ => :september,
- /^oct\.?(ober)?$/ => :october,
- /^nov\.?(ember)?$/ => :november,
- /^dec\.?(ember)?$/ => :december}
- scanner.keys.each do |scanner_item|
- return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def self.scan_for_day_names(token)
- scanner = {/^m[ou]n(day)?$/ => :monday,
- /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
- /^tue$/ => :tuesday,
- /^we(dnes|nds|nns)day$/ => :wednesday,
- /^wed$/ => :wednesday,
- /^th(urs|ers)day$/ => :thursday,
- /^thu$/ => :thursday,
- /^fr[iy](day)?$/ => :friday,
- /^sat(t?[ue]rday)?$/ => :saturday,
- /^su[nm](day)?$/ => :sunday}
- scanner.keys.each do |scanner_item|
- return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def self.scan_for_day_portions(token)
- scanner = {/^ams?$/ => :am,
- /^pms?$/ => :pm,
- /^mornings?$/ => :morning,
- /^afternoons?$/ => :afternoon,
- /^evenings?$/ => :evening,
- /^(night|nite)s?$/ => :night}
- scanner.keys.each do |scanner_item|
- return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def self.scan_for_times(token, options)
- if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
- return Chronic::RepeaterTime.new(token.word, options)
- end
- return nil
- end
-
- def self.scan_for_units(token)
- scanner = {/^years?$/ => :year,
- /^seasons?$/ => :season,
- /^months?$/ => :month,
- /^fortnights?$/ => :fortnight,
- /^weeks?$/ => :week,
- /^weekends?$/ => :weekend,
- /^days?$/ => :day,
- /^hours?$/ => :hour,
- /^minutes?$/ => :minute,
- /^seconds?$/ => :second}
- scanner.keys.each do |scanner_item|
- if scanner_item =~ token.word
- klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
- klass = eval(klass_name)
- return klass.new(scanner[scanner_item])
- end
- end
- return nil
- end
-
- def <=>(other)
- width <=> other.width
- end
-
- # returns the width (in seconds or months) of this repeatable.
- def width
- raise("Repeatable#width must be overridden in subclasses")
- end
-
- # returns the next occurance of this repeatable.
- def next(pointer)
- !@now.nil? || raise("Start point must be set before calling #next")
- [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
- #raise("Repeatable#next must be overridden in subclasses")
- end
-
- def this(pointer)
- !@now.nil? || raise("Start point must be set before calling #this")
- [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
- end
-
- def to_s
- 'repeater'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
- DAY_SECONDS = 86_400 # (24 * 60 * 60)
-
- def next(pointer)
- super
-
- if !@current_day_start
- @current_day_start = Time.local(@now.year, @now.month, @now.day)
- end
-
- direction = pointer == :future ? 1 : -1
- @current_day_start += direction * DAY_SECONDS
-
- Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
- end
-
- def this(pointer = :future)
- super
-
- case pointer
- when :future
- day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
- day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
- when :past
- day_begin = Time.construct(@now.year, @now.month, @now.day)
- day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
- when :none
- day_begin = Time.construct(@now.year, @now.month, @now.day)
- day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
- end
-
- Chronic::Span.new(day_begin, day_end)
- end
-
- def offset(span, amount, pointer)
- direction = pointer == :future ? 1 : -1
- span + direction * amount * DAY_SECONDS
- end
-
- def width
- DAY_SECONDS
- end
-
- def to_s
- super << '-day'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
- DAY_SECONDS = 86400 # (24 * 60 * 60)
-
- def next(pointer)
- super
-
- direction = pointer == :future ? 1 : -1
-
- if !@current_day_start
- @current_day_start = Time.construct(@now.year, @now.month, @now.day)
- @current_day_start += direction * DAY_SECONDS
-
- day_num = symbol_to_number(@type)
-
- while @current_day_start.wday != day_num
- @current_day_start += direction * DAY_SECONDS
- end
- else
- @current_day_start += direction * 7 * DAY_SECONDS
- end
-
- Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
- end
-
- def this(pointer = :future)
- super
-
- pointer = :future if pointer == :none
- self.next(pointer)
- end
-
- def width
- DAY_SECONDS
- end
-
- def to_s
- super << '-dayname-' << @type.to_s
- end
-
- private
-
- def symbol_to_number(sym)
- lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
- lookup[sym] || raise("Invalid symbol specified")
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
- @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
- @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
- @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
- @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
-
- def initialize(type)
- super
-
- if type.kind_of? Integer
- @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
- else
- lookup = {:am => 0..(12 * 60 * 60 - 1),
- :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
- :morning => @@morning,
- :afternoon => @@afternoon,
- :evening => @@evening,
- :night => @@night}
- @range = lookup[type]
- lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
- end
- @range || raise("Range should have been set by now")
- end
-
- def next(pointer)
- super
-
- full_day = 60 * 60 * 24
-
- if !@current_span
- now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
- if now_seconds < @range.begin
- case pointer
- when :future
- range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
- when :past
- range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
- end
- elsif now_seconds > @range.end
- case pointer
- when :future
- range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
- when :past
- range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
- end
- else
- case pointer
- when :future
- range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
- when :past
- range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
- end
- end
-
- @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
- else
- case pointer
- when :future
- @current_span += full_day
- when :past
- @current_span -= full_day
- end
- end
- end
-
- def this(context = :future)
- super
-
- range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
- @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
- end
-
- def offset(span, amount, pointer)
- @now = span.begin
- portion_span = self.next(pointer)
- direction = pointer == :future ? 1 : -1
- portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
- end
-
- def width
- @range || raise("Range has not been set")
- return @current_span.width if @current_span
- if @type.kind_of? Integer
- return (12 * 60 * 60)
- else
- @range.end - @range.begin
- end
- end
-
- def to_s
- super << '-dayportion-' << @type.to_s
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
- FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
-
- def next(pointer)
- super
-
- if !@current_fortnight_start
- case pointer
- when :future
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = @now
- next_sunday_span = sunday_repeater.next(:future)
- @current_fortnight_start = next_sunday_span.begin
- when :past
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
- 2.times { sunday_repeater.next(:past) }
- last_sunday_span = sunday_repeater.next(:past)
- @current_fortnight_start = last_sunday_span.begin
- end
- else
- direction = pointer == :future ? 1 : -1
- @current_fortnight_start += direction * FORTNIGHT_SECONDS
- end
-
- Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
- end
-
- def this(pointer = :future)
- super
-
- pointer = :future if pointer == :none
-
- case pointer
- when :future
- this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = @now
- sunday_repeater.this(:future)
- this_sunday_span = sunday_repeater.this(:future)
- this_fortnight_end = this_sunday_span.begin
- Chronic::Span.new(this_fortnight_start, this_fortnight_end)
- when :past
- this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = @now
- last_sunday_span = sunday_repeater.next(:past)
- this_fortnight_start = last_sunday_span.begin
- Chronic::Span.new(this_fortnight_start, this_fortnight_end)
- end
- end
-
- def offset(span, amount, pointer)
- direction = pointer == :future ? 1 : -1
- span + direction * amount * FORTNIGHT_SECONDS
- end
-
- def width
- FORTNIGHT_SECONDS
- end
-
- def to_s
- super << '-fortnight'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
- HOUR_SECONDS = 3600 # 60 * 60
-
- def next(pointer)
- super
-
- if !@current_hour_start
- case pointer
- when :future
- @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
- when :past
- @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
- end
- else
- direction = pointer == :future ? 1 : -1
- @current_hour_start += direction * HOUR_SECONDS
- end
-
- Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
- end
-
- def this(pointer = :future)
- super
-
- case pointer
- when :future
- hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
- hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
- when :past
- hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
- hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
- when :none
- hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
- hour_end = hour_begin + HOUR_SECONDS
- end
-
- Chronic::Span.new(hour_start, hour_end)
- end
-
- def offset(span, amount, pointer)
- direction = pointer == :future ? 1 : -1
- span + direction * amount * HOUR_SECONDS
- end
-
- def width
- HOUR_SECONDS
- end
-
- def to_s
- super << '-hour'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
- MINUTE_SECONDS = 60
-
- def next(pointer = :future)
- super
-
- if !@current_minute_start
- case pointer
- when :future
- @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
- when :past
- @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
- end
- else
- direction = pointer == :future ? 1 : -1
- @current_minute_start += direction * MINUTE_SECONDS
- end
-
- Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
- end
-
- def this(pointer = :future)
- super
-
- case pointer
- when :future
- minute_begin = @now
- minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
- when :past
- minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
- minute_end = @now
- when :none
- minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
- minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
- end
-
- Chronic::Span.new(minute_begin, minute_end)
- end
-
- def offset(span, amount, pointer)
- direction = pointer == :future ? 1 : -1
- span + direction * amount * MINUTE_SECONDS
- end
-
- def width
- MINUTE_SECONDS
- end
-
- def to_s
- super << '-minute'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
- MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
- YEAR_MONTHS = 12
-
- def next(pointer)
- super
-
- if !@current_month_start
- @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
- else
- @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
- end
-
- Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
- end
-
- def this(pointer = :future)
- super
-
- case pointer
- when :future
- month_start = Time.construct(@now.year, @now.month, @now.day + 1)
- month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
- when :past
- month_start = Time.construct(@now.year, @now.month)
- month_end = Time.construct(@now.year, @now.month, @now.day)
- when :none
- month_start = Time.construct(@now.year, @now.month)
- month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
- end
-
- Chronic::Span.new(month_start, month_end)
- end
-
- def offset(span, amount, pointer)
- Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
- end
-
- def offset_by(time, amount, pointer)
- direction = pointer == :future ? 1 : -1
-
- amount_years = direction * amount / YEAR_MONTHS
- amount_months = direction * amount % YEAR_MONTHS
-
- new_year = time.year + amount_years
- new_month = time.month + amount_months
- if new_month > YEAR_MONTHS
- new_year += 1
- new_month -= YEAR_MONTHS
- end
- Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
- end
-
- def width
- MONTH_SECONDS
- end
-
- def to_s
- super << '-month'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
- MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
-
- def next(pointer)
- super
-
- if !@current_month_begin
- target_month = symbol_to_number(@type)
- case pointer
- when :future
- if @now.month < target_month
- @current_month_begin = Time.construct(@now.year, target_month)
- else @now.month > target_month
- @current_month_begin = Time.construct(@now.year + 1, target_month)
- end
- when :none
- if @now.month <= target_month
- @current_month_begin = Time.construct(@now.year, target_month)
- else @now.month > target_month
- @current_month_begin = Time.construct(@now.year + 1, target_month)
- end
- when :past
- if @now.month > target_month
- @current_month_begin = Time.construct(@now.year, target_month)
- else @now.month < target_month
- @current_month_begin = Time.construct(@now.year - 1, target_month)
- end
- end
- @current_month_begin || raise("Current month should be set by now")
- else
- case pointer
- when :future
- @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
- when :past
- @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
- end
- end
-
- cur_month_year = @current_month_begin.year
- cur_month_month = @current_month_begin.month
-
- if cur_month_month == 12
- next_month_year = cur_month_year + 1
- next_month_month = 1
- else
- next_month_year = cur_month_year
- next_month_month = cur_month_month + 1
- end
-
- Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
- end
-
- def this(pointer = :future)
- super
-
- case pointer
- when :past
- self.next(pointer)
- when :future, :none
- self.next(:none)
- end
- end
-
- def width
- MONTH_SECONDS
- end
-
- def index
- symbol_to_number(@type)
- end
-
- def to_s
- super << '-monthname-' << @type.to_s
- end
-
- private
-
- def symbol_to_number(sym)
- lookup = {:january => 1,
- :february => 2,
- :march => 3,
- :april => 4,
- :may => 5,
- :june => 6,
- :july => 7,
- :august => 8,
- :september => 9,
- :october => 10,
- :november => 11,
- :december => 12}
- lookup[sym] || raise("Invalid symbol specified")
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
- SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
-
- def next(pointer)
- super
-
- raise 'Not implemented'
- end
-
- def this(pointer = :future)
- super
-
- raise 'Not implemented'
- end
-
- def width
- SEASON_SECONDS
- end
-
- def to_s
- super << '-season'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
- @summer = ['jul 21', 'sep 22']
- @autumn = ['sep 23', 'dec 21']
- @winter = ['dec 22', 'mar 19']
- @spring = ['mar 20', 'jul 20']
-
- def next(pointer)
- super
- raise 'Not implemented'
- end
-
- def this(pointer = :future)
- super
- raise 'Not implemented'
- end
-
- def width
- (91 * 24 * 60 * 60)
- end
-
- def to_s
- super << '-season-' << @type.to_s
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
- SECOND_SECONDS = 1 # haha, awesome
-
- def next(pointer = :future)
- super
-
- direction = pointer == :future ? 1 : -1
-
- if !@second_start
- @second_start = @now + (direction * SECOND_SECONDS)
- else
- @second_start += SECOND_SECONDS * direction
- end
-
- Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
- end
-
- def this(pointer = :future)
- super
-
- Chronic::Span.new(@now, @now + 1)
- end
-
- def offset(span, amount, pointer)
- direction = pointer == :future ? 1 : -1
- span + direction * amount * SECOND_SECONDS
- end
-
- def width
- SECOND_SECONDS
- end
-
- def to_s
- super << '-second'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
- class Tick #:nodoc:
- attr_accessor :time
-
- def initialize(time, ambiguous = false)
- @time = time
- @ambiguous = ambiguous
- end
-
- def ambiguous?
- @ambiguous
- end
-
- def *(other)
- Tick.new(@time * other, @ambiguous)
- end
-
- def to_f
- @time.to_f
- end
-
- def to_s
- @time.to_s + (@ambiguous ? '?' : '')
- end
- end
-
- def initialize(time, options = {})
- t = time.gsub(/\:/, '')
- @type =
- if (1..2) === t.size
- hours = t.to_i
- hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
- elsif t.size == 3
- Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
- elsif t.size == 4
- ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
- hours = t[0..1].to_i
- hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
- elsif t.size == 5
- Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
- elsif t.size == 6
- ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
- hours = t[0..1].to_i
- hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
- else
- raise("Time cannot exceed six digits")
- end
- end
-
- # Return the next past or future Span for the time that this Repeater represents
- # pointer - Symbol representing which temporal direction to fetch the next day
- # must be either :past or :future
- def next(pointer)
- super
-
- half_day = 60 * 60 * 12
- full_day = 60 * 60 * 24
-
- first = false
-
- unless @current_time
- first = true
- midnight = Time.local(@now.year, @now.month, @now.day)
- yesterday_midnight = midnight - full_day
- tomorrow_midnight = midnight + full_day
-
- catch :done do
- if pointer == :future
- if @type.ambiguous?
- [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
- (@current_time = t; throw :done) if t >= @now
- end
- else
- [midnight + @type, tomorrow_midnight + @type].each do |t|
- (@current_time = t; throw :done) if t >= @now
- end
- end
- else # pointer == :past
- if @type.ambiguous?
- [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
- (@current_time = t; throw :done) if t <= @now
- end
- else
- [midnight + @type, yesterday_midnight + @type].each do |t|
- (@current_time = t; throw :done) if t <= @now
- end
- end
- end
- end
-
- @current_time || raise("Current time cannot be nil at this point")
- end
-
- unless first
- increment = @type.ambiguous? ? half_day : full_day
- @current_time += pointer == :future ? increment : -increment
- end
-
- Chronic::Span.new(@current_time, @current_time + width)
- end
-
- def this(context = :future)
- super
-
- context = :future if context == :none
-
- self.next(context)
- end
-
- def width
- 1
- end
-
- def to_s
- super << '-time-' << @type.to_s
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
- WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
-
- def next(pointer)
- super
-
- if !@current_week_start
- case pointer
- when :future
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = @now
- next_sunday_span = sunday_repeater.next(:future)
- @current_week_start = next_sunday_span.begin
- when :past
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
- sunday_repeater.next(:past)
- last_sunday_span = sunday_repeater.next(:past)
- @current_week_start = last_sunday_span.begin
- end
- else
- direction = pointer == :future ? 1 : -1
- @current_week_start += direction * WEEK_SECONDS
- end
-
- Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
- end
-
- def this(pointer = :future)
- super
-
- case pointer
- when :future
- this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = @now
- this_sunday_span = sunday_repeater.this(:future)
- this_week_end = this_sunday_span.begin
- Chronic::Span.new(this_week_start, this_week_end)
- when :past
- this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = @now
- last_sunday_span = sunday_repeater.next(:past)
- this_week_start = last_sunday_span.begin
- Chronic::Span.new(this_week_start, this_week_end)
- when :none
- sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
- sunday_repeater.start = @now
- last_sunday_span = sunday_repeater.next(:past)
- this_week_start = last_sunday_span.begin
- Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
- end
- end
-
- def offset(span, amount, pointer)
- direction = pointer == :future ? 1 : -1
- span + direction * amount * WEEK_SECONDS
- end
-
- def width
- WEEK_SECONDS
- end
-
- def to_s
- super << '-week'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
- WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
-
- def next(pointer)
- super
-
- if !@current_week_start
- case pointer
- when :future
- saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
- saturday_repeater.start = @now
- next_saturday_span = saturday_repeater.next(:future)
- @current_week_start = next_saturday_span.begin
- when :past
- saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
- saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
- last_saturday_span = saturday_repeater.next(:past)
- @current_week_start = last_saturday_span.begin
- end
- else
- direction = pointer == :future ? 1 : -1
- @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
- end
-
- Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
- end
-
- def this(pointer = :future)
- super
-
- case pointer
- when :future, :none
- saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
- saturday_repeater.start = @now
- this_saturday_span = saturday_repeater.this(:future)
- Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
- when :past
- saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
- saturday_repeater.start = @now
- last_saturday_span = saturday_repeater.this(:past)
- Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
- end
- end
-
- def offset(span, amount, pointer)
- direction = pointer == :future ? 1 : -1
- weekend = Chronic::RepeaterWeekend.new(:weekend)
- weekend.start = span.begin
- start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
- Chronic::Span.new(start, start + (span.end - span.begin))
- end
-
- def width
- WEEKEND_SECONDS
- end
-
- def to_s
- super << '-weekend'
- end
-end
\ No newline at end of file
+++ /dev/null
-class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
-
- def next(pointer)
- super
-
- if !@current_year_start
- case pointer
- when :future
- @current_year_start = Time.construct(@now.year + 1)
- when :past
- @current_year_start = Time.construct(@now.year - 1)
- end
- else
- diff = pointer == :future ? 1 : -1
- @current_year_start = Time.construct(@current_year_start.year + diff)
- end
-
- Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
- end
-
- def this(pointer = :future)
- super
-
- case pointer
- when :future
- this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
- this_year_end = Time.construct(@now.year + 1, 1, 1)
- when :past
- this_year_start = Time.construct(@now.year, 1, 1)
- this_year_end = Time.construct(@now.year, @now.month, @now.day)
- when :none
- this_year_start = Time.construct(@now.year, 1, 1)
- this_year_end = Time.construct(@now.year + 1, 1, 1)
- end
-
- Chronic::Span.new(this_year_start, this_year_end)
- end
-
- def offset(span, amount, pointer)
- direction = pointer == :future ? 1 : -1
-
- sb = span.begin
- new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
-
- se = span.end
- new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
-
- Chronic::Span.new(new_begin, new_end)
- end
-
- def width
- (365 * 24 * 60 * 60)
- end
-
- def to_s
- super << '-year'
- end
-end
\ No newline at end of file
+++ /dev/null
-module Chronic
-
- class Scalar < Tag #:nodoc:
- def self.scan(tokens)
- # for each token
- tokens.each_index do |i|
- if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
- if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
- if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
- if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
- end
- tokens
- end
-
- def self.scan_for_scalars(token, post_token)
- if token.word =~ /^\d*$/
- unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
- return Scalar.new(token.word.to_i)
- end
- end
- return nil
- end
-
- def self.scan_for_days(token, post_token)
- if token.word =~ /^\d\d?$/
- unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
- return ScalarDay.new(token.word.to_i)
- end
- end
- return nil
- end
-
- def self.scan_for_months(token, post_token)
- if token.word =~ /^\d\d?$/
- unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
- return ScalarMonth.new(token.word.to_i)
- end
- end
- return nil
- end
-
- def self.scan_for_years(token, post_token)
- if token.word =~ /^([1-9]\d)?\d\d?$/
- unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
- return ScalarYear.new(token.word.to_i)
- end
- end
- return nil
- end
-
- def to_s
- 'scalar'
- end
- end
-
- class ScalarDay < Scalar #:nodoc:
- def to_s
- super << '-day-' << @type.to_s
- end
- end
-
- class ScalarMonth < Scalar #:nodoc:
- def to_s
- super << '-month-' << @type.to_s
- end
- end
-
- class ScalarYear < Scalar #:nodoc:
- def to_s
- super << '-year-' << @type.to_s
- end
- end
-
-end
\ No newline at end of file
+++ /dev/null
-module Chronic
-
- class Separator < Tag #:nodoc:
- def self.scan(tokens)
- tokens.each_index do |i|
- if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
- if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
- if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
- if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
- end
- tokens
- end
-
- def self.scan_for_commas(token)
- scanner = {/^,$/ => :comma}
- scanner.keys.each do |scanner_item|
- return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def self.scan_for_slash_or_dash(token)
- scanner = {/^-$/ => :dash,
- /^\/$/ => :slash}
- scanner.keys.each do |scanner_item|
- return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def self.scan_for_at(token)
- scanner = {/^(at|@)$/ => :at}
- scanner.keys.each do |scanner_item|
- return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def self.scan_for_in(token)
- scanner = {/^in$/ => :in}
- scanner.keys.each do |scanner_item|
- return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def to_s
- 'separator'
- end
- end
-
- class SeparatorComma < Separator #:nodoc:
- def to_s
- super << '-comma'
- end
- end
-
- class SeparatorSlashOrDash < Separator #:nodoc:
- def to_s
- super << '-slashordash-' << @type.to_s
- end
- end
-
- class SeparatorAt < Separator #:nodoc:
- def to_s
- super << '-at'
- end
- end
-
- class SeparatorIn < Separator #:nodoc:
- def to_s
- super << '-in'
- end
- end
-
-end
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Tokens are tagged with subclassed instances of this class when
+ * they match specific criteria
+ */
+class Horde_Date_Parser_Tag
+{
+ public $type;
+ public $now;
+
+ public function __construct($type)
+ {
+ $this->type = $type;
+ }
+
+ public function start($s)
+ {
+ $this->now = $s;
+ }
+
+}
+++ /dev/null
-module Chronic
- class TimeZone < Tag #:nodoc:
- def self.scan(tokens)
- tokens.each_index do |i|
- if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
- end
- tokens
- end
-
- def self.scan_for_all(token)
- scanner = {/[PMCE][DS]T/i => :tz}
- scanner.keys.each do |scanner_item|
- return self.new(scanner[scanner_item]) if scanner_item =~ token.word
- end
- return nil
- end
-
- def to_s
- 'timezone'
- end
- end
-end
\ No newline at end of file
--- /dev/null
+<?php
+class Horde_Date_Parser_Token
+{
+ public $word;
+ public $tags;
+
+ public function __construct($word)
+ {
+ $this->word = $word;
+ $this->tags = array();
+ }
+
+ /**
+ * Tag this token with the specified tag
+ */
+ public function tag($new_tag)
+ {
+ $this->tags[] = $new_tag;
+ }
+
+ /**
+ * Remove all tags of the given class
+ */
+ public function untag($tag_class)
+ {
+ $this->tags = array_filter($this->tags, create_function('$t', 'return $t instanceof ' . $tag_class));
+ }
+
+ /**
+ * Return true if this token has any tags
+ */
+ public function tagged()
+ {
+ return count($this->tags) > 0;
+ }
+
+ /**
+ * Return the Tag that matches the given class
+ */
+ public function getTag($tag_class)
+ {
+ $matches = array_filter($this->tags, create_function('$t', 'return $t instanceof ' . $tag_class));
+ return array_shift($matches);
+ }
+
+ /**
+ * Print this Token in a pretty way
+ */
+ public function __toString()
+ {
+ return '(' . implode(', ', $this->tags) . ') ';
+ }
+
+}
--- /dev/null
+ # A Span represents a range of time. Since this class extends
+ # Range, you can use #begin and #end to get the beginning and
+ # ending times of the span (they will be of class Time)
+ class Span < Range
+ # Returns the width of this span in seconds
+ def width
+ (self.end - self.begin).to_i
+ end
+
+ # Add a number of seconds to this span, returning the
+ # resulting Span
+ def +(seconds)
+ Span.new(self.begin + seconds, self.end + seconds)
+ end
+
+ # Subtract a number of seconds to this span, returning the
+ # resulting Span
+ def -(seconds)
+ self + -seconds
+ end
+
+ # Prints this span in a nice fashion
+ def to_s
+ '(' << self.begin.to_s << '..' << self.end.to_s << ')'
+ end
+ end
+