From: Chuck Hagenbuch Date: Tue, 16 Dec 2008 05:22:16 +0000 (-0500) Subject: start adding base classes for date_parser and break out the locale-specific classes X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=84d643347c81534132daa5514ae9657595e93a80;p=horde.git start adding base classes for date_parser and break out the locale-specific classes --- diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser.php index 477240d97..75777a2bd 100644 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser.php +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser.php @@ -1,53 +1,29 @@ += 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 +} diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php new file mode 100644 index 000000000..c6a8ac0f7 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php @@ -0,0 +1,4 @@ + :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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Handlers.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Handlers.php deleted file mode 100644 index 551d632fa..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Handlers.php +++ /dev/null @@ -1,469 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php new file mode 100644 index 000000000..436250c91 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php @@ -0,0 +1,152 @@ +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 :guess). If no date or time can be found, + # +nil+ will be returned. + # + # Options are: + # + # [:context] + # :past or :future (defaults to :future) + # + # If your string represents a birthday, you can set :context to :past + # and if an ambiguous string is given, it will assume it is in the + # past. Specify :future or omit to set a future context. + # + # [:now] + # Time (defaults to Time.now) + # + # By setting :now to a Time, all computations will be based off + # of that time instead of Time.now + # + # [:guess] + # +true+ or +false+ (defaults to +true+) + # + # By default, the parser will guess a single point in time for the + # given date or time. If you'd rather have the entire time span returned, + # set :guess to +false+ and a Chronic::Span will be returned. + # + # [:ambiguous_time_range] + # Integer or :none (defaults to 6 (6am-6pm)) + # + # If an Integer is given, ambiguous times (like 5:00) will be + # assumed to be within the range of that time in the AM to that time + # in the PM. For example, if you set it to 7, then the parser will + # look for the time between 7am and 7pm. In the case of 5:00, it would + # assume that means 5:00pm. If :none is given, no assumption + # will be made, and the first matching instance of that time will + # be used. + 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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php new file mode 100644 index 000000000..4162a260b --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php @@ -0,0 +1,26 @@ +#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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php new file mode 100644 index 000000000..551d632fa --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php @@ -0,0 +1,469 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php new file mode 100644 index 000000000..45b8148e4 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php @@ -0,0 +1,40 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php new file mode 100644 index 000000000..224efaf96 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php @@ -0,0 +1,27 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php new file mode 100644 index 000000000..9f80daf2f --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php @@ -0,0 +1,115 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php new file mode 100644 index 000000000..a92d83f63 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php @@ -0,0 +1,47 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php new file mode 100644 index 000000000..0486a4ddf --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php @@ -0,0 +1,46 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php new file mode 100644 index 000000000..c854933ad --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php @@ -0,0 +1,93 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php new file mode 100644 index 000000000..058fbb904 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php @@ -0,0 +1,65 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php new file mode 100644 index 000000000..f38a3f825 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php @@ -0,0 +1,52 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php new file mode 100644 index 000000000..342d3cd41 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php @@ -0,0 +1,52 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php new file mode 100644 index 000000000..edd89eeb2 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php @@ -0,0 +1,61 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php new file mode 100644 index 000000000..1f8b748a9 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php @@ -0,0 +1,93 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php new file mode 100644 index 000000000..a255865fb --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php @@ -0,0 +1,23 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php new file mode 100644 index 000000000..adfd1f281 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php @@ -0,0 +1,24 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php new file mode 100644 index 000000000..6d05545ca --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php @@ -0,0 +1,36 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php new file mode 100644 index 000000000..f8560141c --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php @@ -0,0 +1,117 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php new file mode 100644 index 000000000..ec88ff14b --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php @@ -0,0 +1,68 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php new file mode 100644 index 000000000..f012267d9 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php @@ -0,0 +1,60 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php new file mode 100644 index 000000000..426371f9b --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php @@ -0,0 +1,58 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php new file mode 100644 index 000000000..b08cfee18 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php @@ -0,0 +1,74 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php new file mode 100644 index 000000000..86c56e33b --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php @@ -0,0 +1,76 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php new file mode 100644 index 000000000..41041ef47 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php @@ -0,0 +1,22 @@ +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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Ordinal.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Ordinal.php deleted file mode 100644 index 45b8148e4..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Ordinal.php +++ /dev/null @@ -1,40 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Parser.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Parser.php deleted file mode 100644 index 5e7779f63..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Parser.php +++ /dev/null @@ -1,239 +0,0 @@ -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 :guess). If no date or time can be found, - # +nil+ will be returned. - # - # Options are: - # - # [:context] - # :past or :future (defaults to :future) - # - # If your string represents a birthday, you can set :context to :past - # and if an ambiguous string is given, it will assume it is in the - # past. Specify :future or omit to set a future context. - # - # [:now] - # Time (defaults to Time.now) - # - # By setting :now to a Time, all computations will be based off - # of that time instead of Time.now - # - # [:guess] - # +true+ or +false+ (defaults to +true+) - # - # By default, the parser will guess a single point in time for the - # given date or time. If you'd rather have the entire time span returned, - # set :guess to +false+ and a Chronic::Span will be returned. - # - # [:ambiguous_time_range] - # Integer or :none (defaults to 6 (6am-6pm)) - # - # If an Integer is given, ambiguous times (like 5:00) will be - # assumed to be within the range of that time in the AM to that time - # in the PM. For example, if you set it to 7, then the parser will - # look for the time between 7am and 7pm. In the case of 5:00, it would - # assume that means 5:00pm. If :none is given, no assumption - # will be made, and the first matching instance of that time will - # be used. - 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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Pointer.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Pointer.php deleted file mode 100644 index 224efaf96..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Pointer.php +++ /dev/null @@ -1,27 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeater.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeater.php deleted file mode 100644 index 9f80daf2f..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeater.php +++ /dev/null @@ -1,115 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Day.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Day.php deleted file mode 100644 index a92d83f63..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Day.php +++ /dev/null @@ -1,47 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayName.php deleted file mode 100644 index 0486a4ddf..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayName.php +++ /dev/null @@ -1,46 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayPortion.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayPortion.php deleted file mode 100644 index c854933ad..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayPortion.php +++ /dev/null @@ -1,93 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Fortnight.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Fortnight.php deleted file mode 100644 index 058fbb904..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Fortnight.php +++ /dev/null @@ -1,65 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Hour.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Hour.php deleted file mode 100644 index f38a3f825..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Hour.php +++ /dev/null @@ -1,52 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Minute.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Minute.php deleted file mode 100644 index 342d3cd41..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Minute.php +++ /dev/null @@ -1,52 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Month.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Month.php deleted file mode 100644 index edd89eeb2..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Month.php +++ /dev/null @@ -1,61 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/MonthName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/MonthName.php deleted file mode 100644 index 1f8b748a9..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/MonthName.php +++ /dev/null @@ -1,93 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Season.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Season.php deleted file mode 100644 index a255865fb..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Season.php +++ /dev/null @@ -1,23 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/SeasonName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/SeasonName.php deleted file mode 100644 index adfd1f281..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/SeasonName.php +++ /dev/null @@ -1,24 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Second.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Second.php deleted file mode 100644 index 6d05545ca..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Second.php +++ /dev/null @@ -1,36 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Time.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Time.php deleted file mode 100644 index f8560141c..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Time.php +++ /dev/null @@ -1,117 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Week.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Week.php deleted file mode 100644 index ec88ff14b..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Week.php +++ /dev/null @@ -1,68 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Weekend.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Weekend.php deleted file mode 100644 index f012267d9..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Weekend.php +++ /dev/null @@ -1,60 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Year.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Year.php deleted file mode 100644 index 426371f9b..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Year.php +++ /dev/null @@ -1,58 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Scalar.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Scalar.php deleted file mode 100644 index b08cfee18..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Scalar.php +++ /dev/null @@ -1,74 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Separator.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Separator.php deleted file mode 100644 index 86c56e33b..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Separator.php +++ /dev/null @@ -1,76 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php new file mode 100644 index 000000000..a0796408a --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php @@ -0,0 +1,21 @@ +type = $type; + } + + public function start($s) + { + $this->now = $s; + } + +} diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/TimeZone.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/TimeZone.php deleted file mode 100644 index 41041ef47..000000000 --- a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/TimeZone.php +++ /dev/null @@ -1,22 +0,0 @@ -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 diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php new file mode 100644 index 000000000..16b3b70a5 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php @@ -0,0 +1,54 @@ +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) . ') '; + } + +} diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Span.php b/framework/Horde_Date_Parser/lib/Horde/Date/Span.php new file mode 100644 index 000000000..d61c24cc0 --- /dev/null +++ b/framework/Horde_Date_Parser/lib/Horde/Date/Span.php @@ -0,0 +1,27 @@ + # 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 +