diff --git a/tracks/app/controllers/application.rb b/tracks/app/controllers/application.rb index 984d38e0..e2858cb8 100644 --- a/tracks/app/controllers/application.rb +++ b/tracks/app/controllers/application.rb @@ -83,7 +83,7 @@ class ApplicationController < ActionController::Base def parse_date_per_user_prefs( s ) return nil if s == '' - Date.strptime(s, @user.preference.date_format) + Chronic.parse(s) end def init_data_for_sidebar diff --git a/tracks/app/controllers/mobile_controller.rb b/tracks/app/controllers/mobile_controller.rb index 6377a5ac..050ff071 100644 --- a/tracks/app/controllers/mobile_controller.rb +++ b/tracks/app/controllers/mobile_controller.rb @@ -91,7 +91,7 @@ class MobileController < ApplicationController @contexts = Context.find :all, :order => 'position ASC', :conditions => ['user_id = ?', @user.id] @projects = Project.find :all, :order => 'position ASC', - :conditions => ['user_id = ? and done = ?', @user.id, false] + :conditions => ['user_id = ? and state = ?', @user.id, "active"] @all_todos = Todo.find(:all, :conditions => ['user_id = ? and type = ?', @user.id, "Immediate"]) end diff --git a/tracks/app/controllers/todo_controller.rb b/tracks/app/controllers/todo_controller.rb index 2b6a12c5..e4efbb7a 100644 --- a/tracks/app/controllers/todo_controller.rb +++ b/tracks/app/controllers/todo_controller.rb @@ -47,6 +47,18 @@ class TodoController < ApplicationController end end + def date_preview + return if params["todo_due"].blank? + @date = parse_date_per_user_prefs(params["todo_due"]) + + if @date.nil? + @form_date = "Invalid date" + else + @form_date = @date.strftime("%a %b %d %Y") + end + render :partial => "shared/date_preview", :layout => false + end + def create init @item = @user.todos.build @@ -54,7 +66,8 @@ class TodoController < ApplicationController @item.attributes = p if @item.due? - @item.due = parse_date_per_user_prefs(p["due"]) + @date = parse_date_per_user_prefs(p["due"]) + @item.due = @date.to_s(:db) else @item.due = "" end diff --git a/tracks/app/models/todo.rb b/tracks/app/models/todo.rb index be206ce5..5201ecf0 100644 --- a/tracks/app/models/todo.rb +++ b/tracks/app/models/todo.rb @@ -1,4 +1,5 @@ class Todo < ActiveRecord::Base + require 'validations' belongs_to :context, :order => 'name' belongs_to :project @@ -11,6 +12,7 @@ class Todo < ActiveRecord::Base validates_presence_of :description validates_length_of :description, :maximum => 100 validates_length_of :notes, :maximum => 60000, :allow_nil => true + # validates_chronic_date :due, :allow_nil => true def self.not_done( id=id ) self.find(:all, :conditions =>[ "done = ? AND context_id = ?", false, id], :order =>"due IS NULL, due ASC, created_at ASC") diff --git a/tracks/app/views/shared/_add_new_item_form.rhtml b/tracks/app/views/shared/_add_new_item_form.rhtml index d387d4a0..038c3303 100644 --- a/tracks/app/views/shared/_add_new_item_form.rhtml +++ b/tracks/app/views/shared/_add_new_item_form.rhtml @@ -63,10 +63,19 @@

<%= text_field("todo", "show_from", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5, "autocomplete" => "off") %>
<% end -%> +

<%= end_form_tag %> + +<%= observe_field "todo_due", + :frequency => 1, + :update => "date-preview", + :with => "todo_due", + :complete => "Element.show('date-preview')", + :url => { :action => "date_preview" } %> + <%= calendar_setup( "todo_due" ) %> <% if controller.controller_name == "deferred" -%> <%= calendar_setup( "todo_show_from" ) %> diff --git a/tracks/app/views/shared/_date_preview.rhtml b/tracks/app/views/shared/_date_preview.rhtml new file mode 100644 index 00000000..3dbbb4c8 --- /dev/null +++ b/tracks/app/views/shared/_date_preview.rhtml @@ -0,0 +1 @@ +<%= @form_date %> \ No newline at end of file diff --git a/tracks/app/views/todo/_edit_form.rhtml b/tracks/app/views/todo/_edit_form.rhtml index 7c27e7e5..edd14b2f 100644 --- a/tracks/app/views/todo/_edit_form.rhtml +++ b/tracks/app/views/todo/_edit_form.rhtml @@ -60,6 +60,7 @@ Cancel + <%= calendar_setup( "due_#{@item.id}" ) %> <% if @item.class == Deferred -%> <%= calendar_setup( "show_from_#{@item.id}" ) %> diff --git a/tracks/app/views/todo/date_preview.rhtml b/tracks/app/views/todo/date_preview.rhtml new file mode 100644 index 00000000..e69de29b diff --git a/tracks/lib/validations.rb b/tracks/lib/validations.rb new file mode 100644 index 00000000..bedc914c --- /dev/null +++ b/tracks/lib/validations.rb @@ -0,0 +1,28 @@ +ActiveRecord::Validations::ClassMethods.class_eval do +# Custom validations + +# Validating a date field parsed by Chronic. If Chronic cannot parse the +# date, it returns nil +# Adapted from Stuart Rackham's custom date validation +# http://www.bigbold.com/snippets/posts/show/1548 +# +def validates_chronic_date(*attr_names) + configuration = + { :message => 'is an invalid date. Here are some valid examples: feb 23, 23 feb 06, 6 feb 2006, 2006-02-23, tomorrow, today, 5 days (hence), 1 month hence, etc.)', + :on => :save, + } + configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) + # Don't let validates_each handle allow_nils, it checks the cast value. + allow_nil = configuration.delete(:allow_nil) + validates_each(attr_names, configuration) do |record, attr_name, value| + before_cast = record.send("#{attr_name}_before_type_cast") + next if allow_nil and (before_cast == '') + begin + date = Chronic.parse(before_cast.to_s).nil? + rescue + record.errors.add(attr_name, configuration[:message]) + end + end +end + +end \ No newline at end of file diff --git a/tracks/public/javascripts/calendar-setup.js b/tracks/public/javascripts/calendar-setup.js index 81bcb34b..ca4e1100 100644 --- a/tracks/public/javascripts/calendar-setup.js +++ b/tracks/public/javascripts/calendar-setup.js @@ -213,8 +213,9 @@ Calendar.setup = function (params) { * they still work properly. Pressing '+' when no date is entered in the * field will set the date to tomorrow, and likewise '-' with no date * entered will set the date to yesterday. + * 2006-10-24: Commented out while trying to use Chronic library for the field */ -DateDueKeyboardShortcutSupport = Class.create(); +/*DateDueKeyboardShortcutSupport = Class.create(); DateDueKeyboardShortcutSupport.prototype = { initialize: function(element, dateFormat) { this.element = $(element); @@ -252,6 +253,12 @@ DateDueKeyboardShortcutSupport.prototype = { this.cancel(event); } }, + + setTextBoxToChronicDate : function() { + today = new Date(); + this.setDate(today); + }, + setTextBoxToTodaysDate : function() { today = new Date(); this.setDate(today); @@ -291,4 +298,4 @@ DateDueKeyboardShortcutSupport.prototype = { ((event.which) ? event.which : 0)); return String.fromCharCode(charCode); } -}; +};*/ diff --git a/tracks/vendor/plugins/chronic/README b/tracks/vendor/plugins/chronic/README new file mode 100644 index 00000000..7c3b0ae9 --- /dev/null +++ b/tracks/vendor/plugins/chronic/README @@ -0,0 +1,119 @@ +=Chronic + +Chronic is a natural language date/time parser written in pure Ruby. See below for the wide variety of formats Chronic will parse. + +==Installation + +Chronic can be installed via RubyGems: + + $ sudo gem install chronic + +==Usage + +You can parse strings containing a natural language date using the Chronic.parse method. + + require 'chronic' + + Time.now #=> Sun Aug 27 23:18:25 PDT 2006 + + #--- + + Chronic.parse('tomorrow') + #=> Mon Aug 28 12:00:00 PDT 2006 + + Chronic.parse('monday', :context => :past) + #=> Mon Aug 21 12:00:00 PDT 2006 + + Chronic.parse('this tuesday 5:00') + #=> Tue Aug 29 17:00:00 PDT 2006 + + Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none) + #=> Tue Aug 29 05:00:00 PDT 2006 + + Chronic.parse('may 27th', :now => Time.local(2000, 1, 1)) + #=> Sat May 27 12:00:00 PDT 2000 + + Chronic.parse('may 27th', :guess => false) + #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007 + +See Chronic.parse for detailed usage instructions. + +==Examples + +Chronic can parse a huge variety of date and time formats. Following is a small sample of strings that will be properly parsed. Parsing is case insensitive and will handle common abbreviations and misspellings. + +Simple + + thursday + november + summer + friday 13:00 + mon 2:35 + 4pm + 6 in the morning + friday 1pm + sat 7 in the evening + yesterday + today + tomorrow + this tuesday + next month + last winter + this morning + last night + this second + yesterday at 4:00 + last friday at 20:00 + last week tuesday + tomorrow at 6:45pm + afternoon yesterday + thursday last week + +Complex + + 3 years ago + 5 months before now + 7 hours ago + 7 days from now + 1 week hence + in 3 hours + 1 year ago tomorrow + 3 months ago saturday at 5:00 pm + 7 hours before tomorrow at noon + 3rd wednesday in november + 3rd month next year + 3rd thursday this september + 4th day last week + +Specific Dates + + January 5 + dec 25 + may 27th + October 2006 + oct 06 + jan 3 2010 + february 14, 2004 + 3 jan 2000 + 17 april 85 + 5/27/1979 + 27/5/1979 + 05/06 + 1979-05-27 + Friday + 5 + 4:00 + 17:00 + 0800 + +Specific Times (many of the above with an added time) + + January 5 at 7pm + 1979-05-27 05:00 + etc + +==Limitations + +Chronic uses Ruby's built in Time class for all time storage and computation. Because of this, only times that the Time class can handle will be properly parsed. Parsing for times outside of this range will simply return nil. Support for a wider range of times is planned for a future release. + +Time zones other than the local one are not currently supported. Support for other time zones is planned for a future release. \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/init.rb b/tracks/vendor/plugins/chronic/init.rb new file mode 100644 index 00000000..361bc7a3 --- /dev/null +++ b/tracks/vendor/plugins/chronic/init.rb @@ -0,0 +1,5 @@ +require 'chronic' + +ActiveRecord::Base.class_eval do + include Chronic +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/lib/chronic.rb b/tracks/vendor/plugins/chronic/lib/chronic.rb new file mode 100644 index 00000000..4ccdb757 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic.rb @@ -0,0 +1,41 @@ +#============================================================================= +# +# Name: Chronic +# Author: Tom Preston-Werner +# Purpose: Parse natural language dates and times into Time or +# Chronic::Span objects +# +#============================================================================= + +require 'chronic/chronic' +require 'chronic/handlers' + +require 'chronic/repeater' +require 'chronic/repeaters/repeater_year' +require 'chronic/repeaters/repeater_season' +require 'chronic/repeaters/repeater_season_name' +require 'chronic/repeaters/repeater_month' +require 'chronic/repeaters/repeater_month_name' +require 'chronic/repeaters/repeater_fortnight' +require 'chronic/repeaters/repeater_week' +require 'chronic/repeaters/repeater_weekend' +require 'chronic/repeaters/repeater_day' +require 'chronic/repeaters/repeater_day_name' +require 'chronic/repeaters/repeater_day_portion' +require 'chronic/repeaters/repeater_hour' +require 'chronic/repeaters/repeater_minute' +require 'chronic/repeaters/repeater_second' +require 'chronic/repeaters/repeater_time' + +require 'chronic/grabber' +require 'chronic/pointer' +require 'chronic/scalar' +require 'chronic/ordinal' +require 'chronic/separator' + +module Chronic + def self.debug=(val); @debug = val; end +end + +Chronic.debug = false + diff --git a/tracks/vendor/plugins/chronic/lib/chronic/chronic.rb b/tracks/vendor/plugins/chronic/lib/chronic/chronic.rb new file mode 100644 index 00000000..0fac5151 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/chronic.rb @@ -0,0 +1,242 @@ +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].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].each do |tokenizer| + @tokens = tokenizer.scan(@tokens) + end + + # strip any non-tagged tokens + @tokens = @tokens.select { |token| token.tagged? } + + if @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.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!(/\bfrom now\b/, 'future') + 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.gsub!(/\ba\b/, '1') + normalized_text.gsub!(/\s+/, ' ') + normalized_text = numericize_numbers(normalized_text) + normalized_text = numericize_ordinals(normalized_text) + end + + # Convert number words to numbers (three => 3) + def numericize_numbers(text) #:nodoc: + 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/tracks/vendor/plugins/chronic/lib/chronic/grabber.rb b/tracks/vendor/plugins/chronic/lib/chronic/grabber.rb new file mode 100644 index 00000000..4162a260 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/grabber.rb @@ -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/tracks/vendor/plugins/chronic/lib/chronic/handlers.rb b/tracks/vendor/plugins/chronic/lib/chronic/handlers.rb new file mode 100644 index 00000000..70b5d0ed --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/handlers.rb @@ -0,0 +1,403 @@ +module Chronic + + class << self + + def definitions #:nodoc: + @definitions ||= + {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)], + + :date => [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)], + + :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r), + Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)], + + :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)], + + :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) + 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) + 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) + 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 an narrow + + self.definitions[:narrow].each do |handler| + if handler.match(tokens, self.definitions) + #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! + 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.next(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_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 + + 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: outer_span = head.this(options[:context]) + when :next: outer_span = head.next(:future) + else raise(ChronicPain, "Invalid grabber") + end + + 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: + return span if tags.empty? + + head, *rest = tags + head.start = pointer == :future ? span.begin : span.end + h = head.this(pointer) + + 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 + #ttokens = [] + 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) + t1.untag(RepeaterDayPortion) + t1.tag(RepeaterDayPortion.new(:am)) + elsif [:afternoon, :evening, :night].include?(t1tag.type) + t1.untag(RepeaterDayPortion) + t1.tag(RepeaterDayPortion.new(:pm)) + end + end + end + #tokens = ttokens + + # 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/tracks/vendor/plugins/chronic/lib/chronic/ordinal.rb b/tracks/vendor/plugins/chronic/lib/chronic/ordinal.rb new file mode 100644 index 00000000..45b8148e --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/ordinal.rb @@ -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/tracks/vendor/plugins/chronic/lib/chronic/pointer.rb b/tracks/vendor/plugins/chronic/lib/chronic/pointer.rb new file mode 100644 index 00000000..a4a07df8 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/pointer.rb @@ -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 = {/past/ => :past, + /future/ => :future, + /in/ => :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/tracks/vendor/plugins/chronic/lib/chronic/repeater.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeater.rb new file mode 100644 index 00000000..590f4029 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeater.rb @@ -0,0 +1,114 @@ +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\.?(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, + /^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, + /^nights?$/ => :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})?$/ + 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?$/ => :weekends, + /^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, :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 #next") + [:future, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future") + end + + def to_s + 'repeater' + end +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day.rb new file mode 100644 index 00000000..e90e52a7 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day.rb @@ -0,0 +1,44 @@ +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.local(@now.year, @now.month, @now.day, @now.hour + 1) + day_end = Time.local(@now.year, @now.month, @now.day) + DAY_SECONDS + when :past + day_begin = Time.local(@now.year, @now.month, @now.day) + day_end = Time.local(@now.year, @now.month, @now.day, @now.hour) + 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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day_name.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day_name.rb new file mode 100644 index 00000000..6aba163d --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day_name.rb @@ -0,0 +1,45 @@ +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.local(@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 + + self.next(pointer) + end + + def width + DAY_SECONDS + end + + def to_s + super << '-dayofweek-' << @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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day_portion.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day_portion.rb new file mode 100644 index 00000000..2a2c3fc0 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_day_portion.rb @@ -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 => 1..(12 * 60 * 60), + :pm => (12 * 60 * 60)..(24 * 60 * 60), + :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.local(@now.year, @now.month, @now.day) + if now_seconds < @range.begin + case pointer + when :future + range_start = Time.local(@now.year, @now.month, @now.day) + @range.begin + when :past + range_start = Time.local(@now.year, @now.month, @now.day) - full_day + @range.begin + end + elsif now_seconds > @range.end + case pointer + when :future + range_start = Time.local(@now.year, @now.month, @now.day) + full_day + @range.begin + when :past + range_start = Time.local(@now.year, @now.month, @now.day) + @range.begin + end + else + case pointer + when :future + range_start = Time.local(@now.year, @now.month, @now.day) + full_day + @range.begin + when :past + range_start = Time.local(@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.local(@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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_fortnight.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_fortnight.rb new file mode 100644 index 00000000..e8042956 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_fortnight.rb @@ -0,0 +1,64 @@ +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 + + case pointer + when :future + this_fortnight_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 + 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.local(@now.year, @now.month, @now.day, @now.hour) + sunday_repeater = Chronic::RepeaterDayName.new(:sunday) + sunday_repeater.start = @now + #sunday_repeater.next(:past) + 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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_hour.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_hour.rb new file mode 100644 index 00000000..46029508 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_hour.rb @@ -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.local(@now.year, @now.month, @now.day, @now.hour + 1) + when :past + @current_hour_start = Time.local(@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.local(@now.year, @now.month, @now.day, @now.hour, @now.min + 1) + hour_end = Time.local(@now.year, @now.month, @now.day, @now.hour + 1) + when :past + hour_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + hour_end = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min) + when :none + hour_start = Time.local(@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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_minute.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_minute.rb new file mode 100644 index 00000000..ffe6cbc9 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_minute.rb @@ -0,0 +1,21 @@ +class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc: + MINUTE_SECONDS = 60 + + def this(pointer = :future) + minute_begin = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min) + Chronic::Span.new(minute_begin, minute_begin + MINUTE_SECONDS) + 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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_month.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_month.rb new file mode 100644 index 00000000..96325fe8 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_month.rb @@ -0,0 +1,54 @@ +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.local(@now.year, @now.month), 1, pointer) + else + @current_month_start = offset_by(Time.local(@current_month_start.year, @current_month_start.month), 1, pointer) + end + + Chronic::Span.new(@current_month_start, Time.local(@current_month_start.year, @current_month_start.month + 1)) + end + + def this(pointer = :future) + super + + case pointer + when :future + month_start = Time.local(@now.year, @now.month, @now.day + 1) + month_end = self.offset_by(Time.local(@now.year, @now.month), 1, :future) + when :past + month_start = Time.local(@now.year, @now.month) + month_end = Time.local(@now.year, @now.month, @now.day) + 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.local(new_year, new_month, time.day, time.hour, time.min, time.sec) + end + + def width + MONTH_SECONDS + end +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_month_name.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_month_name.rb new file mode 100644 index 00000000..41af8e31 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_month_name.rb @@ -0,0 +1,82 @@ +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.local(@now.year, target_month) + else @now.month > target_month + @current_month_begin = Time.local(@now.year + 1, target_month) + end + when :past + if @now.month > target_month + @current_month_begin = Time.local(@now.year, target_month) + else @now.month < target_month + @current_month_begin = Time.local(@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.local(@current_month_begin.year + 1, @current_month_begin.month) + when :past + @current_month_begin = Time.local(@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.local(next_month_year, next_month_month)) + end + + def this(pointer = :future) + super + + self.next(pointer) + end + + def width + MONTH_SECONDS + end + + def index + symbol_to_number(@type) + end + + def to_s + super << '-month-' << @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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_season.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_season.rb new file mode 100644 index 00000000..a255865f --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_season.rb @@ -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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_season_name.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_season_name.rb new file mode 100644 index 00000000..adfd1f28 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_season_name.rb @@ -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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_second.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_second.rb new file mode 100644 index 00000000..26860fe6 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_second.rb @@ -0,0 +1,34 @@ +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) + 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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_time.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_time.rb new file mode 100644 index 00000000..b8a6de04 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_time.rb @@ -0,0 +1,106 @@ +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.sub(/\:/, '') + @type = + if (1..2) === t.size + Tick.new(t.to_i * 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 + Tick.new(t[0..1].to_i * 60 * 60 + t[2..3].to_i * 60, ambiguous) + else + raise("Time cannot exceed four 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) + [:future, :past].include?(context) || raise("First argument 'context' must be one of :past or :future") + 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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_week.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_week.rb new file mode 100644 index 00000000..f4e5338d --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_week.rb @@ -0,0 +1,62 @@ +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) + 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/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_weekend.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_weekend.rb new file mode 100644 index 00000000..5761ad42 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_weekend.rb @@ -0,0 +1,11 @@ +class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc: + WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60) + + def width + WEEKEND_SECONDS + end + + def to_s + super << '-weekend' + end +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_year.rb b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_year.rb new file mode 100644 index 00000000..6535d1df --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/repeaters/repeater_year.rb @@ -0,0 +1,55 @@ +class Chronic::RepeaterYear < Chronic::Repeater #:nodoc: + + def next(pointer) + super + + if !@current_year_start + case pointer + when :future + @current_year_start = Time.local(@now.year + 1) + when :past + @current_year_start = Time.local(@now.year - 1) + end + else + diff = pointer == :future ? 1 : -1 + @current_year_start = Time.local(@current_year_start.year + diff) + end + + Chronic::Span.new(@current_year_start, Time.local(@current_year_start.year + 1)) + end + + def this(pointer = :future) + super + + case pointer + when :future + this_year_start = Time.local(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS + this_year_end = Time.local(@now.year + 1, 1, 1) + when :past + this_year_start = Time.local(@now.year, 1, 1) + this_year_end = Time.local(@now.year, @now.month, @now.day) + 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.local(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec) + + se = span.end + new_end = Time.local(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/tracks/vendor/plugins/chronic/lib/chronic/scalar.rb b/tracks/vendor/plugins/chronic/lib/chronic/scalar.rb new file mode 100644 index 00000000..f7189afe --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/scalar.rb @@ -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 =~ /^\d\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/tracks/vendor/plugins/chronic/lib/chronic/separator.rb b/tracks/vendor/plugins/chronic/lib/chronic/separator.rb new file mode 100644 index 00000000..86c56e33 --- /dev/null +++ b/tracks/vendor/plugins/chronic/lib/chronic/separator.rb @@ -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/tracks/vendor/plugins/chronic/test/parse_numbers.rb b/tracks/vendor/plugins/chronic/test/parse_numbers.rb new file mode 100644 index 00000000..fe858c7a --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/parse_numbers.rb @@ -0,0 +1,50 @@ +__END__ + +require 'test/unit' + +class ParseNumbersTest < Test::Unit::TestCase + + def test_parse + strings = {1 => 'one', + 5 => 'five', + 10 => 'ten', + 11 => 'eleven', + 12 => 'twelve', + 13 => 'thirteen', + 14 => 'fourteen', + 15 => 'fifteen', + 16 => 'sixteen', + 17 => 'seventeen', + 18 => 'eighteen', + 19 => 'nineteen', + 20 => 'twenty', + 27 => 'twenty seven', + 31 => 'thirty-one', + 59 => 'fifty nine', + 100 => 'a hundred', + 100 => 'one hundred', + 150 => 'one hundred and fifty', + 150 => 'one fifty', + 200 => 'two-hundred', + 500 => '5 hundred', + 999 => 'nine hundred and ninety nine', + 1_000 => 'one thousand', + 1_200 => 'twelve hundred', + 1_200 => 'one thousand two hundred', + 17_000 => 'seventeen thousand', + 21_473 => 'twentyone-thousand-four-hundred-and-seventy-three', + 74_002 => 'seventy four thousand and two', + 99_999 => 'ninety nine thousand nine hundred ninety nine', + 100_000 => '100 thousand', + 250_000 => 'two hundred fifty thousand', + 1_000_000 => 'one million', + 1_250_007 => 'one million two hundred fifty thousand and seven', + 1_000_000_000 => 'one billion', + 1_000_000_001 => 'one billion and one'} + + strings.keys.sort.each do |key| + assert_equal key, JW::NumberMagick.convert(strings[key]) + end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/suite.rb b/tracks/vendor/plugins/chronic/test/suite.rb new file mode 100644 index 00000000..fa8bdaab --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/suite.rb @@ -0,0 +1,9 @@ +require 'test/unit' + +tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"] +tests.delete_if { |o| o =~ /test_parsing/ } +tests.each do |file| + require file +end + +require File.dirname(__FILE__) + '/test_parsing.rb' \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_Chronic.rb b/tracks/vendor/plugins/chronic/test/test_Chronic.rb new file mode 100644 index 00000000..04fedb52 --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_Chronic.rb @@ -0,0 +1,50 @@ +require 'chronic' +require 'test/unit' + +class TestChronic < Test::Unit::TestCase + + def setup + # Wed Aug 16 14:00:00 UTC 2006 + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_post_normalize_am_pm_aliases + # affect wanted patterns + + tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")] + tokens[0].tag(Chronic::RepeaterTime.new("5:00")) + tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning)) + + assert_equal :morning, tokens[1].tags[0].type + + tokens = Chronic.dealias_and_disambiguate_times(tokens, {}) + + assert_equal :am, tokens[1].tags[0].type + assert_equal 2, tokens.size + + # don't affect unwanted patterns + + tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")] + tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning)) + + assert_equal :morning, tokens[1].tags[0].type + + tokens = Chronic.dealias_and_disambiguate_times(tokens, {}) + + assert_equal :morning, tokens[1].tags[0].type + assert_equal 2, tokens.size + end + + def test_guess + span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0)) + assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span) + + span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1)) + assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span) + + span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12)) + assert_equal Time.local(2006, 11, 16), Chronic.guess(span) + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_Handler.rb b/tracks/vendor/plugins/chronic/test/test_Handler.rb new file mode 100644 index 00000000..4e36dfe2 --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_Handler.rb @@ -0,0 +1,110 @@ +require 'chronic' +require 'test/unit' + +class TestHandler < Test::Unit::TestCase + + def setup + # Wed Aug 16 14:00:00 UTC 2006 + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_handler_class_1 + handler = Chronic::Handler.new([:repeater], :handler) + + tokens = [Chronic::Token.new('friday')] + tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + + assert handler.match(tokens, Chronic.definitions) + + tokens << Chronic::Token.new('afternoon') + tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon)) + + assert !handler.match(tokens, Chronic.definitions) + end + + def test_handler_class_2 + handler = Chronic::Handler.new([:repeater, :repeater?], :handler) + + tokens = [Chronic::Token.new('friday')] + tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + + assert handler.match(tokens, Chronic.definitions) + + tokens << Chronic::Token.new('afternoon') + tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon)) + + assert handler.match(tokens, Chronic.definitions) + + tokens << Chronic::Token.new('afternoon') + tokens[2].tag(Chronic::RepeaterDayPortion.new(:afternoon)) + + assert !handler.match(tokens, Chronic.definitions) + end + + def test_handler_class_3 + handler = Chronic::Handler.new([:repeater, 'time?'], :handler) + + tokens = [Chronic::Token.new('friday')] + tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + + assert handler.match(tokens, Chronic.definitions) + + tokens << Chronic::Token.new('afternoon') + tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon)) + + assert !handler.match(tokens, Chronic.definitions) + end + + def test_handler_class_4 + handler = Chronic::Handler.new([:repeater_month_name, :scalar_day, 'time?'], :handler) + + tokens = [Chronic::Token.new('may')] + tokens[0].tag(Chronic::RepeaterMonthName.new(:may)) + + assert !handler.match(tokens, Chronic.definitions) + + tokens << Chronic::Token.new('27') + tokens[1].tag(Chronic::ScalarDay.new(27)) + + assert handler.match(tokens, Chronic.definitions) + end + + def test_handler_class_5 + handler = Chronic::Handler.new([:repeater, 'time?'], :handler) + + tokens = [Chronic::Token.new('friday')] + tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + + assert handler.match(tokens, Chronic.definitions) + + tokens << Chronic::Token.new('5:00') + tokens[1].tag(Chronic::RepeaterTime.new('5:00')) + + assert handler.match(tokens, Chronic.definitions) + + tokens << Chronic::Token.new('pm') + tokens[2].tag(Chronic::RepeaterDayPortion.new(:pm)) + + assert handler.match(tokens, Chronic.definitions) + end + + def test_handler_class_6 + handler = Chronic::Handler.new([:scalar, :repeater, :pointer], :handler) + + tokens = [Chronic::Token.new('3'), + Chronic::Token.new('years'), + Chronic::Token.new('past')] + + tokens[0].tag(Chronic::Scalar.new(3)) + tokens[1].tag(Chronic::RepeaterYear.new(:year)) + tokens[2].tag(Chronic::Pointer.new(:past)) + + assert handler.match(tokens, Chronic.definitions) + end + + def test_constantize + handler = Chronic::Handler.new([], :handler) + assert_equal Chronic::RepeaterTime, handler.constantize(:repeater_time) + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_RepeaterDayName.rb b/tracks/vendor/plugins/chronic/test/test_RepeaterDayName.rb new file mode 100644 index 00000000..8e119db3 --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_RepeaterDayName.rb @@ -0,0 +1,52 @@ +require 'chronic' +require 'test/unit' + +class TestRepeaterDayName < Test::Unit::TestCase + + def setup + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_match + token = Chronic::Token.new('saturday') + repeater = Chronic::Repeater.scan_for_day_names(token) + assert_equal Chronic::RepeaterDayName, repeater.class + assert_equal :saturday, repeater.type + + token = Chronic::Token.new('sunday') + repeater = Chronic::Repeater.scan_for_day_names(token) + assert_equal Chronic::RepeaterDayName, repeater.class + assert_equal :sunday, repeater.type + end + + def test_next_future + mondays = Chronic::RepeaterDayName.new(:monday) + mondays.start = @now + + span = mondays.next(:future) + + assert_equal Time.local(2006, 8, 21), span.begin + assert_equal Time.local(2006, 8, 22), span.end + + span = mondays.next(:future) + + assert_equal Time.local(2006, 8, 28), span.begin + assert_equal Time.local(2006, 8, 29), span.end + end + + def test_next_past + mondays = Chronic::RepeaterDayName.new(:monday) + mondays.start = @now + + span = mondays.next(:past) + + assert_equal Time.local(2006, 8, 14), span.begin + assert_equal Time.local(2006, 8, 15), span.end + + span = mondays.next(:past) + + assert_equal Time.local(2006, 8, 7), span.begin + assert_equal Time.local(2006, 8, 8), span.end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_RepeaterFortnight.rb b/tracks/vendor/plugins/chronic/test/test_RepeaterFortnight.rb new file mode 100644 index 00000000..cb80c43c --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_RepeaterFortnight.rb @@ -0,0 +1,63 @@ +require 'chronic' +require 'test/unit' + +class TestRepeaterFortnight < Test::Unit::TestCase + + def setup + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_next_future + fortnights = Chronic::RepeaterFortnight.new(:fortnight) + fortnights.start = @now + + next_fortnight = fortnights.next(:future) + assert_equal Time.local(2006, 8, 20), next_fortnight.begin + assert_equal Time.local(2006, 9, 3), next_fortnight.end + + next_next_fortnight = fortnights.next(:future) + assert_equal Time.local(2006, 9, 3), next_next_fortnight.begin + assert_equal Time.local(2006, 9, 17), next_next_fortnight.end + end + + def test_next_past + fortnights = Chronic::RepeaterFortnight.new(:fortnight) + fortnights.start = @now + + last_fortnight = fortnights.next(:past) + assert_equal Time.local(2006, 7, 30), last_fortnight.begin + assert_equal Time.local(2006, 8, 13), last_fortnight.end + + last_last_fortnight = fortnights.next(:past) + assert_equal Time.local(2006, 7, 16), last_last_fortnight.begin + assert_equal Time.local(2006, 7, 30), last_last_fortnight.end + end + + def test_this_future + fortnights = Chronic::RepeaterFortnight.new(:fortnight) + fortnights.start = @now + + this_fortnight = fortnights.this(:future) + assert_equal Time.local(2006, 8, 16, 15), this_fortnight.begin + assert_equal Time.local(2006, 8, 27), this_fortnight.end + end + + def test_this_past + fortnights = Chronic::RepeaterFortnight.new(:fortnight) + fortnights.start = @now + + this_fortnight = fortnights.this(:past) + assert_equal Time.local(2006, 8, 13, 0), this_fortnight.begin + assert_equal Time.local(2006, 8, 16, 14), this_fortnight.end + end + + def test_offset + span = Chronic::Span.new(@now, @now + 1) + + offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future) + + assert_equal Time.local(2006, 9, 6, 14), offset_span.begin + assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_RepeaterHour.rb b/tracks/vendor/plugins/chronic/test/test_RepeaterHour.rb new file mode 100644 index 00000000..48f37c42 --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_RepeaterHour.rb @@ -0,0 +1,65 @@ +require 'chronic' +require 'test/unit' + +class TestRepeaterHour < Test::Unit::TestCase + + def setup + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_next_future + hours = Chronic::RepeaterHour.new(:hour) + hours.start = @now + + next_hour = hours.next(:future) + assert_equal Time.local(2006, 8, 16, 15), next_hour.begin + assert_equal Time.local(2006, 8, 16, 16), next_hour.end + + next_next_hour = hours.next(:future) + assert_equal Time.local(2006, 8, 16, 16), next_next_hour.begin + assert_equal Time.local(2006, 8, 16, 17), next_next_hour.end + end + + def test_next_past + hours = Chronic::RepeaterHour.new(:hour) + hours.start = @now + + past_hour = hours.next(:past) + assert_equal Time.local(2006, 8, 16, 13), past_hour.begin + assert_equal Time.local(2006, 8, 16, 14), past_hour.end + + past_past_hour = hours.next(:past) + assert_equal Time.local(2006, 8, 16, 12), past_past_hour.begin + assert_equal Time.local(2006, 8, 16, 13), past_past_hour.end + end + + def test_this + @now = Time.local(2006, 8, 16, 14, 30) + + hours = Chronic::RepeaterHour.new(:hour) + hours.start = @now + + this_hour = hours.this(:future) + assert_equal Time.local(2006, 8, 16, 14, 31), this_hour.begin + assert_equal Time.local(2006, 8, 16, 15), this_hour.end + + this_hour = hours.this(:past) + assert_equal Time.local(2006, 8, 16, 14), this_hour.begin + assert_equal Time.local(2006, 8, 16, 14, 30), this_hour.end + end + + def test_offset + span = Chronic::Span.new(@now, @now + 1) + + offset_span = Chronic::RepeaterHour.new(:hour).offset(span, 3, :future) + + assert_equal Time.local(2006, 8, 16, 17), offset_span.begin + assert_equal Time.local(2006, 8, 16, 17, 0, 1), offset_span.end + + offset_span = Chronic::RepeaterHour.new(:hour).offset(span, 24, :past) + + assert_equal Time.local(2006, 8, 15, 14), offset_span.begin + assert_equal Time.local(2006, 8, 15, 14, 0, 1), offset_span.end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_RepeaterMonth.rb b/tracks/vendor/plugins/chronic/test/test_RepeaterMonth.rb new file mode 100644 index 00000000..d0609c5f --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_RepeaterMonth.rb @@ -0,0 +1,47 @@ +require 'chronic' +require 'test/unit' + +class TestRepeaterMonth < Test::Unit::TestCase + + def setup + # Wed Aug 16 14:00:00 2006 + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_offset_by + # future + + time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 1, :future) + assert_equal Time.local(2006, 9, 16, 14), time + + time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 5, :future) + assert_equal Time.local(2007, 1, 16, 14), time + + # past + + time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 1, :past) + assert_equal Time.local(2006, 7, 16, 14), time + + time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 10, :past) + assert_equal Time.local(2005, 10, 16, 14), time + end + + def test_offset + # future + + span = Chronic::Span.new(@now, @now + 60) + offset_span = Chronic::RepeaterMonth.new(:month).offset(span, 1, :future) + + assert_equal Time.local(2006, 9, 16, 14), offset_span.begin + assert_equal Time.local(2006, 9, 16, 14, 1), offset_span.end + + # past + + span = Chronic::Span.new(@now, @now + 60) + offset_span = Chronic::RepeaterMonth.new(:month).offset(span, 1, :past) + + assert_equal Time.local(2006, 7, 16, 14), offset_span.begin + assert_equal Time.local(2006, 7, 16, 14, 1), offset_span.end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_RepeaterMonthName.rb b/tracks/vendor/plugins/chronic/test/test_RepeaterMonthName.rb new file mode 100644 index 00000000..6326a458 --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_RepeaterMonthName.rb @@ -0,0 +1,57 @@ +require 'chronic' +require 'test/unit' + +class TestRepeaterMonthName < Test::Unit::TestCase + + def setup + # Wed Aug 16 14:00:00 2006 + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_next + # future + + mays = Chronic::RepeaterMonthName.new(:may) + mays.start = @now + + next_may = mays.next(:future) + assert_equal Time.local(2007, 5), next_may.begin + assert_equal Time.local(2007, 6), next_may.end + + next_next_may = mays.next(:future) + assert_equal Time.local(2008, 5), next_next_may.begin + assert_equal Time.local(2008, 6), next_next_may.end + + decembers = Chronic::RepeaterMonthName.new(:december) + decembers.start = @now + + next_december = decembers.next(:future) + assert_equal Time.local(2006, 12), next_december.begin + assert_equal Time.local(2007, 1), next_december.end + + # past + + mays = Chronic::RepeaterMonthName.new(:may) + mays.start = @now + + assert_equal Time.local(2006, 5), mays.next(:past).begin + assert_equal Time.local(2005, 5), mays.next(:past).begin + end + + def test_this + octobers = Chronic::RepeaterMonthName.new(:october) + octobers.start = @now + + this_october = octobers.this(:future) + assert_equal Time.local(2006, 10, 1), this_october.begin + assert_equal Time.local(2006, 11, 1), this_october.end + + aprils = Chronic::RepeaterMonthName.new(:april) + aprils.start = @now + + this_april = aprils.this(:past) + assert_equal Time.local(2006, 4, 1), this_april.begin + assert_equal Time.local(2006, 5, 1), this_april.end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_RepeaterTime.rb b/tracks/vendor/plugins/chronic/test/test_RepeaterTime.rb new file mode 100644 index 00000000..bb277358 --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_RepeaterTime.rb @@ -0,0 +1,72 @@ +require 'chronic' +require 'test/unit' + +class TestRepeaterTime < Test::Unit::TestCase + + def setup + # Wed Aug 16 14:00:00 2006 + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_next_future + t = Chronic::RepeaterTime.new('4:00') + t.start = @now + + assert_equal Time.local(2006, 8, 16, 16), t.next(:future).begin + assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin + + t = Chronic::RepeaterTime.new('13:00') + t.start = @now + + assert_equal Time.local(2006, 8, 17, 13), t.next(:future).begin + assert_equal Time.local(2006, 8, 18, 13), t.next(:future).begin + + t = Chronic::RepeaterTime.new('0400') + t.start = @now + + assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin + assert_equal Time.local(2006, 8, 18, 4), t.next(:future).begin + end + + def test_next_past + t = Chronic::RepeaterTime.new('4:00') + t.start = @now + + assert_equal Time.local(2006, 8, 16, 4), t.next(:past).begin + assert_equal Time.local(2006, 8, 15, 16), t.next(:past).begin + + t = Chronic::RepeaterTime.new('13:00') + t.start = @now + + assert_equal Time.local(2006, 8, 16, 13), t.next(:past).begin + assert_equal Time.local(2006, 8, 15, 13), t.next(:past).begin + end + + def test_type + t1 = Chronic::RepeaterTime.new('4') + assert_equal 14_400, t1.type.time + + t1 = Chronic::RepeaterTime.new('14') + assert_equal 50_400, t1.type.time + + t1 = Chronic::RepeaterTime.new('4:00') + assert_equal 14_400, t1.type.time + + t1 = Chronic::RepeaterTime.new('4:30') + assert_equal 16_200, t1.type.time + + t1 = Chronic::RepeaterTime.new('1400') + assert_equal 50_400, t1.type.time + + t1 = Chronic::RepeaterTime.new('0400') + assert_equal 14_400, t1.type.time + + t1 = Chronic::RepeaterTime.new('04') + assert_equal 14_400, t1.type.time + + t1 = Chronic::RepeaterTime.new('400') + assert_equal 14_400, t1.type.time + end + + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_RepeaterWeek.rb b/tracks/vendor/plugins/chronic/test/test_RepeaterWeek.rb new file mode 100644 index 00000000..084ef4ee --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_RepeaterWeek.rb @@ -0,0 +1,63 @@ +require 'chronic' +require 'test/unit' + +class TestRepeaterWeek < Test::Unit::TestCase + + def setup + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_next_future + weeks = Chronic::RepeaterWeek.new(:week) + weeks.start = @now + + next_week = weeks.next(:future) + assert_equal Time.local(2006, 8, 20), next_week.begin + assert_equal Time.local(2006, 8, 27), next_week.end + + next_next_week = weeks.next(:future) + assert_equal Time.local(2006, 8, 27), next_next_week.begin + assert_equal Time.local(2006, 9, 3), next_next_week.end + end + + def test_next_past + weeks = Chronic::RepeaterWeek.new(:week) + weeks.start = @now + + last_week = weeks.next(:past) + assert_equal Time.local(2006, 8, 6), last_week.begin + assert_equal Time.local(2006, 8, 13), last_week.end + + last_last_week = weeks.next(:past) + assert_equal Time.local(2006, 7, 30), last_last_week.begin + assert_equal Time.local(2006, 8, 6), last_last_week.end + end + + def test_this_future + weeks = Chronic::RepeaterWeek.new(:week) + weeks.start = @now + + this_week = weeks.this(:future) + assert_equal Time.local(2006, 8, 16, 15), this_week.begin + assert_equal Time.local(2006, 8, 20), this_week.end + end + + def test_this_past + weeks = Chronic::RepeaterWeek.new(:week) + weeks.start = @now + + this_week = weeks.this(:past) + assert_equal Time.local(2006, 8, 13, 0), this_week.begin + assert_equal Time.local(2006, 8, 16, 14), this_week.end + end + + def test_offset + span = Chronic::Span.new(@now, @now + 1) + + offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future) + + assert_equal Time.local(2006, 9, 6, 14), offset_span.begin + assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_RepeaterYear.rb b/tracks/vendor/plugins/chronic/test/test_RepeaterYear.rb new file mode 100644 index 00000000..eaebe25a --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_RepeaterYear.rb @@ -0,0 +1,63 @@ +require 'chronic' +require 'test/unit' + +class TestRepeaterYear < Test::Unit::TestCase + + def setup + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_next_future + years = Chronic::RepeaterYear.new(:year) + years.start = @now + + next_year = years.next(:future) + assert_equal Time.local(2007, 1, 1), next_year.begin + assert_equal Time.local(2008, 1, 1), next_year.end + + next_next_year = years.next(:future) + assert_equal Time.local(2008, 1, 1), next_next_year.begin + assert_equal Time.local(2009, 1, 1), next_next_year.end + end + + def test_next_past + years = Chronic::RepeaterYear.new(:year) + years.start = @now + + last_year = years.next(:past) + assert_equal Time.local(2005, 1, 1), last_year.begin + assert_equal Time.local(2006, 1, 1), last_year.end + + last_last_year = years.next(:past) + assert_equal Time.local(2004, 1, 1), last_last_year.begin + assert_equal Time.local(2005, 1, 1), last_last_year.end + end + + def test_this + years = Chronic::RepeaterYear.new(:year) + years.start = @now + + this_year = years.this(:future) + assert_equal Time.local(2006, 8, 17), this_year.begin + assert_equal Time.local(2007, 1, 1), this_year.end + + this_year = years.this(:past) + assert_equal Time.local(2006, 1, 1), this_year.begin + assert_equal Time.local(2006, 8, 16), this_year.end + end + + def test_offset + span = Chronic::Span.new(@now, @now + 1) + + offset_span = Chronic::RepeaterYear.new(:year).offset(span, 3, :future) + + assert_equal Time.local(2009, 8, 16, 14), offset_span.begin + assert_equal Time.local(2009, 8, 16, 14, 0, 1), offset_span.end + + offset_span = Chronic::RepeaterYear.new(:year).offset(span, 10, :past) + + assert_equal Time.local(1996, 8, 16, 14), offset_span.begin + assert_equal Time.local(1996, 8, 16, 14, 0, 1), offset_span.end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_Span.rb b/tracks/vendor/plugins/chronic/test/test_Span.rb new file mode 100644 index 00000000..099455a2 --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_Span.rb @@ -0,0 +1,24 @@ +require 'chronic' +require 'test/unit' + +class TestSpan < Test::Unit::TestCase + + def setup + # Wed Aug 16 14:00:00 UTC 2006 + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_span_width + span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0)) + assert_equal (60 * 60 * 24), span.width + end + + def test_span_math + s = Chronic::Span.new(1, 2) + assert_equal 2, (s + 1).begin + assert_equal 3, (s + 1).end + assert_equal 0, (s - 1).begin + assert_equal 1, (s - 1).end + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_Token.rb b/tracks/vendor/plugins/chronic/test/test_Token.rb new file mode 100644 index 00000000..80463d1e --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_Token.rb @@ -0,0 +1,26 @@ +require 'chronic' +require 'test/unit' + +class TestToken < Test::Unit::TestCase + + def setup + # Wed Aug 16 14:00:00 UTC 2006 + @now = Time.local(2006, 8, 16, 14, 0, 0, 0) + end + + def test_token + token = Chronic::Token.new('foo') + assert_equal 0, token.tags.size + assert !token.tagged? + token.tag("mytag") + assert_equal 1, token.tags.size + assert token.tagged? + assert_equal String, token.get_tag(String).class + token.tag(5) + assert_equal 2, token.tags.size + token.untag(String) + assert_equal 1, token.tags.size + assert_equal 'foo', token.word + end + +end \ No newline at end of file diff --git a/tracks/vendor/plugins/chronic/test/test_parsing.rb b/tracks/vendor/plugins/chronic/test/test_parsing.rb new file mode 100644 index 00000000..406625a7 --- /dev/null +++ b/tracks/vendor/plugins/chronic/test/test_parsing.rb @@ -0,0 +1,478 @@ +require 'chronic' +require 'test/unit' + +class TestParsing < Test::Unit::TestCase + + def setup + # Wed Aug 16 14:00:00 UTC 2006 + @time_2006_08_16_14_00_00 = Time.local(2006, 8, 16, 14, 0, 0, 0) + @time_2006_08_16_03_00_00 = Time.local(2006, 8, 16, 3, 0, 0, 0) + Chronic.debug = false + end + + def test__parse_guess_dates + # rm_sd + + time = Chronic.parse("may 27", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2007, 5, 27, 12), time + + time = Chronic.parse("may 28", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 5, 28, 12), time + + time = Chronic.parse("may 28 5pm", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 5, 28, 17), time + + time = Chronic.parse("may 28 at 5pm", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 5, 28, 17), time + + # rm_od + + time = Chronic.parse("may 27th", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2007, 5, 27, 12), time + + time = Chronic.parse("may 27th", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 5, 27, 12), time + + time = Chronic.parse("may 27th 5:00 pm", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 5, 27, 17), time + + time = Chronic.parse("may 27th at 5pm", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 5, 27, 17), time + + time = Chronic.parse("may 27th at 5", :now => @time_2006_08_16_14_00_00, :ambiguous_time_range => :none) + assert_equal Time.local(2007, 5, 27, 5), time + + # rm_sy + + time = Chronic.parse("June 1979", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(1979, 6, 16, 0), time + + time = Chronic.parse("dec 79", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(1979, 12, 16, 12), time + + # rm_sd_sy + + time = Chronic.parse("jan 3 2010", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2010, 1, 3, 12), time + + time = Chronic.parse("jan 3 2010 midnight", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2010, 1, 4, 0), time + + time = Chronic.parse("jan 3 2010 at midnight", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2010, 1, 4, 0), time + + time = Chronic.parse("jan 3 2010 at 4", :now => @time_2006_08_16_14_00_00, :ambiguous_time_range => :none) + assert_equal Time.local(2010, 1, 3, 4), time + + #time = Chronic.parse("January 12, '00", :now => @time_2006_08_16_14_00_00) + #assert_equal Time.local(2000, 1, 12, 12), time + + time = Chronic.parse("may 27 79", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(1979, 5, 27, 12), time + + time = Chronic.parse("may 27 79 4:30", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(1979, 5, 27, 16, 30), time + + time = Chronic.parse("may 27 79 at 4:30", :now => @time_2006_08_16_14_00_00, :ambiguous_time_range => :none) + assert_equal Time.local(1979, 5, 27, 4, 30), time + + # sd_rm_sy + + time = Chronic.parse("3 jan 2010", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2010, 1, 3, 12), time + + time = Chronic.parse("3 jan 2010 4pm", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2010, 1, 3, 16), time + + # sm_sd_sy + + time = Chronic.parse("5/27/1979", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(1979, 5, 27, 12), time + + time = Chronic.parse("5/27/1979 4am", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(1979, 5, 27, 4), time + + # sd_sm_sy + + time = Chronic.parse("27/5/1979", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(1979, 5, 27, 12), time + + time = Chronic.parse("27/5/1979 @ 0700", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(1979, 5, 27, 7), time + + # sm_sy + + time = Chronic.parse("05/06", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 5, 16, 12), time + + time = Chronic.parse("12/06", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 12, 16, 12), time + + time = Chronic.parse("13/06", :now => @time_2006_08_16_14_00_00) + assert_equal nil, time + + # sy_sm_sd + + time = Chronic.parse("2000-1-1", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2000, 1, 1, 12), time + + time = Chronic.parse("2006-08-20", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 20, 12), time + + time = Chronic.parse("2006-08-20 7pm", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 20, 19), time + + time = Chronic.parse("2006-08-20 03:00", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 20, 3), time + + # rm_sd_rt + + #time = Chronic.parse("jan 5 13:00", :now => @time_2006_08_16_14_00_00) + #assert_equal Time.local(2007, 1, 5, 13), time + + # due to limitations of the Time class, these don't work + + time = Chronic.parse("may 40", :now => @time_2006_08_16_14_00_00) + assert_equal nil, time + + time = Chronic.parse("may 27 40", :now => @time_2006_08_16_14_00_00) + assert_equal nil, time + + time = Chronic.parse("1800-08-20", :now => @time_2006_08_16_14_00_00) + assert_equal nil, time + end + + def test_parse_guess_r + time = Chronic.parse("friday", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 18, 12), time + + time = Chronic.parse("5", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 17), time + + time = Chronic.parse("5", :now => @time_2006_08_16_03_00_00, :ambiguous_time_range => :none) + assert_equal Time.local(2006, 8, 16, 5), time + + time = Chronic.parse("13:00", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 17, 13), time + + time = Chronic.parse("13:45", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 17, 13, 45), time + + time = Chronic.parse("november", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 11, 16), time + end + + def test_parse_guess_rr + time = Chronic.parse("friday 13:00", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 18, 13), time + + time = Chronic.parse("monday 4:00", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 21, 16), time + + time = Chronic.parse("sat 4:00", :now => @time_2006_08_16_14_00_00, :ambiguous_time_range => :none) + assert_equal Time.local(2006, 8, 19, 4), time + + time = Chronic.parse("sunday 4:20", :now => @time_2006_08_16_14_00_00, :ambiguous_time_range => :none) + assert_equal Time.local(2006, 8, 20, 4, 20), time + + time = Chronic.parse("4 pm", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 16), time + + time = Chronic.parse("4 am", :now => @time_2006_08_16_14_00_00, :ambiguous_time_range => :none) + assert_equal Time.local(2006, 8, 16, 4), time + + time = Chronic.parse("4:00 in the morning", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 4), time + + #time = Chronic.parse("november 4", :now => @time_2006_08_16_14_00_00) + #assert_equal Time.local(2006, 11, 4, 12), time + end + + def test_parse_guess_rrr + time = Chronic.parse("friday 1 pm", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 18, 13), time + + time = Chronic.parse("friday 11 at night", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 18, 23), time + + time = Chronic.parse("friday 11 in the evening", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 18, 23), time + + time = Chronic.parse("sunday 6am", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 20, 6), time + end + + def test_parse_guess_gr + # year + + time = Chronic.parse("this year", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 10, 24, 12, 30), time + + time = Chronic.parse("this year", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 4, 24, 12, 30), time + + # month + + time = Chronic.parse("this month", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 24, 12), time + + time = Chronic.parse("this month", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 8, 8, 12), time + + # month name + + time = Chronic.parse("last november", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2005, 11, 16), time + + # fortnight + + time = Chronic.parse("this fortnight", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 21, 19, 30), time + + time = Chronic.parse("this fortnight", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 8, 14, 19), time + + # week + + time = Chronic.parse("this week", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 18, 7, 30), time + + time = Chronic.parse("this week", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 8, 14, 19), time + + # day + + time = Chronic.parse("this day", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 19, 30), time + + time = Chronic.parse("this day", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 8, 16, 7), time + + time = Chronic.parse("today", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 19, 30), time + + time = Chronic.parse("yesterday", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 15, 12), time + + time = Chronic.parse("tomorrow", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 17, 12), time + + # day name + + time = Chronic.parse("this tuesday", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 22, 12), time + + time = Chronic.parse("next tuesday", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 22, 12), time + + time = Chronic.parse("last tuesday", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 15, 12), time + + time = Chronic.parse("this wed", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 23, 12), time + + time = Chronic.parse("next wed", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 23, 12), time + + time = Chronic.parse("last wed", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 9, 12), time + + # day portion + + time = Chronic.parse("this morning", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 9), time + + time = Chronic.parse("tonight", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 22), time + + # second + + time = Chronic.parse("this second", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 14), time + + time = Chronic.parse("this second", :now => @time_2006_08_16_14_00_00, :context => :past) + assert_equal Time.local(2006, 8, 16, 14), time + + time = Chronic.parse("next second", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 14, 0, 1), time + + time = Chronic.parse("last second", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 13, 59, 59), time + end + + def test_parse_guess_grr + time = Chronic.parse("yesterday at 4:00", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 15, 16), time + + time = Chronic.parse("yesterday at 4:00", :now => @time_2006_08_16_14_00_00, :ambiguous_time_range => :none) + assert_equal Time.local(2006, 8, 15, 4), time + + time = Chronic.parse("last friday at 4:00", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 11, 16), time + + time = Chronic.parse("next wed 4:00", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 23, 16), time + + time = Chronic.parse("yesterday afternoon", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 15, 15), time + + time = Chronic.parse("last week tuesday", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 8, 12), time + end + + def test_parse_guess_grrr + time = Chronic.parse("today at 6:00pm", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 18), time + + time = Chronic.parse("this day 1800", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 18), time + + time = Chronic.parse("yesterday at 4:00pm", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 15, 16), time + end + + def test_parse_guess_rgr + time = Chronic.parse("afternoon yesterday", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 15, 15), time + + time = Chronic.parse("tuesday last week", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 8, 12), time + end + + def test_parse_guess_s_r_p + # past + + time = Chronic.parse("3 years ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2003, 8, 16, 14, 30, 30), time + + time = Chronic.parse("1 month ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 7, 16, 14, 30, 30), time + + time = Chronic.parse("1 fortnight ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 2, 14, 30, 30), time + + time = Chronic.parse("2 fortnights ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 7, 19, 14, 30, 30), time + + time = Chronic.parse("3 weeks ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 7, 26, 14, 30, 30), time + + time = Chronic.parse("3 days ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 13, 14, 0, 30), time + + #time = Chronic.parse("1 monday ago", :now => @time_2006_08_16_14_00_00) + #assert_equal Time.local(2006, 8, 14, 12), time + + time = Chronic.parse("5 mornings ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 12, 9), time + + time = Chronic.parse("7 hours ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 7, 0, 30), time + + time = Chronic.parse("3 minutes ago", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 13, 57), time + + time = Chronic.parse("20 seconds before now", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 13, 59, 40), time + + # future + + time = Chronic.parse("3 years from now", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2009, 8, 16, 14, 30, 30), time + + time = Chronic.parse("6 months hence", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2007, 2, 16, 14, 30, 30), time + + time = Chronic.parse("3 fortnights hence", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 9, 27, 14, 30, 30), time + + time = Chronic.parse("1 week from now", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 23, 14, 30, 30), time + + time = Chronic.parse("1 day hence", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 17, 14, 0, 30), time + + time = Chronic.parse("5 mornings hence", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 21, 9), time + + time = Chronic.parse("1 hour from now", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 15, 0, 30), time + + time = Chronic.parse("20 minutes hence", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 14, 20), time + + time = Chronic.parse("20 seconds from now", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 14, 0, 20), time + end + + def test_parse_guess_p_s_r + time = Chronic.parse("in 3 hours", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 16, 17, 0, 30), time + end + + def test_parse_guess_s_r_p_a + # past + + time = Chronic.parse("3 years ago tomorrow", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2003, 8, 17, 12), time + + time = Chronic.parse("3 years ago this friday", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2003, 8, 18, 12), time + + time = Chronic.parse("3 months ago saturday at 5:00 pm", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 5, 19, 17), time + + time = Chronic.parse("2 days from this second", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 18, 14), time + + time = Chronic.parse("7 hours before tomorrow at midnight", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 17, 17), time + + # future + end + + def test_parse_guess_o_r_s_r + time = Chronic.parse("3rd wednesday in november", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 11, 15, 12), time + + time = Chronic.parse("10th wednesday in november", :now => @time_2006_08_16_14_00_00) + assert_equal nil, time + end + + def test_parse_guess_o_r_g_r + time = Chronic.parse("3rd month next year", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2007, 3, 16, 12, 30), time + + time = Chronic.parse("3rd thursday this september", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 9, 21, 12), time + + time = Chronic.parse("4th day last week", :now => @time_2006_08_16_14_00_00) + assert_equal Time.local(2006, 8, 9, 12), time + end + + def test_parse_guess_nonsense + time = Chronic.parse("some stupid nonsense", :now => @time_2006_08_16_14_00_00) + assert_equal nil, time + end + + def test_parse_span + span = Chronic.parse("friday", :now => @time_2006_08_16_14_00_00, :guess => false) + assert_equal Time.local(2006, 8, 18), span.begin + assert_equal Time.local(2006, 8, 19), span.end + + span = Chronic.parse("november", :now => @time_2006_08_16_14_00_00, :guess => false) + assert_equal Time.local(2006, 11), span.begin + assert_equal Time.local(2006, 12), span.end + end + + def test_argument_validation + assert_raise(Chronic::InvalidArgumentException) do + time = Chronic.parse("may 27", :foo => :bar) + end + + assert_raise(Chronic::InvalidArgumentException) do + time = Chronic.parse("may 27", :context => :bar) + end + end + +end \ No newline at end of file