pax_global_header00006660000000000000000000000064123556212460014520gustar00rootroot0000000000000052 comment=e4171bd307c0a3cc3ceff7575fd4d2d82e274223 ice_cube-0.12.1/000077500000000000000000000000001235562124600133375ustar00rootroot00000000000000ice_cube-0.12.1/.gitignore000066400000000000000000000001101235562124600153170ustar00rootroot00000000000000Gemfile.lock *.swp *.swo .DS_Store coverage coverage.data *.gem .bundle ice_cube-0.12.1/.rspec000066400000000000000000000000071235562124600144510ustar00rootroot00000000000000-fd -c ice_cube-0.12.1/.travis.yml000066400000000000000000000002421235562124600154460ustar00rootroot00000000000000script: "bundle exec rspec spec" notifications: email: - john.crepezzi@gmail.com branches: only: - master - v0.7 rvm: - 1.9.3 - 2.0.0 - 2.1 ice_cube-0.12.1/CHANGELOG.md000066400000000000000000000133651235562124600151600ustar00rootroot00000000000000# Changelog ## 0.12.0 / 2014-04-06 * [FEATURE] Rename to `start_time` as a hash key (see UPGRADING) (#102) * [FEATURE] Notify of deprecated usage (#219) * [BUGFIX] Skip double occurrences over DST (#189) * [BUGFIX] Avoid symbolizing hash keys from input * [BUGFIX] Ensure time comparisons are done in schedule time zone (#209) * [BUGFIX] Occurrence#overnight? now works on the last day of the month (#218) ## 0.11.3 / 2014-02-07 * [BUGFIX] Fix a StopIteration leak ## 0.11.2 / 2014-01-25 * [ENHANCEMENT] Use Enumerator for schedule occurrences * [BUGFIX] Fix high CPU usage on minutely schedules ## 0.11.1 / 2013-10-28 * [ENHANCEMENT] Move deprecated into IceCube namespace * [ENHANCEMENT] Standardize the exceptions that we raise * [BUGFIX] Fix ActiveSupport edge case restoring serialized TZ ## 0.11.0 / 2013-06-13 * [FEATURE] `schedule.last(n)` method (#117) * [FEATURE] `previous_occurrence` & `previous_occurrences` methods (#170) * [BUGFIX] Occurrence `to_s` accepts format to comply with Rails ## 0.10.1 / 2013-05-17 * [BUGFIX] Match time zone from schedule when finding times (#152) * [BUGFIX] Reliably calculate distance to same day in next month (#171) * [ENHANCEMENT] Accept arrays in multiparameter DSL methods (#139) * [BUGFIX] Updating interval on a rule shouldn't leave duplicate validations (#158) (#157) * [BUGFIX] Allow Occurrence to work transparently with Arel (#168) * [BUGFIX] Raise errors for invalid input (#139) ## 0.10.0 / 2013-02-25 * [BUGFIX] Fix monthly intervals to not skip short months (#105) * [FEATURE] Add support for `week_start` (@masquita) (#75) * [BUGFIX] Fix `occurring_between?` for zero-length occurrences at start boundary (#147) * [ENHANCEMENT] Add block initialization, new schedule yields itself (#146) * [ENHANCEMENT] Warn on use of DateTime and convert to local Time (#144) * [BUGFIX] Bug fix for count limit across multiple rules (#149) * [BUGFIX] Fix occurrences in DST transition (#150) * [ENHANCEMENT] Start time counts as an implicit occurrence (no more empty schedule) (#135) * [FEATURE] Schedule occurrences have end times (#119) ## 0.9.3 / 2013-01-03 * [BUGFIX] Match the subseconds of `start_time` when finding occurrences (#89) * [FEATURE] Duration is dependent upon `end_time` (#120) * [ENHANCEMENT] Duration defaults to 0 * [BUGFIX] Avoid microseconds when comparing times (#83) * [BUGFIX] Handle DateTime's lack of subseconds ## 0.9.2 / 2012-12-08 * [FEATURE] Allow passing Time, Date, or DateTime to all calls ## 0.9.1 / 2012-10-19 * [BUGFIX] A fix for removing `until` validations (#106) * [BUGFIX] A DST edge fix ## 0.9.0 / 2012-10-12 * [FEATURE] Fix the effect on `end_time` on IceCube::Schedule (#99) * [ENHANCEMENT] Remove `end_time` from `to_s` (#99) * [BUGFIX] Single recurrences now work properly with `conflict_with?` (#71) * [BUGFIX] Fix a bug with interval > 1 when using `occurrences_between` (#92) * [BUGFIX] Allow count, until removal by setting to nil (#94) * [FEATURE] Allow deserialization of string structures easily (#93) * [BUGFIX] Ignore usecs when creating Time.now for `*_occurrences` (#84) * [BUGFIX] DST bug fix (#98) * [FEATURE] Added `occurring_between?` (#88) ## 0.8.0 * Added support for WEEKST (thanks @devwout) ## 0.7.9 * Added INTERVAL to `to_ical` for all interval validations ## 0.7.8 * Bug fixes ## 0.7.7 * Added "Weekends" and "Weekdays" to day's `to_s` ## 0.7.6 * Support for `terminating?` and `conflicts_with?` ## 0.7.5 * Fix an issue with `occurrences_between` when using count (#54) ## 0.7.4 * NameError when serializing schedule with `end_time` (thanks @digx) ## 0.7.3 * Fix for time interval buckets (affects hour, minute, sec) ## 0.7.2 * Fix for interval to/from YAML issue ## 0.7.1 * Fix for comparing rules with nil ## 0.7.0 * Large rewrite, fixing a few small bugs and including some large optimizations to the spidering algo * Support for `each_occurrence` which iterates as it builds forever ## 0.6.15 * Deserialize `until_date` properly in `to_hash` and `to_yaml` (thanks @promisedlandt) ## 0.6.14 * Fixed a skipping issue around DST ending ## 0.6.13 * Fix by Ben Fyvie for daily rule crossing over a year boundary * Additional accessor methods on validations and rules for easy use in microformats (thanks @jamesarosen) * Fix for changing start date affecting schedules without reloading * Fix for typo in `active_support_occurs_between`? causing load issues with ActiveSupport (thanks @carlthuringer) ## 0.6.12 * Be able to set the `start_date` and duration after creating a schedule ## 0.6.11 * Added the ability to add and remove rdates, rrules, exdates, and exrules from a schedule ## 0.6.10 * UNTIL date now serialized with time information ## 0.6.9 * Added support for `Schedule#occurs_between?` ## 0.6.5 * Added a `:start_date_override` option to `from_hash` / `from_yaml` (@sakrafd) ## 0.6.4 * Fixed bug where `next_occurrence` wouldn't actually grab the correct next occurrence with schedules that had more than one recurrence rule and/or a recurrence rule and a recurrence date * Added `next_occurrences` function to schedule, allowing you to get the next _N_ occurrences after a given date ## 0.6.3 * Change how `active_support_occurs_on` works * Fixed bug where `next_occurrence` wouldn't work if no `end_date` was set ## 0.6.2 * Patch release for `to_yaml` performance issue ## 0.6.1 * Lessen the amount of info we store in yaml on the time zone ## 0.6.0 * Changed how time serialization is done to preserve TimeWithZone when appropriate. (#8) * Backward compatibility is intact, but bumping the minor version for the YAML format change. * Fixed next occurrence to work on never-ending schedules (#11) ice_cube-0.12.1/Gemfile000066400000000000000000000000471235562124600146330ustar00rootroot00000000000000source 'https://rubygems.org' gemspec ice_cube-0.12.1/LICENSE000066400000000000000000000020551235562124600143460ustar00rootroot00000000000000Copyright © 2010-2012 John Crepezzi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ice_cube-0.12.1/README.md000066400000000000000000000216561235562124600146300ustar00rootroot00000000000000# ice_cube - Easy schedule expansion [![Build Status][travis-ice_cube-png]][travis-ice_cube] ```bash gem install ice_cube ``` ice_cube is a ruby library for easily handling repeated events (schedules). The API is modeled after [iCalendar events][ical-3.6.1], in a pleasant Ruby syntax. The power lies in the ability to specify multiple rules, and have ice_cube quickly figure out whether the schedule falls on a certain date (.occurs_on?), or what times it occurs at (.occurrences, .first, .all_occurrences). Imagine you want: > Every friday the 13th that falls in October You would write: ```ruby schedule.add_recurrence_rule( Rule.yearly.day_of_month(13).day(:friday).month_of_year(:october) ) ``` --- ## Quick Introductions * [Presentation from Lone Star Ruby Conf][ice_cube-lone_star_pdf] * [Quick Introduction][ice_cube-ruby_nyc_pdf] * [Documentation Website][ice_cube-docs] --- With ice_cube, you can specify (in increasing order of precedence): * Recurrence Rules - Rules on how to include recurring times in a schedule * Recurrence Times - To specifically include in a schedule * Exception Times - To specifically exclude from a schedule Example: Specifying a recurrence with an exception time: ```ruby schedule = IceCube::Schedule.new(now = Time.now) do |s| s.add_recurrence_rule(Rule.daily.count(3)) s.add_exception_time(now + 1.day) end # list occurrences until end_time (end_time is needed for non-terminating rules) occurrences = schedule.occurrences(end_time) # [now] # or all of the occurrences (only for terminating schedules) occurrences = schedule.all_occurrences # [now, now + 2.days] # or check just a single time schedule.occurs_at?(now + 1.day) # false schedule.occurs_at?(now + 2.days) # true # or check just a single day schedule.occurs_on?(Date.today) # true # or check whether it occurs between two dates schedule.occurs_between?(now, now + 30.days) # true schedule.occurs_between?(now + 3.days, now + 30.days) # false # or the first (n) occurrences schedule.first(2) # [now, now + 2.days] schedule.first # now # or the last (n) occurrences (if the schedule terminates) schedule.last(2) # [now + 1.day, now + 2.days] schedule.last # now + 2.days # or the next occurrence schedule.next_occurrence(from_time) # defaults to Time.now schedule.next_occurrences(3, from_time) # defaults to Time.now schedule.remaining_occurrences # for terminating schedules # or the previous occurrence schedule.previous_occurrence(from_time) schedule.previous_occurrences(3, from_time) # or give the schedule a duration and ask if occurring_at? schedule = IceCube::Schedule.new(now, :duration => 3600) schedule.add_recurrence_rule Rule.daily schedule.occurring_at?(now + 1800) # true schedule.occurring_between?(t1, t2) # using end_time also sets the duration schedule = IceCube::Schedule.new(start = Time.now, :end_time => start + 3600) schedule.add_recurrence_rule Rule.daily schedule.occurring_at?(start + 3599) # true schedule.occurring_at?(start + 3600) # false # take control and use iteration schedule = IceCube::Schedule.new schedule.add_recurrence_rule IceCube::Rule.daily.until(Date.today + 30) schedule.each_occurrence { |t| puts t } ``` The reason that schedules have durations and not individual rules, is to maintain compatability with the ical RFC: http://www.kanzaki.com/docs/ical/rrule.html To limit schedules use `count` or `until` on the recurrence rules. Setting `end_time` on the schedule just sets the duration (from the start time) for each occurrence. --- ## Time Zones and ActiveSupport vs. Standard Ruby Time Classes ice_cube works great without ActiveSupport but only supports the environment's single "local" time zone (`ENV['TZ']`) or UTC. To correctly support multiple time zones (especially for DST), you should require 'active_support/time'. A schedule's occurrences will be returned in the same class and time zone as the schedule's start_time. Schedule start times are supported as: * Time.local (default when no time is specified) * Time.utc * ActiveSupport::TimeWithZone (with `Time.zone.now`, `Time.zone.local`, `time.in_time_zone(tz)`) * DateTime (deprecated) and Date are converted to a Time.local --- ## Persistence ice_cube implements its own hash-based .to_yaml, so you can quickly (and safely) serialize schedule objects in and out of your data store ``` ruby yaml = schedule.to_yaml IceCube::Schedule.from_yaml(yaml) hash = schedule.to_hash IceCube::Schedule.from_hash(hash) IceCube::Schedule.from_yaml(yaml, :start_date_override => Time.now) IceCube::Schedule.from_hash(hash, :start_date_override => Time.now) ``` --- ## Using your words ice_cube can provide ical or string representations of individual rules, or the whole schedule. ```ruby rule = IceCube::Rule.daily(2).day_of_week(:tuesday => [1, -1], :wednesday => [2]) rule.to_ical # 'FREQ=DAILY;INTERVAL=2;BYDAY=1TU,-1TU,2WE' rule.to_s # 'Every 2 days on the last and 1st Tuesdays and the 2nd Wednesday' ``` --- ## Some types of Rules There are many types of recurrence rules that can be added to a schedule: ### Daily ```ruby # every day schedule.add_recurrence_rule Rule.daily # every third day schedule.add_recurrence_rule Rule.daily(3) ``` ### Weekly ```ruby # every week schedule.add_recurrence_rule Rule.weekly # every other week on monday and tuesday schedule.add_recurrence_rule Rule.weekly(2).day(:monday, :tuesday) # for programmatic convenience (same as above) schedule.add_recurrence_rule Rule.weekly(2).day(1, 2) # specifying a weekly interval with a different first weekday (defaults to Sunday) schedule.add_recurrence_rule Rule.weekly(1, :monday) ``` ### Monthly (by day of month) ```ruby # every month on the first and last days of the month schedule.add_recurrence_rule Rule.monthly.day_of_month(1, -1) # every other month on the 15th of the month schedule.add_recurrence_rule Rule.monthly(2).day_of_month(15) ``` Monthly rules will skip months that are too short for the specified day of month (e.g. no occurrences in February for `day_of_month(31)`). ### Monthly (by day of Nth week) ```ruby # every month on the first and last tuesdays of the month schedule.add_recurrence_rule Rule.monthly.day_of_week(:tuesday => [1, -1]) # every other month on the first monday and last tuesday schedule.add_recurrence_rule Rule.monthly(2).day_of_week( :monday => [1], :tuesday => [-1] ) # for programmatic convenience (same as above) schedule.add_recurrence_rule Rule.monthly(2).day_of_week(1 => [1], 2 => [-1]) ``` ### Yearly (by day of year) ```ruby # every year on the 100th days from the beginning and end of the year schedule.add_recurrence_rule Rule.yearly.day_of_year(100, -100) # every fourth year on new year's eve schedule.add_recurrence_rule Rule.yearly(4).day_of_year(-1) ``` ### Yearly (by month of year) ```ruby # every year on the same day as start_date but in january and february schedule.add_recurrence_rule Rule.yearly.month_of_year(:january, :februrary) # every third year in march schedule.add_recurrence_rule Rule.yearly(3).month_of_year(:march) # for programatic convenience (same as above) schedule.add_recurrence_rule Rule.yearly(3).month_of_year(3) ``` ### Hourly (by hour of day) ```ruby # every hour on the same minute and second as start date schedule.add_recurrence_rule Rule.hourly # every other hour, on mondays schedule.add_recurrence_rule Rule.hourly(2).day(:monday) ``` ### Minutely (every N minutes) ```ruby # every 10 minutes schedule.add_recurrence_rule Rule.minutely(10) # every hour and a half, on the last tuesday of the month schedule.add_recurrence_rule Rule.minutely(90).day_of_week(:tuesday => [-1]) ``` ### Secondly (every N seconds) ```ruby # every second schedule.add_recurrence_rule Rule.secondly # every 15 seconds between 12:00 - 12:59 schedule.add_recurrence_rule Rule.secondly(15).hour_of_day(12) ``` --- ## recurring_select The team over at [GetJobber](http://getjobber.com/) have open-sourced RecurringSelect, which makes working with IceCube easier in a Rails app via some nice helpers. Check it out at https://github.com/GetJobber/recurring_select --- ## Contributors * Andrew Vit ([@avit][github-avit]) * Mat Brown - mat@patch.com * Philip Roberts * @sakrafd --- ## Issues? Use the GitHub [issue tracker][ice_cube-issues] ## Contributing * Contributions are welcome - I use GitHub for issue tracking (accompanying failing tests are awesome) and feature requests * Submit via fork and pull request (include tests) * If you're working on something major, shoot me a message beforehand [ical-3.6.1]: https://tools.ietf.org/html/rfc5545#section-3.6.1 [github-avit]: https://github.com/avit/ [travis-ice_cube]: http://travis-ci.org/seejohnrun/ice_cube [travis-ice_cube-png]: https://secure.travis-ci.org/seejohnrun/ice_cube.png [ice_cube-lone_star_pdf]: http://seejohnrun.github.com/ice_cube/static/lsrc_ice_cube.pdf [ice_cube-ruby_nyc_pdf]: http://seejohnrun.github.com/ice_cube/static/ice_cube_ruby_nyc.pdf [ice_cube-docs]: http://seejohnrun.github.com/ice_cube/ [ice_cube-issues]: https://github.com/seejohnrun/ice_cube/issues ice_cube-0.12.1/Rakefile000066400000000000000000000007201235562124600150030ustar00rootroot00000000000000require 'rspec/core/rake_task' require File.dirname(__FILE__) + '/lib/ice_cube/version' task :build => :test do system "gem build ice_cube.gemspec" end task :release => :build do # tag and push system "git tag v#{IceCube::VERSION}" system "git push origin --tags" # push the gem system "gem push ice_cube-#{IceCube::VERSION}.gem" end RSpec::Core::RakeTask.new(:test) do |t| t.pattern = 'spec/**/*_spec.rb' fail_on_error = true # be explicit end ice_cube-0.12.1/UPGRADING.md000066400000000000000000000011361235562124600152020ustar00rootroot00000000000000# Upgrading ## 0.12.0 Previous versions used `start_date` as a hash key. This version introduces a deprecation by renaming to `start_time` for consistency: see [issue #102][issue-102]. The old name will continue to be recognized when reading seralized schedules from previous versions, and we will default to exporting serialized schedules with *both* names. You can disable this duplication by setting `IceCube.compatibility = 12` after your downstream code is updated to look for `start_time`. Watch for deprecation notices in your log. [issue-102]: https://github.com/seejohnrun/ice_cube/issues/102 ice_cube-0.12.1/ice_cube.gemspec000066400000000000000000000016241235562124600164450ustar00rootroot00000000000000require File.dirname(__FILE__) + '/lib/ice_cube/version' Gem::Specification.new do |s| s.name = 'ice_cube' s.summary = 'Ruby Date Recurrence Library' s.description = 'ice_cube is a recurring date library for Ruby. It allows for quick, programatic expansion of recurring date rules.' s.author = 'John Crepezzi' s.email = 'john@crepezzi.com' s.homepage = 'http://seejohnrun.github.com/ice_cube/' s.license = 'MIT' s.version = IceCube::VERSION s.platform = Gem::Platform::RUBY s.files = Dir['lib/**/*.rb'] s.test_files = Dir.glob('spec/*.rb') s.require_paths = ['lib'] s.has_rdoc = true s.rubyforge_project = "ice-cube" s.add_development_dependency('rake') s.add_development_dependency('rspec', '~> 2.12.0') s.add_development_dependency('activesupport', '>= 3.0.0') s.add_development_dependency('tzinfo') end ice_cube-0.12.1/lib/000077500000000000000000000000001235562124600141055ustar00rootroot00000000000000ice_cube-0.12.1/lib/ice_cube.rb000066400000000000000000000062321235562124600161730ustar00rootroot00000000000000require 'date' require 'ice_cube/deprecated' module IceCube autoload :VERSION, 'ice_cube/version' autoload :TimeUtil, 'ice_cube/time_util' autoload :FlexibleHash, 'ice_cube/flexible_hash' autoload :Rule, 'ice_cube/rule' autoload :Schedule, 'ice_cube/schedule' autoload :Occurrence, 'ice_cube/occurrence' autoload :IcalBuilder, 'ice_cube/builders/ical_builder' autoload :HashBuilder, 'ice_cube/builders/hash_builder' autoload :StringBuilder, 'ice_cube/builders/string_builder' autoload :HashParser, 'ice_cube/parsers/hash_parser' autoload :YamlParser, 'ice_cube/parsers/yaml_parser' autoload :CountExceeded, 'ice_cube/errors/count_exceeded' autoload :UntilExceeded, 'ice_cube/errors/until_exceeded' autoload :ValidatedRule, 'ice_cube/validated_rule' autoload :SingleOccurrenceRule, 'ice_cube/single_occurrence_rule' autoload :SecondlyRule, 'ice_cube/rules/secondly_rule' autoload :MinutelyRule, 'ice_cube/rules/minutely_rule' autoload :HourlyRule, 'ice_cube/rules/hourly_rule' autoload :DailyRule, 'ice_cube/rules/daily_rule' autoload :WeeklyRule, 'ice_cube/rules/weekly_rule' autoload :MonthlyRule, 'ice_cube/rules/monthly_rule' autoload :YearlyRule, 'ice_cube/rules/yearly_rule' module Validations autoload :Lock, 'ice_cube/validations/lock' autoload :ScheduleLock, 'ice_cube/validations/schedule_lock' autoload :Count, 'ice_cube/validations/count' autoload :Until, 'ice_cube/validations/until' autoload :SecondlyInterval, 'ice_cube/validations/secondly_interval' autoload :MinutelyInterval, 'ice_cube/validations/minutely_interval' autoload :DailyInterval, 'ice_cube/validations/daily_interval' autoload :WeeklyInterval, 'ice_cube/validations/weekly_interval' autoload :MonthlyInterval, 'ice_cube/validations/monthly_interval' autoload :YearlyInterval, 'ice_cube/validations/yearly_interval' autoload :HourlyInterval, 'ice_cube/validations/hourly_interval' autoload :HourOfDay, 'ice_cube/validations/hour_of_day' autoload :MonthOfYear, 'ice_cube/validations/month_of_year' autoload :MinuteOfHour, 'ice_cube/validations/minute_of_hour' autoload :SecondOfMinute, 'ice_cube/validations/second_of_minute' autoload :DayOfMonth, 'ice_cube/validations/day_of_month' autoload :DayOfWeek, 'ice_cube/validations/day_of_week' autoload :Day, 'ice_cube/validations/day' autoload :DayOfYear, 'ice_cube/validations/day_of_year' end # Define some useful constants ONE_SECOND = 1 ONE_MINUTE = ONE_SECOND * 60 ONE_HOUR = ONE_MINUTE * 60 ONE_DAY = ONE_HOUR * 24 ONE_WEEK = ONE_DAY * 7 # Defines the format used by IceCube when printing out Schedule#to_s. # Defaults to '%B %e, %Y' def self.to_s_time_format @to_s_time_format ||= '%B %e, %Y' end # Sets the format used by IceCube when printing out Schedule#to_s. def self.to_s_time_format=(format) @to_s_time_format = format end # Retain backwards compatibility for schedules exported from older versions # This represents the version number, 11 = 0.11, 1.0 will be 100 def self.compatibility @compatibility ||= 11 end def self.compatibility=(version) @compatibility = version end end ice_cube-0.12.1/lib/ice_cube/000077500000000000000000000000001235562124600156435ustar00rootroot00000000000000ice_cube-0.12.1/lib/ice_cube/builders/000077500000000000000000000000001235562124600174545ustar00rootroot00000000000000ice_cube-0.12.1/lib/ice_cube/builders/hash_builder.rb000066400000000000000000000005721235562124600224360ustar00rootroot00000000000000module IceCube class HashBuilder def initialize(rule = nil) @hash = { :validations => {}, :rule_type => rule.class.name } end def validations @hash[:validations] end def []=(key, value) @hash[key] = value end def validations_array(type) validations[type] ||= [] end def to_hash @hash end end end ice_cube-0.12.1/lib/ice_cube/builders/ical_builder.rb000066400000000000000000000023771235562124600224300ustar00rootroot00000000000000module IceCube class IcalBuilder ICAL_DAYS = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'] def initialize @hash = {} end def self.fixnum_to_ical_day(num) ICAL_DAYS[num] end def [](key) @hash[key] ||= [] end # Build for a single rule entry def to_s arr = [] if freq = @hash.delete('FREQ') arr << "FREQ=#{freq.join(',')}" end arr.concat(@hash.map do |key, value| if value.is_a?(Array) "#{key}=#{value.join(',')}" end end.compact) arr.join(';') end def self.ical_utc_format(time) time = time.dup.utc "#{time.strftime('%Y%m%dT%H%M%SZ')}" # utc time end def self.ical_format(time, force_utc) time = time.dup.utc if force_utc if time.utc? ":#{time.strftime('%Y%m%dT%H%M%SZ')}" # utc time else ";TZID=#{time.strftime('%Z:%Y%m%dT%H%M%S')}" # local time specified end end def self.ical_duration(duration) hours = duration / 3600; duration %= 3600 minutes = duration / 60; duration %= 60 repr = '' repr << "#{hours}H" if hours > 0 repr << "#{minutes}M" if minutes > 0 repr << "#{duration}S" if duration > 0 "PT#{repr}" end end end ice_cube-0.12.1/lib/ice_cube/builders/string_builder.rb000066400000000000000000000026621235562124600230230ustar00rootroot00000000000000module IceCube class StringBuilder attr_writer :base def initialize @types = {} end def piece(type, prefix = nil, suffix = nil) @types[type] ||= [] end def to_s @types.each_with_object(@base || '') do |(type, segments), str| if f = self.class.formatter(type) str << ' ' << f.call(segments) else next if segments.empty? str << ' ' << self.class.sentence(segments) end end end def self.formatter(type) @formatters[type] end def self.register_formatter(type, &formatter) @formatters ||= {} @formatters[type] = formatter end module Helpers NUMBER_SUFFIX = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th'] SPECIAL_SUFFIX = { 11 => 'th', 12 => 'th', 13 => 'th', 14 => 'th' } # influenced by ActiveSupport's to_sentence def sentence(array) case array.length when 0 ; '' when 1 ; array[0].to_s when 2 ; "#{array[0]} and #{array[1]}" else ; "#{array[0...-1].join(', ')}, and #{array[-1]}" end end def nice_number(number) return 'last' if number == -1 suffix = SPECIAL_SUFFIX[number] || NUMBER_SUFFIX[number.abs % 10] if number < -1 number.abs.to_s << suffix << ' to last' else number.to_s << suffix end end end extend Helpers end end ice_cube-0.12.1/lib/ice_cube/deprecated.rb000066400000000000000000000025231235562124600202720ustar00rootroot00000000000000module IceCube module Deprecated # Define a deprecated alias for a method # @param [Symbol] name - name of method to define # @param [Symbol] replacement - name of method to replace (alias) def deprecated_alias(name, replacement) # Create a wrapped version define_method(name) do |*args, &block| warn "IceCube: #{self.class}##{name} is deprecated, please use ##{replacement} at: #{ caller[0] }" send replacement, *args, &block end end # Deprecate a defined method # @param [Symbol] name - name of deprecated method # @param [Symbol] replacement - name of the desired replacement def deprecated(name, replacement) # Replace old method old_name = :"#{name}_without_deprecation" alias_method old_name, name # And replace it with a wrapped version define_method(name) do |*args, &block| warn "IceCube: #{self.class}##{name} is deprecated, please use ##{replacement} at: #{ caller[0] }" send old_name, *args, &block end end def self.schedule_options(schedule, options) if options[:start_date_override] warn "IceCube: :start_date_override option is deprecated, please use a block {|s| s.start_time = override }. at: #{ caller[0] }" schedule.start_time = options[:start_date_override] end end end end ice_cube-0.12.1/lib/ice_cube/errors/000077500000000000000000000000001235562124600171575ustar00rootroot00000000000000ice_cube-0.12.1/lib/ice_cube/errors/count_exceeded.rb000066400000000000000000000001671235562124600224660ustar00rootroot00000000000000module IceCube # An exception for when a count on a Rule is passed class CountExceeded < StopIteration end end ice_cube-0.12.1/lib/ice_cube/errors/until_exceeded.rb000066400000000000000000000001751235562124600224700ustar00rootroot00000000000000module IceCube # An exception for when an until date on a Rule is passed class UntilExceeded < StopIteration end end ice_cube-0.12.1/lib/ice_cube/flexible_hash.rb000066400000000000000000000013701235562124600207660ustar00rootroot00000000000000require 'delegate' module IceCube # Find keys by symbol or string without symbolizing user input # Due to the serialization format of ice_cube, this limited implementation # is entirely sufficient class FlexibleHash < SimpleDelegator def [](key) key = _match_key(key) super end def fetch(key) key = _match_key(key) super end def delete(key) key = _match_key(key) super end private def _match_key(key) return key if __getobj__.has_key? key if Symbol == key.class __getobj__.keys.detect { |k| return k if k == key.to_s } elsif String == key.class __getobj__.keys.detect { |k| return k if k.to_s == key } end key end end end ice_cube-0.12.1/lib/ice_cube/occurrence.rb000066400000000000000000000054161235562124600203260ustar00rootroot00000000000000require 'forwardable' require 'delegate' module IceCube # Wraps start_time and end_time in a single concept concerning the duration. # This delegates to the enclosed start_time so it behaves like a normal Time # in almost all situations, however: # # Without ActiveSupport, it's necessary to cast the occurrence using # +#to_time+ before doing arithmetic, else Time will try to subtract it # using +#to_i+ and return a new time instead. # # Time.now - Occurrence.new(start_time) # => 1970-01-01 01:00:00 # Time.now - Occurrence.new(start_time).to_time # => 3600 # # When ActiveSupport::Time core extensions are loaded, it's possible to # subtract an Occurrence object directly from a Time to get the difference: # # Time.now - Occurrence.new(start_time) # => 3600 # class Occurrence < SimpleDelegator # Report class name as 'Time' to thwart type checking. def self.name 'Time' end # Optimize for common methods to avoid method_missing extend Forwardable def_delegators :start_time, :to_i, :<=>, :== def_delegators :to_range, :cover?, :include?, :each, :first, :last attr_reader :start_time, :end_time def initialize(start_time, end_time=nil) @start_time = start_time @end_time = end_time || start_time __setobj__ @start_time end def is_a?(klass) klass == ::Time || super end alias_method :kind_of?, :is_a? def intersects? other if other.is_a?(Occurrence) || other.is_a?(Range) lower_bound_1 = first + 1 upper_bound_1 = last # exclude end lower_bound_2 = other.first + 1 upper_bound_2 = other.last + 1 if (lower_bound_2 <=> upper_bound_2) > 0 false elsif (lower_bound_1 <=> upper_bound_1) > 0 false else (upper_bound_1 <=> lower_bound_2) >= 0 and (upper_bound_2 <=> lower_bound_1) >= 0 end else cover? other end end def comparable_time start_time end def duration end_time - start_time end def to_range start_time..end_time end def to_time start_time end # Shows both the start and end time if there is a duration. # Optional format argument (e.g. :long, :short) supports Rails # time formats and is only used when ActiveSupport is available. # def to_s(format=nil) if format && to_time.public_method(:to_s).arity > 0 t0, t1 = start_time.to_s(format), end_time.to_s(format) else t0, t1 = start_time.to_s, end_time.to_s end duration > 0 ? "#{t0} - #{t1}" : t0 end def overnight? offset = start_time + 3600 * 24 midnight = Time.new(offset.year, offset.month, offset.day) midnight < end_time end end end ice_cube-0.12.1/lib/ice_cube/parsers/000077500000000000000000000000001235562124600173225ustar00rootroot00000000000000ice_cube-0.12.1/lib/ice_cube/parsers/hash_parser.rb000066400000000000000000000043661235562124600221570ustar00rootroot00000000000000module IceCube class HashParser attr_reader :hash def initialize(original_hash) @hash = original_hash end def to_schedule data = normalize_keys(hash) schedule = IceCube::Schedule.new parse_time(data[:start_time]) apply_duration schedule, data apply_end_time schedule, data apply_rrules schedule, data apply_exrules schedule, data apply_rtimes schedule, data apply_extimes schedule, data yield schedule if block_given? schedule end private def normalize_keys(hash) data = IceCube::FlexibleHash.new(hash.dup) if (start_date = data.delete(:start_date)) warn "IceCube: :start_date is deprecated, please use :start_time at: #{ caller[0] }" data[:start_time] = start_date end {:rdates => :rtimes, :exdates => :extimes}.each do |old_key, new_key| if (times = data.delete(old_key)) warn "IceCube: :#{old_key} is deprecated, please use :#{new_key} at: #{ caller[0] }" (data[new_key] ||= []).concat times end end data end def apply_duration(schedule, data) return unless data[:duration] schedule.duration = data[:duration].to_i end def apply_end_time(schedule, data) return unless data[:end_time] schedule.end_time = parse_time(data[:end_time]) end def apply_rrules(schedule, data) return unless data[:rrules] data[:rrules].each do |h| schedule.rrule(IceCube::Rule.from_hash(h)) end end def apply_exrules(schedule, data) return unless data[:exrules] warn "IceCube: :exrules is deprecated, and will be removed in a future release. at: #{ caller[0] }" data[:exrules].each do |h| schedule.exrule(IceCube::Rule.from_hash(h)) end end def apply_rtimes(schedule, data) return unless data[:rtimes] data[:rtimes].each do |t| schedule.add_recurrence_time TimeUtil.deserialize_time(t) end end def apply_extimes(schedule, data) return unless data[:extimes] data[:extimes].each do |t| schedule.add_exception_time TimeUtil.deserialize_time(t) end end def parse_time(time) TimeUtil.deserialize_time(time) end end end ice_cube-0.12.1/lib/ice_cube/parsers/yaml_parser.rb000066400000000000000000000006461235562124600221730ustar00rootroot00000000000000require 'yaml' module IceCube class YamlParser < HashParser SERIALIZED_START = /start_(?:time|date): .+(?(?:-|\+)\d{2}:\d{2})$/ attr_reader :hash def initialize(yaml) @hash = YAML::load(yaml) yaml.match SERIALIZED_START do |match| start_time = hash[:start_time] || hash[:start_date] TimeUtil.restore_deserialized_offset start_time, match[:tz] end end end end ice_cube-0.12.1/lib/ice_cube/rule.rb000066400000000000000000000050571235562124600171460ustar00rootroot00000000000000require 'yaml' module IceCube class Rule attr_reader :uses # Is this a terminating schedule? def terminating? until_time || occurrence_count end def ==(rule) if rule.is_a? Rule hash = to_hash hash && hash == rule.to_hash end end def hash h = to_hash h.nil? ? super : h.hash end def to_ical raise MethodNotImplemented, "Expected to be overrridden by subclasses" end # Yaml implementation def to_yaml(*args) YAML::dump(to_hash, *args) end # From yaml def self.from_yaml(yaml) from_hash YAML::load(yaml) end def to_hash raise MethodNotImplemented, "Expected to be overridden by subclasses" end # Convert from a hash and create a rule def self.from_hash(original_hash) hash = IceCube::FlexibleHash.new original_hash return nil unless match = hash[:rule_type].match(/\:\:(.+?)Rule/) rule = IceCube::Rule.send(match[1].downcase.to_sym, hash[:interval] || 1) rule.interval(hash[:interval] || 1, TimeUtil.wday_to_sym(hash[:week_start] || 0)) if match[1] == "Weekly" rule.until(TimeUtil.deserialize_time(hash[:until])) if hash[:until] rule.count(hash[:count]) if hash[:count] hash[:validations] && hash[:validations].each do |key, value| key = key.to_sym unless key.is_a?(Symbol) value.is_a?(Array) ? rule.send(key, *value) : rule.send(key, value) end rule end # Reset the uses on the rule to 0 def reset @uses = 0 end def next_time(time, schedule, closing_time) end def on?(time, schedule) next_time(time, schedule, time).to_i == time.to_i end # Whether this rule requires a full run def full_required? !@count.nil? end # Convenience methods for creating Rules class << self # Secondly Rule def secondly(interval = 1) SecondlyRule.new(interval) end # Minutely Rule def minutely(interval = 1) MinutelyRule.new(interval) end # Hourly Rule def hourly(interval = 1) HourlyRule.new(interval) end # Daily Rule def daily(interval = 1) DailyRule.new(interval) end # Weekly Rule def weekly(interval = 1, week_start = :sunday) WeeklyRule.new(interval, week_start) end # Monthly Rule def monthly(interval = 1) MonthlyRule.new(interval) end # Yearly Rule def yearly(interval = 1) YearlyRule.new(interval) end end end end ice_cube-0.12.1/lib/ice_cube/rules/000077500000000000000000000000001235562124600167755ustar00rootroot00000000000000ice_cube-0.12.1/lib/ice_cube/rules/daily_rule.rb000066400000000000000000000003761235562124600214610ustar00rootroot00000000000000module IceCube class DailyRule < ValidatedRule include Validations::DailyInterval def initialize(interval = 1, week_start = :sunday) super interval(interval) schedule_lock(:hour, :min, :sec) reset end end end ice_cube-0.12.1/lib/ice_cube/rules/hourly_rule.rb000066400000000000000000000003711235562124600216740ustar00rootroot00000000000000module IceCube class HourlyRule < ValidatedRule include Validations::HourlyInterval def initialize(interval = 1, week_start = :sunday) super interval(interval) schedule_lock(:min, :sec) reset end end end ice_cube-0.12.1/lib/ice_cube/rules/minutely_rule.rb000066400000000000000000000003671235562124600222250ustar00rootroot00000000000000module IceCube class MinutelyRule < ValidatedRule include Validations::MinutelyInterval def initialize(interval = 1, week_start = :sunday) super interval(interval) schedule_lock(:sec) reset end end end ice_cube-0.12.1/lib/ice_cube/rules/monthly_rule.rb000066400000000000000000000004101235562124600220360ustar00rootroot00000000000000module IceCube class MonthlyRule < ValidatedRule include Validations::MonthlyInterval def initialize(interval = 1, week_start = :sunday) super interval(interval) schedule_lock(:day, :hour, :min, :sec) reset end end end ice_cube-0.12.1/lib/ice_cube/rules/secondly_rule.rb000066400000000000000000000003351235562124600221720ustar00rootroot00000000000000module IceCube class SecondlyRule < ValidatedRule include Validations::SecondlyInterval def initialize(interval = 1, week_start = :sunday) super interval(interval) reset end end end ice_cube-0.12.1/lib/ice_cube/rules/weekly_rule.rb000066400000000000000000000004231235562124600216500ustar00rootroot00000000000000module IceCube class WeeklyRule < ValidatedRule include Validations::WeeklyInterval def initialize(interval = 1, week_start = :sunday) super interval(interval, week_start) schedule_lock(:wday, :hour, :min, :sec) reset end end end ice_cube-0.12.1/lib/ice_cube/rules/yearly_rule.rb000066400000000000000000000004161235562124600216570ustar00rootroot00000000000000module IceCube class YearlyRule < ValidatedRule include Validations::YearlyInterval def initialize(interval = 1, week_start = :sunday) super interval(interval) schedule_lock(:month, :day, :hour, :min, :sec) reset end end end ice_cube-0.12.1/lib/ice_cube/schedule.rb000066400000000000000000000375121235562124600177740ustar00rootroot00000000000000require 'yaml' module IceCube class Schedule extend Deprecated # Get the start time attr_reader :start_time deprecated_alias :start_date, :start_time # Get the end time attr_reader :end_time deprecated_alias :end_date, :end_time # Create a new schedule def initialize(start_time = nil, options = {}) self.start_time = start_time || TimeUtil.now self.end_time = self.start_time + options[:duration] if options[:duration] self.end_time = options[:end_time] if options[:end_time] @all_recurrence_rules = [] @all_exception_rules = [] yield self if block_given? end # Set start_time def start_time=(start_time) @start_time = TimeUtil.ensure_time start_time end deprecated_alias :start_date=, :start_time= # Set end_time def end_time=(end_time) @end_time = TimeUtil.ensure_time end_time end deprecated_alias :end_date=, :end_time= def duration end_time ? end_time - start_time : 0 end def duration=(seconds) @end_time = start_time + seconds end # Add a recurrence time to the schedule def add_recurrence_time(time) return nil if time.nil? rule = SingleOccurrenceRule.new(time) add_recurrence_rule rule time end alias :rtime :add_recurrence_time deprecated_alias :rdate, :rtime deprecated_alias :add_recurrence_date, :add_recurrence_time # Add an exception time to the schedule def add_exception_time(time) return nil if time.nil? rule = SingleOccurrenceRule.new(time) add_exception_rule rule time end alias :extime :add_exception_time deprecated_alias :exdate, :extime deprecated_alias :add_exception_date, :add_exception_time # Add a recurrence rule to the schedule def add_recurrence_rule(rule) @all_recurrence_rules << rule unless @all_recurrence_rules.include?(rule) end alias :rrule :add_recurrence_rule # Remove a recurrence rule def remove_recurrence_rule(rule) res = @all_recurrence_rules.delete(rule) res.nil? ? [] : [res] end # Add an exception rule to the schedule def add_exception_rule(rule) @all_exception_rules << rule unless @all_exception_rules.include?(rule) end alias :exrule :add_exception_rule # Remove an exception rule def remove_exception_rule(rule) res = @all_exception_rules.delete(rule) res.nil? ? [] : [res] end # Get the recurrence rules def recurrence_rules @all_recurrence_rules.reject { |r| r.is_a?(SingleOccurrenceRule) } end alias :rrules :recurrence_rules # Get the exception rules def exception_rules @all_exception_rules.reject { |r| r.is_a?(SingleOccurrenceRule) } end alias :exrules :exception_rules # Get the recurrence times that are on the schedule def recurrence_times @all_recurrence_rules.select { |r| r.is_a?(SingleOccurrenceRule) }.map(&:time) end alias :rtimes :recurrence_times deprecated_alias :rdates, :rtimes deprecated_alias :recurrence_dates, :recurrence_times # Remove a recurrence time def remove_recurrence_time(time) found = false @all_recurrence_rules.delete_if do |rule| found = true if rule.is_a?(SingleOccurrenceRule) && rule.time == time end time if found end alias :remove_rtime :remove_recurrence_time deprecated_alias :remove_recurrence_date, :remove_recurrence_time deprecated_alias :remove_rdate, :remove_rtime # Get the exception times that are on the schedule def exception_times @all_exception_rules.select { |r| r.is_a?(SingleOccurrenceRule) }.map(&:time) end alias :extimes :exception_times deprecated_alias :exdates, :extimes deprecated_alias :exception_dates, :exception_times # Remove an exception time def remove_exception_time(time) found = false @all_exception_rules.delete_if do |rule| found = true if rule.is_a?(SingleOccurrenceRule) && rule.time == time end time if found end alias :remove_extime :remove_exception_time deprecated_alias :remove_exception_date, :remove_exception_time deprecated_alias :remove_exdate, :remove_extime # Get all of the occurrences from the start_time up until a # given Time def occurrences(closing_time) enumerate_occurrences(start_time, closing_time).to_a end # All of the occurrences def all_occurrences require_terminating_rules enumerate_occurrences(start_time).to_a end # Emit an enumerator based on the start time def all_occurrences_enumerator enumerate_occurrences(start_time) end # Iterate forever def each_occurrence(&block) enumerate_occurrences(start_time, &block).to_a self end # The next n occurrences after now def next_occurrences(num, from = nil) from ||= TimeUtil.now(@start_time) enumerate_occurrences(from + 1, nil).take(num) end # The next occurrence after now (overridable) def next_occurrence(from = nil) from ||= TimeUtil.now(@start_time) begin enumerate_occurrences(from + 1, nil).next() rescue StopIteration nil end end # The previous occurrence from a given time def previous_occurrence(from) return nil if from <= start_time enumerate_occurrences(start_time, from - 1).to_a.last end # The previous n occurrences before a given time def previous_occurrences(num, from) return [] if from <= start_time a = enumerate_occurrences(start_time, from - 1).to_a a.size > num ? a[-1*num,a.size] : a end # The remaining occurrences (same requirements as all_occurrences) def remaining_occurrences(from = nil) require_terminating_rules from ||= TimeUtil.now(@start_time) enumerate_occurrences(from).to_a end # Returns an enumerator for all remaining occurrences def remaining_occurrences_enumerator(from = nil) from ||= TimeUtil.now(@start_time) enumerate_occurrences(from) end # Occurrences between two times def occurrences_between(begin_time, closing_time) enumerate_occurrences(begin_time, closing_time).to_a end # Return a boolean indicating if an occurrence falls between two times def occurs_between?(begin_time, closing_time) begin enumerate_occurrences(begin_time, closing_time).next() true rescue StopIteration false end end # Return a boolean indicating if an occurrence is occurring between two # times, inclusive of its duration. This counts zero-length occurrences # that intersect the start of the range and within the range, but not # occurrences at the end of the range since none of their duration # intersects the range. def occurring_between?(opening_time, closing_time) opening_time = opening_time - duration closing_time = closing_time - 1 if duration > 0 occurs_between?(opening_time, closing_time) end # Return a boolean indicating if an occurrence falls on a certain date def occurs_on?(date) date = TimeUtil.ensure_date date begin_time = TimeUtil.beginning_of_date(date, start_time) closing_time = TimeUtil.end_of_date(date, start_time) occurs_between?(begin_time, closing_time) end # Determine if the schedule is occurring at a given time def occurring_at?(time) if duration > 0 return false if exception_time?(time) occurs_between?(time - duration + 1, time) else occurs_at?(time) end end # Determine if this schedule conflicts with another schedule # @param [IceCube::Schedule] other_schedule - The schedule to compare to # @param [Time] closing_time - the last time to consider # @return [Boolean] whether or not the schedules conflict at all def conflicts_with?(other_schedule, closing_time = nil) closing_time = TimeUtil.ensure_time closing_time unless terminating? || other_schedule.terminating? || closing_time raise ArgumentError, "One or both schedules must be terminating to use #conflicts_with?" end # Pick the terminating schedule, and other schedule # No need to reverse if terminating? or there is a closing time terminating_schedule = self unless terminating? || closing_time terminating_schedule, other_schedule = other_schedule, terminating_schedule end # Go through each occurrence of the terminating schedule and determine # if the other occurs at that time # last_time = nil terminating_schedule.each_occurrence do |time| if closing_time && time > closing_time last_time = closing_time break end last_time = time return true if other_schedule.occurring_at?(time) end # Due to durations, we need to walk up to the end time, and verify in the # other direction if last_time last_time += terminating_schedule.duration other_schedule.each_occurrence do |time| break if time > last_time return true if terminating_schedule.occurring_at?(time) end end # No conflict, return false false end # Determine if the schedule occurs at a specific time def occurs_at?(time) occurs_between?(time, time) end # Get the first n occurrences, or the first occurrence if n is skipped def first(n = nil) occurrences = enumerate_occurrences(start_time).take(n || 1) n.nil? ? occurrences.first : occurrences end # Get the final n occurrences of a terminating schedule # or the final one if no n is given def last(n = nil) require_terminating_rules occurrences = enumerate_occurrences(start_time).to_a n.nil? ? occurrences.last : occurrences[-n..-1] end # String serialization def to_s pieces = [] rd = recurrence_times_with_start_time - extimes pieces.concat rd.sort.map { |t| t.strftime(IceCube.to_s_time_format) } pieces.concat rrules.map { |t| t.to_s } pieces.concat exrules.map { |t| "not #{t.to_s}" } pieces.concat extimes.sort.map { |t| "not on #{t.strftime(IceCube.to_s_time_format)}" } pieces.join(' / ') end # Serialize this schedule to_ical def to_ical(force_utc = false) pieces = [] pieces << "DTSTART#{IcalBuilder.ical_format(start_time, force_utc)}" pieces.concat recurrence_rules.map { |r| "RRULE:#{r.to_ical}" } pieces.concat exception_rules.map { |r| "EXRULE:#{r.to_ical}" } pieces.concat recurrence_times_without_start_time.map { |t| "RDATE#{IcalBuilder.ical_format(t, force_utc)}" } pieces.concat exception_times.map { |t| "EXDATE#{IcalBuilder.ical_format(t, force_utc)}" } pieces << "DTEND#{IcalBuilder.ical_format(end_time, force_utc)}" if end_time pieces.join("\n") end # Convert the schedule to yaml def to_yaml(*args) YAML::dump(to_hash, *args) end # Load the schedule from yaml def self.from_yaml(yaml, options = {}) YamlParser.new(yaml).to_schedule do |schedule| Deprecated.schedule_options(schedule, options) yield schedule if block_given? end end # Convert the schedule to a hash def to_hash data = {} data[:start_time] = TimeUtil.serialize_time(start_time) data[:start_date] = data[:start_time] if IceCube.compatibility <= 11 data[:end_time] = TimeUtil.serialize_time(end_time) if end_time data[:rrules] = recurrence_rules.map(&:to_hash) if IceCube.compatibility <= 11 && exception_rules.any? data[:exrules] = exception_rules.map(&:to_hash) end data[:rtimes] = recurrence_times.map do |rt| TimeUtil.serialize_time(rt) end data[:extimes] = exception_times.map do |et| TimeUtil.serialize_time(et) end data end # Load the schedule from a hash def self.from_hash(original_hash, options = {}) HashParser.new(original_hash).to_schedule do |schedule| Deprecated.schedule_options(schedule, options) yield schedule if block_given? end end # Determine if the schedule will end # @return [Boolean] true if ending, false if repeating forever def terminating? recurrence_rules.empty? || recurrence_rules.all?(&:terminating?) end def self.dump(schedule) return schedule if schedule.nil? || schedule == "" schedule.to_yaml end def self.load(yaml) return yaml if yaml.nil? || yaml == "" from_yaml(yaml) end private # Reset all rules for another run def reset @all_recurrence_rules.each(&:reset) @all_exception_rules.each(&:reset) end # Find all of the occurrences for the schedule between opening_time # and closing_time # Iteration is unrolled in pairs to skip duplicate times in end of DST def enumerate_occurrences(opening_time, closing_time = nil, &block) opening_time = TimeUtil.match_zone(opening_time, start_time) closing_time = TimeUtil.match_zone(closing_time, start_time) opening_time += start_time.subsec - opening_time.subsec rescue 0 reset opening_time = start_time if opening_time < start_time t1 = full_required? ? start_time : opening_time e = Enumerator.new do |yielder| loop do break unless (t0 = next_time(t1, closing_time)) break if closing_time && t0 > closing_time yielder << (block_given? ? block.call(t0) : t0) if t0 >= opening_time break unless (t1 = next_time(t0 + 1, closing_time)) break if closing_time && t1 > closing_time if TimeUtil.same_clock?(t0, t1) && recurrence_rules.any?(&:dst_adjust?) wind_back_dst next (t1 += 1) end yielder << (block_given? ? block.call(t1) : t1) if t1 >= opening_time next (t1 += 1) end end end # Get the next time after (or including) a specific time def next_time(time, closing_time) loop do min_time = recurrence_rules_with_implicit_start_occurrence.reduce(nil) do |min_time, rule| begin new_time = rule.next_time(time, self, min_time || closing_time) [min_time, new_time].compact.min rescue StopIteration min_time end end break nil unless min_time next (time = min_time + 1) if exception_time?(min_time) break Occurrence.new(min_time, min_time + duration) end end # Indicate if any rule needs to be run from the start of time # If we have rules with counts, we need to walk from the beginning of time def full_required? @all_recurrence_rules.any?(&:full_required?) || @all_exception_rules.any?(&:full_required?) end # Return a boolean indicating whether or not a specific time # is excluded from the schedule def exception_time?(time) @all_exception_rules.any? do |rule| rule.on?(time, self) end end def require_terminating_rules return true if terminating? method_name = caller[0].split(' ').last raise ArgumentError, "All recurrence rules must specify .until or .count to use #{method_name}" end def implicit_start_occurrence_rule SingleOccurrenceRule.new(start_time) end def recurrence_times_without_start_time recurrence_times.reject { |t| t == start_time } end def recurrence_times_with_start_time if recurrence_rules.empty? [start_time].concat recurrence_times_without_start_time else recurrence_times end end def recurrence_rules_with_implicit_start_occurrence if recurrence_rules.empty? [implicit_start_occurrence_rule].concat @all_recurrence_rules else @all_recurrence_rules end end def wind_back_dst recurrence_rules.each do |rule| rule.skipped_for_dst end end end end ice_cube-0.12.1/lib/ice_cube/single_occurrence_rule.rb000066400000000000000000000006461235562124600227160ustar00rootroot00000000000000module IceCube class SingleOccurrenceRule < Rule attr_reader :time def initialize(time) @time = TimeUtil.ensure_time time end # Always terminating def terminating? true end def next_time(t, schedule, closing_time) unless closing_time && closing_time < t time if time.to_i >= t.to_i end end def to_hash { :time => time } end end end ice_cube-0.12.1/lib/ice_cube/time_util.rb000066400000000000000000000222641235562124600201710ustar00rootroot00000000000000require 'date' require 'time' module IceCube module TimeUtil extend Deprecated DAYS = { :sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6 } MONTHS = { :january => 1, :february => 2, :march => 3, :april => 4, :may => 5, :june => 6, :july => 7, :august => 8, :september => 9, :october => 10, :november => 11, :december => 12 } CLOCK_VALUES = [:year, :month, :day, :hour, :min, :sec] # Provides a Time.now without the usec, in the reference zone or utc offset def self.now(reference=Time.now) match_zone(Time.at(Time.now.to_i), reference) end def self.match_zone(time, reference) return unless time = ensure_time(time) if reference.respond_to? :time_zone time.in_time_zone(reference.time_zone) else if reference.utc? time.utc elsif reference.zone time.getlocal else time.getlocal(reference.utc_offset) end end end # Ensure that this is either nil, or a time def self.ensure_time(time, date_eod = false) case time when DateTime warn "IceCube: DateTime support is deprecated (please use Time) at: #{ caller[2] }" Time.local(time.year, time.month, time.day, time.hour, time.min, time.sec) when Date date_eod ? end_of_date(time) : time.to_time else time end end # Ensure that this is either nil, or a date def self.ensure_date(date) case date when Date then date else return Date.new(date.year, date.month, date.day) end end # Serialize a time appropriate for storing def self.serialize_time(time) if time.respond_to?(:time_zone) {:time => time.utc, :zone => time.time_zone.name} elsif time.is_a?(Time) time end end # Deserialize a time serialized with serialize_time or in ISO8601 string format def self.deserialize_time(time_or_hash) case time_or_hash when Time time_or_hash when Hash hash = FlexibleHash.new(time_or_hash) hash[:time].in_time_zone(hash[:zone]) when String Time.parse(time_or_hash) end end # Check the deserialized time offset string against actual local time # offset to try and preserve the original offset for plain Ruby Time. If # the offset is the same as local we can assume the same original zone and # keep it. If it was serialized with a different offset than local TZ it # will lose the zone and not support DST. def self.restore_deserialized_offset(time, orig_offset_str) return time if time.respond_to?(:time_zone) || time.getlocal(orig_offset_str).utc_offset == time.utc_offset warn "IceCube: parsed Time from nonlocal TZ. Use ActiveSupport to fix DST at: #{ caller[0] }" time.localtime(orig_offset_str) end # Get the beginning of a date def self.beginning_of_date(date, reference=nil) args = [date.year, date.month, date.day, 0, 0, 0] reference ||= Time.local(*args) if reference.respond_to?(:time_zone) && reference.time_zone reference.time_zone.local(*args) else match_zone(Time.new(*args << reference.utc_offset), reference) end end # Get the end of a date def self.end_of_date(date, reference=nil) args = [date.year, date.month, date.day, 23, 59, 59] reference ||= Time.local(*args) if reference.respond_to?(:time_zone) && reference.time_zone reference.time_zone.local(*args) else match_zone(Time.new(*args << reference.utc_offset), reference) end end # Convert a symbol to a numeric month def self.sym_to_month(sym) MONTHS.fetch(sym) do |k| return wday = sym.to_i if MONTHS.values.any? { |i| i.to_s == sym.to_s } raise ArgumentError, "Expecting Fixnum or Symbol value for month. " \ "No such month: #{k.inspect}" end end deprecated_alias :symbol_to_month, :sym_to_month # Convert a symbol to a wday number def self.sym_to_wday(sym) DAYS.fetch(sym) do |k| return sym.to_i if DAYS.values.any? { |i| i.to_s == sym.to_s } raise ArgumentError, "Expecting Fixnum or Symbol value for weekday. " \ "No such weekday: #{k.inspect}" end end deprecated_alias :symbol_to_day, :sym_to_wday # Convert wday number to day symbol def self.wday_to_sym(wday) return sym = wday if DAYS.keys.include? wday DAYS.invert.fetch(wday) do |i| raise ArgumentError, "Expecting Fixnum value for weekday. " \ "No such wday number: #{i.inspect}" end end # Convert weekday from base sunday to the schedule's week start. def self.normalize_wday(wday, week_start) (wday - sym_to_wday(week_start)) % 7 end deprecated_alias :normalize_weekday, :normalize_wday # Return the count of the number of times wday appears in the month, # and which of those time falls on def self.which_occurrence_in_month(time, wday) first_occurrence = ((7 - Time.utc(time.year, time.month, 1).wday) + time.wday) % 7 + 1 this_weekday_in_month_count = ((days_in_month(time) - first_occurrence + 1) / 7.0).ceil nth_occurrence_of_weekday = (time.mday - first_occurrence) / 7 + 1 [nth_occurrence_of_weekday, this_weekday_in_month_count] end # Get the days in the month for +time def self.days_in_month(time) date = Date.new(time.year, time.month, 1) ((date >> 1) - date).to_i end # Get the days in the following month for +time def self.days_in_next_month(time) date = Date.new(time.year, time.month, 1) >> 1 ((date >> 1) - date).to_i end # Count the number of days to the same day of the next month without # overflowing shorter months def self.days_to_next_month(time) date = Date.new(time.year, time.month, time.day) ((date >> 1) - date).to_i end # Get a day of the month in the month of a given time without overflowing # into the next month. Accepts days from positive (start of month forward) or # negative (from end of month) def self.day_of_month(value, date) if value.to_i > 0 [value, days_in_month(date)].min else [1 + days_in_month(date) + value, 1].max end end # Number of days in a year def self.days_in_year(time) date = Date.new(time.year, 1, 1) ((date >> 12) - date).to_i end # Number of days to n years def self.days_in_n_years(time, year_distance) date = Date.new(time.year, time.month, time.day) ((date >> year_distance * 12) - date).to_i end # The number of days in n months def self.days_in_n_months(time, month_distance) date = Date.new(time.year, time.month, time.day) ((date >> month_distance) - date).to_i end def self.dst_change(time) one_hour_ago = time - ONE_HOUR if time.dst? ^ one_hour_ago.dst? (time.utc_offset - one_hour_ago.utc_offset) / ONE_HOUR end end def self.same_clock?(t1, t2) CLOCK_VALUES.all? { |i| t1.send(i) == t2.send(i) } end # A utility class for safely moving time around class TimeWrapper def initialize(time, dst_adjust = true) @dst_adjust = dst_adjust @time = time end # Get the wrapper time back def to_time @time end # DST-safely add an interval of time to the wrapped time def add(type, val) type = :day if type == :wday adjust do @time += case type when :year then TimeUtil.days_in_n_years(@time, val) * ONE_DAY when :month then TimeUtil.days_in_n_months(@time, val) * ONE_DAY when :day then val * ONE_DAY when :hour then val * ONE_HOUR when :min then val * ONE_MINUTE when :sec then val end end end # Clear everything below a certain type CLEAR_ORDER = [:sec, :min, :hour, :day, :month, :year] def clear_below(type) type = :day if type == :wday CLEAR_ORDER.each do |ptype| break if ptype == type adjust do send(:"clear_#{ptype}") end end end private def adjust(&block) if @dst_adjust off = @time.utc_offset yield diff = off - @time.utc_offset @time += diff if diff != 0 else yield end end def clear_sec @time.sec > 0 ? @time -= @time.sec : @time end def clear_min @time.min > 0 ? @time -= (@time.min * ONE_MINUTE) : @time end def clear_hour @time.hour > 0 ? @time -= (@time.hour * ONE_HOUR) : @time end # Move to the first of the month, 0 hours def clear_day @time.day > 1 ? @time -= (@time.day - 1) * ONE_DAY : @time end # Clear to january 1st def clear_month @time -= ONE_DAY until @time.month == 12 @time -= TimeUtil.days_in_month(@time) * ONE_DAY end @time += ONE_DAY end def clear_year @time end end end end ice_cube-0.12.1/lib/ice_cube/validated_rule.rb000066400000000000000000000111411235562124600211520ustar00rootroot00000000000000module IceCube class ValidatedRule < Rule include Validations::ScheduleLock include Validations::HourOfDay include Validations::MinuteOfHour include Validations::SecondOfMinute include Validations::DayOfMonth include Validations::DayOfWeek include Validations::Day include Validations::MonthOfYear include Validations::DayOfYear include Validations::Count include Validations::Until # Validations ordered for efficiency in sequence of: # * descending intervals # * boundary limits # * base values by cardinality (n = 60, 60, 31, 24, 12, 7) # * locks by cardinality (n = 365, 60, 60, 31, 24, 12, 7) # * interval multiplier VALIDATION_ORDER = [ :year, :month, :day, :wday, :hour, :min, :sec, :count, :until, :base_sec, :base_min, :base_day, :base_hour, :base_month, :base_wday, :day_of_year, :second_of_minute, :minute_of_hour, :day_of_month, :hour_of_day, :month_of_year, :day_of_week, :interval ] def initialize(interval = 1, *) @validations = Hash.new end # Compute the next time after (or including) the specified time in respect # to the given schedule def next_time(time, schedule, closing_time) @time = time @schedule = schedule return nil unless find_acceptable_time_before(closing_time) @uses += 1 if @time @time end def skipped_for_dst @uses -= 1 if @uses > 0 end def dst_adjust? @validations[:interval].any? &:dst_adjust? end def to_s builder = StringBuilder.new @validations.each do |name, validations| validations.each do |validation| validation.build_s(builder) end end builder.to_s end def to_hash builder = HashBuilder.new(self) @validations.each do |name, validations| validations.each do |validation| validation.build_hash(builder) end end builder.to_hash end def to_ical builder = IcalBuilder.new @validations.each do |name, validations| validations.each do |validation| validation.build_ical(builder) end end builder.to_s end # Get the collection that contains validations of a certain type def validations_for(key) @validations[key] ||= [] end # Fully replace validations def replace_validations_for(key, arr) if arr.nil? @validations.delete(key) else @validations[key] = arr end end # Remove the specified base validations def clobber_base_validations(*types) types.each do |type| @validations.delete(:"base_#{type}") end end private def normalized_interval(interval) int = interval.to_i raise ArgumentError, "'#{interval}' is not a valid input for interval. Please pass an integer." unless int > 0 int end def finds_acceptable_time? validation_names.all? do |type| validation_accepts_or_updates_time?(@validations[type]) end end def find_acceptable_time_before(boundary) until finds_acceptable_time? return false if past_closing_time?(boundary) end true end def validation_accepts_or_updates_time?(validations_for_type) res = validated_results(validations_for_type) # If there is any nil, then we're set - otherwise choose the lowest if res.any? { |r| r.nil? || r == 0 } true else return nil if res.all? { |r| r === true } # allow quick escaping res.reject! { |r| r.nil? || r == 0 || r === true } shift_time_by_validation(res, validations_for_type) false end end def validated_results(validations_for_type) validations_for_type.map do |validation| validation.validate(@time, @schedule) end end def shift_time_by_validation(res, vals) return unless (interval = res.min) validation = vals.first wrapper = TimeUtil::TimeWrapper.new(@time, validation.dst_adjust?) wrapper.add(validation.type, interval) wrapper.clear_below(validation.type) # Move over DST if blocked, no adjustments if wrapper.to_time <= @time wrapper = TimeUtil::TimeWrapper.new(wrapper.to_time, false) until wrapper.to_time > @time wrapper.add(:min, 10) # smallest interval end end # And then get the correct time out @time = wrapper.to_time end def past_closing_time?(closing_time) closing_time && @time > closing_time end def validation_names VALIDATION_ORDER & @validations.keys end end end ice_cube-0.12.1/lib/ice_cube/validations/000077500000000000000000000000001235562124600201605ustar00rootroot00000000000000ice_cube-0.12.1/lib/ice_cube/validations/count.rb000066400000000000000000000021621235562124600216360ustar00rootroot00000000000000module IceCube module Validations::Count # Value reader for limit def occurrence_count @count end def count(max) unless max.nil? || max.is_a?(Fixnum) raise ArgumentError, "Expecting Fixnum or nil value for count, got #{max.inspect}" end @count = max replace_validations_for(:count, max && [Validation.new(max, self)]) self end class Validation attr_reader :rule, :count def initialize(count, rule) @count = count @rule = rule end def type :limit end def dst_adjust? false end def validate(time, schedule) raise CountExceeded if rule.uses && rule.uses >= count end def build_s(builder) builder.piece(:count) << count end def build_hash(builder) builder[:count] = count end def build_ical(builder) builder['COUNT'] << count end StringBuilder.register_formatter(:count) do |segments| count = segments.first "#{count} #{count == 1 ? 'time' : 'times'}" end end end end ice_cube-0.12.1/lib/ice_cube/validations/daily_interval.rb000066400000000000000000000021601235562124600235120ustar00rootroot00000000000000module IceCube module Validations::DailyInterval # Add a new interval validation def interval(interval) @interval = normalized_interval(interval) replace_validations_for(:interval, [Validation.new(@interval)]) clobber_base_validations(:wday, :day) self end class Validation attr_reader :interval def initialize(interval) @interval = interval end def type :day end def dst_adjust? true end def validate(step_time, schedule) t0, t1 = schedule.start_time, step_time days = Date.new(t1.year, t1.month, t1.day) - Date.new(t0.year, t0.month, t0.day) offset = (days % interval).nonzero? interval - offset if offset end def build_s(builder) builder.base = interval == 1 ? 'Daily' : "Every #{interval} days" end def build_hash(builder) builder[:interval] = interval end def build_ical(builder) builder['FREQ'] << 'DAILY' builder['INTERVAL'] << interval unless interval == 1 end end end end ice_cube-0.12.1/lib/ice_cube/validations/day.rb000066400000000000000000000031111235562124600212560ustar00rootroot00000000000000require 'date' module IceCube module Validations::Day def day(*days) days.flatten.each do |day| unless day.is_a?(Fixnum) || day.is_a?(Symbol) raise ArgumentError, "expecting Fixnum or Symbol value for day, got #{day.inspect}" end day = TimeUtil.sym_to_wday(day) validations_for(:day) << Validation.new(day) end clobber_base_validations(:wday, :day) self end class Validation include Validations::Lock attr_reader :day alias :value :day def initialize(day) @day = day end def type :wday end def dst_adjust? true end def build_s(builder) builder.piece(:day) << day end def build_hash(builder) builder.validations_array(:day) << day end def build_ical(builder) ical_day = IcalBuilder.fixnum_to_ical_day(day) # Only add if there aren't others from day_of_week that override if builder['BYDAY'].none? { |b| b.end_with?(ical_day) } builder['BYDAY'] << ical_day end end StringBuilder.register_formatter(:day) do |validation_days| # sort the days validation_days.sort! # pick the right shortening, if applicable if validation_days == [0, 6] 'on Weekends' elsif validation_days == (1..5).to_a 'on Weekdays' else segments = validation_days.map { |d| "#{Date::DAYNAMES[d]}s" } "on #{StringBuilder.sentence(segments)}" end end end end end ice_cube-0.12.1/lib/ice_cube/validations/day_of_month.rb000066400000000000000000000021771235562124600231620ustar00rootroot00000000000000module IceCube module Validations::DayOfMonth def day_of_month(*days) days.flatten.each do |day| unless day.is_a?(Fixnum) raise ArgumentError, "expecting Fixnum value for day, got #{day.inspect}" end validations_for(:day_of_month) << Validation.new(day) end clobber_base_validations(:day, :wday) self end class Validation include Validations::Lock attr_reader :day alias :value :day def initialize(day) @day = day end def type :day end def dst_adjust? true end def build_s(builder) builder.piece(:day_of_month) << StringBuilder.nice_number(day) end def build_hash(builder) builder.validations_array(:day_of_month) << day end def build_ical(builder) builder['BYMONTHDAY'] << day end StringBuilder.register_formatter(:day_of_month) do |entries| str = "on the #{StringBuilder.sentence(entries)} " str << (entries.size == 1 ? 'day of the month' : 'days of the month') str end end end end ice_cube-0.12.1/lib/ice_cube/validations/day_of_week.rb000066400000000000000000000033341235562124600227640ustar00rootroot00000000000000module IceCube module Validations::DayOfWeek def day_of_week(dows) dows.each do |day, occs| occs.each do |occ| day = TimeUtil.sym_to_wday(day) validations_for(:day_of_week) << Validation.new(day, occ) end end clobber_base_validations :day, :wday self end class Validation attr_reader :day, :occ def initialize(day, occ) @day = day @occ = occ end def type :day end def dst_adjust? true end def validate(step_time, schedule) wday = step_time.wday offset = (day < wday) ? (7 - wday + day) : (day - wday) wrapper = TimeUtil::TimeWrapper.new(step_time) wrapper.add :day, offset loop do which_occ, num_occ = TimeUtil.which_occurrence_in_month(wrapper.to_time, day) this_occ = (occ < 0) ? (num_occ + occ + 1) : (occ) break offset if which_occ == this_occ wrapper.add :day, 7 offset += 7 end end def build_s(builder) builder.piece(:day_of_week) << "#{StringBuilder.nice_number(occ)} #{Date::DAYNAMES[day]}" end def build_hash(builder) builder.validations[:day_of_week] ||= {} arr = (builder.validations[:day_of_week][day] ||= []) arr << occ end def build_ical(builder) ical_day = IcalBuilder.fixnum_to_ical_day(day) # Delete any with this day and no occ first builder['BYDAY'].delete_if { |d| d == ical_day } builder['BYDAY'] << "#{occ}#{ical_day}" end StringBuilder.register_formatter(:day_of_week) do |segments| 'on the ' + segments.join(' and ') end end end end ice_cube-0.12.1/lib/ice_cube/validations/day_of_year.rb000066400000000000000000000024761235562124600227770ustar00rootroot00000000000000module IceCube module Validations::DayOfYear def day_of_year(*days) days.flatten.each do |day| unless day.is_a?(Fixnum) raise ArgumentError, "expecting Fixnum value for day, got #{day.inspect}" end validations_for(:day_of_year) << Validation.new(day) end clobber_base_validations(:month, :day, :wday) self end class Validation attr_reader :day def initialize(day) @day = day end def type :day end def dst_adjust? true end def validate(step_time, schedule) days_in_year = TimeUtil.days_in_year(step_time) yday = day < 0 ? day + days_in_year : day offset = yday - step_time.yday offset >= 0 ? offset : offset + days_in_year end def build_s(builder) builder.piece(:day_of_year) << StringBuilder.nice_number(day) end def build_hash(builder) builder.validations_array(:day_of_year) << day end def build_ical(builder) builder['BYYEARDAY'] << day end StringBuilder.register_formatter(:day_of_year) do |entries| str = "on the #{StringBuilder.sentence(entries)} " str << (entries.size == 1 ? 'day of the year' : 'days of the year') str end end end end ice_cube-0.12.1/lib/ice_cube/validations/hour_of_day.rb000066400000000000000000000022261235562124600230050ustar00rootroot00000000000000module IceCube module Validations::HourOfDay # Add hour of day validations def hour_of_day(*hours) hours.flatten.each do |hour| unless hour.is_a?(Fixnum) raise ArgumentError, "expecting Fixnum value for hour, got #{hour.inspect}" end validations_for(:hour_of_day) << Validation.new(hour) end clobber_base_validations(:hour) self end class Validation include Validations::Lock attr_reader :hour alias :value :hour def initialize(hour) @hour = hour end def type :hour end def dst_adjust? true end def build_s(builder) builder.piece(:hour_of_day) << StringBuilder.nice_number(hour) end def build_hash(builder) builder.validations_array(:hour_of_day) << hour end def build_ical(builder) builder['BYHOUR'] << hour end StringBuilder.register_formatter(:hour_of_day) do |segments| str = "on the #{StringBuilder.sentence(segments)} " str << (segments.size == 1 ? 'hour of the day' : 'hours of the day') end end end end ice_cube-0.12.1/lib/ice_cube/validations/hourly_interval.rb000066400000000000000000000021261235562124600237340ustar00rootroot00000000000000module IceCube module Validations::HourlyInterval def interval(interval) @interval = normalized_interval(interval) replace_validations_for(:interval, [Validation.new(@interval)]) clobber_base_validations(:hour) self end class Validation attr_reader :interval def initialize(interval) @interval = interval end def type :hour end def dst_adjust? false end def validate(step_time, schedule) t0, t1 = schedule.start_time.to_i, step_time.to_i sec = (t1 - t1 % ONE_HOUR) - (t0 - t0 % ONE_HOUR) hours = sec / ONE_HOUR offset = (hours % interval).nonzero? interval - offset if offset end def build_s(builder) builder.base = interval == 1 ? 'Hourly' : "Every #{interval} hours" end def build_hash(builder) builder[:interval] = interval end def build_ical(builder) builder['FREQ'] << 'HOURLY' builder['INTERVAL'] << interval unless interval == 1 end end end end ice_cube-0.12.1/lib/ice_cube/validations/lock.rb000066400000000000000000000055431235562124600214440ustar00rootroot00000000000000module IceCube # This validation mixin is used by the various "fixed-time" (e.g. day, # day_of_month, hour_of_day) Validation and ScheduleLock::Validation modules. # It is not a standalone rule validation like the others. # # Given the including Validation's defined +type+ field, it will lock # to the specified +value+ or else the corresponding time unit from the # schedule's start_time # module Validations::Lock INTERVALS = {:min => 60, :sec => 60, :hour => 24, :month => 12, :wday => 7} def validate(time, schedule) case type when :day then validate_day_lock(time, schedule) when :hour then validate_hour_lock(time, schedule) else validate_interval_lock(time, schedule) end end private # Validate if the current time unit matches the same unit from the schedule # start time, returning the difference to the interval # def validate_interval_lock(time, schedule) t0 = starting_unit(schedule.start_time) t1 = time.send(type) t0 >= t1 ? t0 - t1 : INTERVALS[type] - t1 + t0 end # Lock the hour if explicitly set by hour_of_day, but allow for the nearest # hour during DST start to keep the correct interval. # def validate_hour_lock(time, schedule) h0 = starting_unit(schedule.start_time) h1 = time.hour if h0 >= h1 h0 - h1 else if dst_offset = TimeUtil.dst_change(time) h0 - h1 + dst_offset else 24 - h1 + h0 end end end # For monthly rules that have no specified day value, the validation relies # on the schedule start time and jumps to include every month even if it # has fewer days than the schedule's start day. # # Negative day values (from month end) also include all months. # # Positive day values are taken literally so months with fewer days will # be skipped. # def validate_day_lock(time, schedule) days_in_month = TimeUtil.days_in_month(time) date = Date.new(time.year, time.month, time.day) if value && value < 0 start = TimeUtil.day_of_month(value, date) month_overflow = days_in_month - TimeUtil.days_in_next_month(time) elsif value && value > 0 start = value month_overflow = 0 else start = TimeUtil.day_of_month(schedule.start_time.day, date) month_overflow = 0 end sleeps = start - date.day if value && value > 0 until_next_month = days_in_month + sleeps else until_next_month = start < 28 ? days_in_month : TimeUtil.days_to_next_month(date) until_next_month += sleeps - month_overflow end sleeps >= 0 ? sleeps : until_next_month end def starting_unit(start_time) start = value || start_time.send(type) start += INTERVALS[type] while start < 0 start end end end ice_cube-0.12.1/lib/ice_cube/validations/minute_of_hour.rb000066400000000000000000000022531235562124600235310ustar00rootroot00000000000000module IceCube module Validations::MinuteOfHour def minute_of_hour(*minutes) minutes.flatten.each do |minute| unless minute.is_a?(Fixnum) raise ArgumentError, "expecting Fixnum value for minute, got #{minute.inspect}" end validations_for(:minute_of_hour) << Validation.new(minute) end clobber_base_validations(:min) self end class Validation include Validations::Lock attr_reader :minute alias :value :minute def initialize(minute) @minute = minute end def type :min end def dst_adjust? false end def build_s(builder) builder.piece(:minute_of_hour) << StringBuilder.nice_number(minute) end def build_hash(builder) builder.validations_array(:minute_of_hour) << minute end def build_ical(builder) builder['BYMINUTE'] << minute end StringBuilder.register_formatter(:minute_of_hour) do |segments| str = "on the #{StringBuilder.sentence(segments)} " str << (segments.size == 1 ? 'minute of the hour' : 'minutes of the hour') end end end end ice_cube-0.12.1/lib/ice_cube/validations/minutely_interval.rb000066400000000000000000000021461235562124600242620ustar00rootroot00000000000000module IceCube module Validations::MinutelyInterval def interval(interval) @interval = normalized_interval(interval) replace_validations_for(:interval, [Validation.new(@interval)]) clobber_base_validations(:min) self end class Validation attr_reader :interval def initialize(interval) @interval = interval end def type :min end def dst_adjust? false end def validate(step_time, schedule) t0, t1 = schedule.start_time.to_i, step_time.to_i sec = (t1 - t1 % ONE_MINUTE) - (t0 - t0 % ONE_MINUTE) minutes = sec / ONE_MINUTE offset = (minutes % interval).nonzero? interval - offset if offset end def build_s(builder) builder.base = interval == 1 ? 'Minutely' : "Every #{interval} minutes" end def build_hash(builder) builder[:interval] = interval end def build_ical(builder) builder['FREQ'] << 'MINUTELY' builder['INTERVAL'] << interval unless interval == 1 end end end end ice_cube-0.12.1/lib/ice_cube/validations/month_of_year.rb000066400000000000000000000021761235562124600233440ustar00rootroot00000000000000module IceCube module Validations::MonthOfYear def month_of_year(*months) months.flatten.each do |month| unless month.is_a?(Fixnum) || month.is_a?(Symbol) raise ArgumentError, "expecting Fixnum or Symbol value for month, got #{month.inspect}" end month = TimeUtil.sym_to_month(month) validations_for(:month_of_year) << Validation.new(month) end clobber_base_validations :month self end class Validation include Validations::Lock attr_reader :month alias :value :month def initialize(month) @month = month end def type :month end def dst_adjust? true end def build_s(builder) builder.piece(:month_of_year) << Date::MONTHNAMES[month] end def build_hash(builder) builder.validations_array(:month_of_year) << month end def build_ical(builder) builder['BYMONTH'] << month end StringBuilder.register_formatter(:month_of_year) do |segments| "in #{StringBuilder.sentence(segments)}" end end end end ice_cube-0.12.1/lib/ice_cube/validations/monthly_interval.rb000066400000000000000000000020761235562124600241100ustar00rootroot00000000000000module IceCube module Validations::MonthlyInterval def interval(interval) @interval = normalized_interval(interval) replace_validations_for(:interval, [Validation.new(@interval)]) clobber_base_validations(:month) self end class Validation attr_reader :interval def initialize(interval) @interval = interval end def type :month end def dst_adjust? true end def validate(step_time, schedule) t0, t1 = schedule.start_time, step_time months = (t1.month - t0.month) + (t1.year - t0.year) * 12 offset = (months % interval).nonzero? interval - offset if offset end def build_s(builder) builder.base = interval == 1 ? 'Monthly' : "Every #{interval} months" end def build_hash(builder) builder[:interval] = interval end def build_ical(builder) builder['FREQ'] << 'MONTHLY' builder['INTERVAL'] << interval unless interval == 1 end end end end ice_cube-0.12.1/lib/ice_cube/validations/schedule_lock.rb000066400000000000000000000014771235562124600233220ustar00rootroot00000000000000module IceCube module Validations::ScheduleLock # Lock the given time units to the units from schedule's +start_time+ # These locks are all clobberable by other rules of the same #type # using +clobber_base_validation+ # def schedule_lock(*types) types.each do |type| validations_for(:"base_#{type}") << Validation.new(type) end end class Validation include Validations::Lock attr_reader :type, :value def initialize(type) @type = type end def dst_adjust? case @type when :sec, :min then false else true end end # no -op def build_s(builder) end # no -op def build_hash(builder) end # no -op def build_ical(builder) end end end end ice_cube-0.12.1/lib/ice_cube/validations/second_of_minute.rb000066400000000000000000000022641235562124600240310ustar00rootroot00000000000000module IceCube module Validations::SecondOfMinute def second_of_minute(*seconds) seconds.flatten.each do |second| unless second.is_a?(Fixnum) raise ArgumentError, "Expecting Fixnum value for second, got #{second.inspect}" end validations_for(:second_of_minute) << Validation.new(second) end clobber_base_validations :sec self end class Validation include Validations::Lock attr_reader :second alias :value :second def initialize(second) @second = second end def type :sec end def dst_adjust? false end def build_s(builder) builder.piece(:second_of_minute) << StringBuilder.nice_number(second) end def build_hash(builder) builder.validations_array(:second_of_minute) << second end def build_ical(builder) builder['BYSECOND'] << second end StringBuilder.register_formatter(:second_of_minute) do |segments| str = "on the #{StringBuilder.sentence(segments)} " str << (segments.size == 1 ? 'second of the minute' : 'seconds of the minute') end end end end ice_cube-0.12.1/lib/ice_cube/validations/secondly_interval.rb000066400000000000000000000017711235562124600242370ustar00rootroot00000000000000module IceCube module Validations::SecondlyInterval def interval(interval) @interval = normalized_interval(interval) replace_validations_for(:interval, [Validation.new(@interval)]) clobber_base_validations(:sec) self end class Validation attr_reader :interval def initialize(interval) @interval = interval end def type :sec end def dst_adjust? false end def validate(step_time, schedule) seconds = step_time.to_i - schedule.start_time.to_i offset = (seconds % interval).nonzero? interval - offset if offset end def build_s(builder) builder.base = interval == 1 ? 'Secondly' : "Every #{interval} seconds" end def build_hash(builder) builder[:interval] = interval end def build_ical(builder) builder['FREQ'] << 'SECONDLY' builder['INTERVAL'] << interval unless interval == 1 end end end end ice_cube-0.12.1/lib/ice_cube/validations/until.rb000066400000000000000000000017551235562124600216500ustar00rootroot00000000000000module IceCube module Validations::Until extend Deprecated # Value reader for limit def until_time @until end deprecated_alias :until_date, :until_time def until(time) time = TimeUtil.ensure_time(time, true) @until = time replace_validations_for(:until, time.nil? ? nil : [Validation.new(time)]) self end class Validation attr_reader :time def initialize(time) @time = time end def type :limit end def dst_adjust? false end def validate(step_time, schedule) raise UntilExceeded if step_time > time end def build_s(builder) builder.piece(:until) << "until #{time.strftime(IceCube.to_s_time_format)}" end def build_hash(builder) builder[:until] = TimeUtil.serialize_time(time) end def build_ical(builder) builder['UNTIL'] << IcalBuilder.ical_utc_format(time) end end end end ice_cube-0.12.1/lib/ice_cube/validations/weekly_interval.rb000066400000000000000000000030661235562124600237160ustar00rootroot00000000000000require 'date' module IceCube module Validations::WeeklyInterval def interval(interval, week_start = :sunday) @interval = normalized_interval(interval) @week_start = TimeUtil.wday_to_sym(week_start) replace_validations_for(:interval, [Validation.new(@interval, week_start)]) clobber_base_validations(:day) self end def week_start @week_start end class Validation attr_reader :interval, :week_start def initialize(interval, week_start) @interval = interval @week_start = week_start end def type :day end def dst_adjust? true end def validate(step_time, schedule) t0, t1 = schedule.start_time, step_time d0 = Date.new(t0.year, t0.month, t0.day) d1 = Date.new(t1.year, t1.month, t1.day) days = (d1 - TimeUtil.normalize_wday(d1.wday, week_start)) - (d0 - TimeUtil.normalize_wday(d0.wday, week_start)) offset = ((days / 7) % interval).nonzero? (interval - offset) * 7 if offset end def build_s(builder) builder.base = interval == 1 ? 'Weekly' : "Every #{interval} weeks" end def build_hash(builder) builder[:interval] = interval builder[:week_start] = TimeUtil.sym_to_wday(week_start) end def build_ical(builder) builder['FREQ'] << 'WEEKLY' unless interval == 1 builder['INTERVAL'] << interval builder['WKST'] << week_start.to_s.upcase[0..1] end end end end end ice_cube-0.12.1/lib/ice_cube/validations/yearly_interval.rb000066400000000000000000000020041235562124600237120ustar00rootroot00000000000000module IceCube module Validations::YearlyInterval def interval(interval) @interval = normalized_interval(interval) replace_validations_for(:interval, [Validation.new(@interval)]) clobber_base_validations(:year) self end class Validation attr_reader :interval def initialize(interval) @interval = interval end def type :year end def dst_adjust? true end def validate(step_time, schedule) years = step_time.year - schedule.start_time.year offset = (years % interval).nonzero? interval - offset if offset end def build_s(builder) builder.base = interval == 1 ? 'Yearly' : "Every #{interval} years" end def build_hash(builder) builder[:interval] = interval end def build_ical(builder) builder['FREQ'] << 'YEARLY' unless interval == 1 builder['INTERVAL'] << interval end end end end end ice_cube-0.12.1/lib/ice_cube/version.rb000066400000000000000000000000521235562124600176520ustar00rootroot00000000000000module IceCube VERSION = '0.12.1' end ice_cube-0.12.1/spec/000077500000000000000000000000001235562124600142715ustar00rootroot00000000000000ice_cube-0.12.1/spec/data/000077500000000000000000000000001235562124600152025ustar00rootroot00000000000000ice_cube-0.12.1/spec/data/issue40.yml000066400000000000000000000005111235562124600172160ustar00rootroot00000000000000--- :start_date: 2011-11-16 11:31:58.441381000 -05:00 :rrules: - :rule_type: IceCube::MinutelyRule :interval: 60 :until: !!null :count: !!null :validations: :day: - 4 :hour_of_day: - 14 - 15 - 16 :minute_of_hour: - 0 :exrules: [] :rdates: [] :exdates: [] :duration: 3600 :end_time: !!null ice_cube-0.12.1/spec/examples/000077500000000000000000000000001235562124600161075ustar00rootroot00000000000000ice_cube-0.12.1/spec/examples/_no_active_support_spec.rb000066400000000000000000000026621235562124600233560ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' # This file is loaded and run alphabetically first in the suite, before # ActiveSupport gets loaded by other specs. module IceCube describe TimeUtil, :if_active_support_time => false do before do Time.any_instance.should_receive(:respond_to?).with(:time_zone). at_least(1).times.and_return(false) end WORLD_TIME_ZONES.each do |zone| context "in #{zone}", :system_time_zone => zone do it 'should be able to calculate end of dates without active_support' do date = Date.new(2011, 1, 1) end_of_date = Time.local(2011, 1, 1, 23, 59, 59) TimeUtil.end_of_date(date).to_s.should == end_of_date.to_s end it 'should be able to calculate beginning of dates without active_support' do date = Date.new(2011, 1, 1) res = [ TimeUtil.beginning_of_date(date), Time.local(2011, 1, 1, 0, 0, 0) ] res.all? { |r| r.class.name == 'Time' } res.map(&:to_s).uniq.size.should == 1 end it 'should serialize to hash without error' do schedule = IceCube::Schedule.new(Time.now) schedule.add_recurrence_rule IceCube::Rule.hourly.until(Date.today >> 1) schedule.add_recurrence_time Time.now + 123 schedule.add_exception_time Time.now + 456 expect { schedule.to_hash }.to_not raise_error end end end end end ice_cube-0.12.1/spec/examples/active_support_spec.rb000066400000000000000000000157431235562124600225270ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' require 'active_support/time' module IceCube describe Schedule, 'using ActiveSupport' do before(:all) { Time.zone = 'Eastern Time (US & Canada)' } around(:each) do |example| Time.zone = 'America/Anchorage' orig_tz, ENV['TZ'] = ENV['TZ'], 'Pacific/Auckland' example.run ENV['TZ'] = orig_tz end it 'works with a single recurrence time starting from a TimeWithZone' do schedule = Schedule.new(t0 = Time.zone.parse("2010-02-05 05:00:00")) schedule.add_recurrence_time t0 schedule.all_occurrences.should == [t0] end it 'works with a monthly recurrence rule starting from a TimeWithZone' do schedule = Schedule.new(t0 = Time.zone.parse("2010-02-05 05:00:00")) schedule.add_recurrence_rule Rule.monthly schedule.first(10).should == [ Time.zone.parse("2010-02-05 05:00"), Time.zone.parse("2010-03-05 05:00"), Time.zone.parse("2010-04-05 05:00"), Time.zone.parse("2010-05-05 05:00"), Time.zone.parse("2010-06-05 05:00"), Time.zone.parse("2010-07-05 05:00"), Time.zone.parse("2010-08-05 05:00"), Time.zone.parse("2010-09-05 05:00"), Time.zone.parse("2010-10-05 05:00"), Time.zone.parse("2010-11-05 05:00") ] end it 'works with a monthly schedule converting to UTC across DST' do Time.zone = 'Eastern Time (US & Canada)' schedule = Schedule.new(t0 = Time.zone.parse("2009-10-28 19:30:00")) schedule.add_recurrence_rule Rule.monthly schedule.first(7).map { |d| d.getutc }.should == [ Time.utc(2009, 10, 28, 23, 30, 0), Time.utc(2009, 11, 29, 0, 30, 0), Time.utc(2009, 12, 29, 0, 30, 0), Time.utc(2010, 1, 29, 0, 30, 0), Time.utc(2010, 3, 1, 0, 30, 0), Time.utc(2010, 3, 28, 23, 30, 0), Time.utc(2010, 4, 28, 23, 30, 0) ] end it 'can round trip TimeWithZone to YAML' do schedule = Schedule.new(t0 = Time.zone.parse("2010-02-05 05:00:00")) schedule.add_recurrence_time t0 schedule2 = Schedule.from_yaml(schedule.to_yaml) schedule.all_occurrences.should == schedule2.all_occurrences end it 'uses local zone from start time to determine occurs_on? from the beginning of day' do schedule = Schedule.new(t0 = Time.local(2009, 2, 7, 23, 59, 59)) schedule.add_recurrence_rule Rule.daily schedule.occurs_on?(Date.new(2009, 2, 7)).should be_true end it 'uses local zone from start time to determine occurs_on? to the end of day' do schedule = Schedule.new(t0 = Time.local(2009, 2, 7, 0, 0, 0)) schedule.add_recurrence_rule Rule.daily schedule.occurs_on?(Date.new(2009, 2, 7)).should be_true end it 'should use the correct zone for next_occurrences before start_time' do future_time = Time.zone.now.beginning_of_day + 1.day schedule = Schedule.new(future_time) schedule.add_recurrence_rule Rule.daily schedule.next_occurrence.time_zone.should == schedule.start_time.time_zone end it 'should use the correct zone for next_occurrences after start_time' do past_time = Time.zone.now.beginning_of_day schedule = Schedule.new(past_time) schedule.add_recurrence_rule Rule.daily schedule.next_occurrence.time_zone.should == schedule.start_time.time_zone end describe 'querying with time arguments for a different zone' do let(:schedule) do utc = Time.utc(2013, 1, 1).in_time_zone('UTC') Schedule.new(utc) { |s| s.add_recurrence_rule Rule.daily.count(3) } end let(:reference_time) do Time.utc(2013, 1, 1).in_time_zone('Bern') # +01:00 end it 'uses schedule zone for next_occurrence' do next_occurrence = schedule.next_occurrence(reference_time) next_occurrence.should == Time.utc(2013, 1, 2) next_occurrence.time_zone.should == schedule.start_time.time_zone end it 'uses schedule zone for next_occurrences' do next_occurrences = schedule.next_occurrences(2, reference_time) next_occurrences.should == [Time.utc(2013, 1, 2), Time.utc(2013, 1, 3)] next_occurrences.each do |t| t.time_zone.should == schedule.start_time.time_zone end end it 'uses schedule zone for remaining_occurrences' do remaining_occurrences = schedule.remaining_occurrences(reference_time + 1.day) remaining_occurrences.should == [Time.utc(2013, 1, 2), Time.utc(2013, 1, 3)] remaining_occurrences.each do |t| t.time_zone.should == schedule.start_time.time_zone end end it 'uses schedule zone for occurrences' do occurrences = schedule.occurrences(reference_time + 1.day) occurrences.should == [Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)] occurrences.each do |t| t.time_zone.should == schedule.start_time.time_zone end end it 'uses schedule zone for occurrences_between' do occurrences_between = schedule.occurrences_between(reference_time, reference_time + 1.day) occurrences_between.should == [Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)] occurrences_between.each do |t| t.time_zone.should == schedule.start_time.time_zone end end it "uses schedule zone for occurrences_between with a rule terminated by #count" do utc = Time.utc(2013, 1, 1).in_time_zone('UTC') s = Schedule.new(utc) { |s| s.add_recurrence_rule Rule.daily.count(3) } occurrences_between = s.occurrences_between(reference_time, reference_time + 1.day) occurrences_between.should == [Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)] occurrences_between.each do |t| t.time_zone.should == schedule.start_time.time_zone end end it "uses schedule zone for occurrences_between with a rule terminated by #until" do utc = Time.utc(2013, 1, 1).in_time_zone('UTC') s = Schedule.new(utc) { |s| s.add_recurrence_rule Rule.daily.until(utc.advance(:days => 3)) } occurrences_between = s.occurrences_between(reference_time, reference_time + 1.day) occurrences_between.should == [Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)] occurrences_between.each do |t| t.time_zone.should == schedule.start_time.time_zone end end it "uses schedule zone for occurrences_between with an unterminated rule" do utc = Time.utc(2013, 1, 1).in_time_zone('UTC') s = Schedule.new(utc) { |s| s.add_recurrence_rule Rule.daily } occurrences_between = s.occurrences_between(reference_time, reference_time + 1.day) occurrences_between.should == [Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)] occurrences_between.each do |t| t.time_zone.should == schedule.start_time.time_zone end end end end end describe IceCube::Occurrence do it 'can be subtracted from a time' do start_time = Time.now occurrence = Occurrence.new(start_time) difference = (start_time + 60) - occurrence difference.should == 60 end end ice_cube-0.12.1/spec/examples/daily_rule_spec.rb000066400000000000000000000100521235562124600215750ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe DailyRule, 'interval validation' do it 'converts a string integer to an actual int when using the interval method' do rule = Rule.daily.interval("2") rule.validations_for(:interval).first.interval.should == 2 end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.daily("3") rule.validations_for(:interval).first.interval.should == 3 end it 'raises an argument error when a bad value is passed using the interval method' do expect { rule = Rule.daily.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end it 'raises an argument error when a bad value is passed' do expect { rule = Rule.daily("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end end describe DailyRule, 'occurs_on?' do context :system_time_zone => 'America/Vancouver' do it 'should include nearest time in DST start hour' do schedule = Schedule.new(t0 = Time.local(2013, 3, 9, 2, 30, 0)) schedule.add_recurrence_rule Rule.daily schedule.first(3).should == [ Time.local(2013, 3, 9, 2, 30, 0), # -0800 Time.local(2013, 3, 10, 3, 30, 0), # -0700 Time.local(2013, 3, 11, 2, 30, 0) # -0700 ] end it 'should not skip times in DST end hour' do schedule = Schedule.new(t0 = Time.local(2013, 11, 2, 2, 30, 0)) schedule.add_recurrence_rule Rule.daily schedule.first(3).should == [ Time.local(2013, 11, 2, 2, 30, 0), # -0700 Time.local(2013, 11, 3, 2, 30, 0), # -0800 Time.local(2013, 11, 4, 2, 30, 0) # -0800 ] end it 'should include nearest time to DST start when locking hour_of_day' do schedule = Schedule.new(t0 = Time.local(2013, 3, 9, 2, 0, 0)) schedule.add_recurrence_rule Rule.daily.hour_of_day(2) schedule.first(3).should == [ Time.local(2013, 3, 9, 2, 0, 0), # -0800 Time.local(2013, 3, 10, 3, 0, 0), # -0700 Time.local(2013, 3, 11, 2, 0, 0) # -0700 ] end end it 'should update previous interval' do schedule = double(start_time: t0 = Time.now) rule = Rule.daily(7) rule.interval(5) rule.next_time(t0 + 1, schedule, nil).should == t0 + 5 * ONE_DAY end it 'should produce the correct days for @interval = 1' do schedule = Schedule.new(t0 = Time.now) schedule.add_recurrence_rule Rule.daily #check assumption times = schedule.occurrences(t0 + 2 * ONE_DAY) times.size.should == 3 times.should == [t0, t0 + ONE_DAY, t0 + 2 * ONE_DAY] end it 'should produce the correct days for @interval = 2' do schedule = Schedule.new(t0 = Time.now) schedule.add_recurrence_rule Rule.daily(2) #check assumption (3) -- (1) 2 (3) 4 (5) 6 times = schedule.occurrences(t0 + 5 * ONE_DAY) times.size.should == 3 times.should == [t0, t0 + 2 * ONE_DAY, t0 + 4 * ONE_DAY] end it 'should produce the correct days for @interval = 2 when crossing into a new year' do schedule = Schedule.new(t0 = Time.utc(2011, 12, 29)) schedule.add_recurrence_rule Rule.daily(2) #check assumption (3) -- (1) 2 (3) 4 (5) 6 times = schedule.occurrences(t0 + 5 * ONE_DAY) times.size.should == 3 times.should == [t0, t0 + 2 * ONE_DAY, t0 + 4 * ONE_DAY] end it 'should produce the correct days for interval of 4 day with hour and minute of day set' do schedule = Schedule.new(t0 = Time.local(2010, 3, 1)) schedule.add_recurrence_rule Rule.daily(4).hour_of_day(5).minute_of_hour(45) #check assumption 2 -- 1 (2) (3) (4) 5 (6) times = schedule.occurrences(t0 + 5 * ONE_DAY) times.should == [ t0 + 5 * ONE_HOUR + 45 * ONE_MINUTE, t0 + 4 * ONE_DAY + 5 * ONE_HOUR + 45 * ONE_MINUTE ] end end end ice_cube-0.12.1/spec/examples/dst_spec.rb000066400000000000000000000316051235562124600202450ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule, 'occurs_on?' do # DST in 2010 is March 14th at 2am it 'crosses a daylight savings time boundary with a recurrence rule in local time, by utc conversion' do start_date = Time.local(2010, 3, 13, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.count(20) dates = schedule.first(20) dates.size.should == 20 #check assumptions dates.each do |date| date.utc?.should_not == true date.hour.should == 5 end end # DST in 2010 is November 7th at 2am it 'crosses a daylight savings time boundary (in the other direction) with a recurrence rule in local time, by utc conversion' do start_date = Time.local(2010, 11, 6, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.count(20) dates = schedule.first(20) dates.size.should == 20 #check assumptions dates.each do |date| date.utc?.should_not == true date.hour.should == 5 end end it 'cross a daylight savings time boundary with a recurrence rule in local time' do start_date = Time.local(2010, 3, 14, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily # each occurrence MUST occur at 5pm, then we win dates = schedule.occurrences(start_date + 20 * IceCube::ONE_DAY) last = start_date dates.each do |date| date.hour.should == 5 last = date end end it 'every two hours over a daylight savings time boundary, checking interval' do start_date = Time.local(2010, 11, 6, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.hourly(2) dates = schedule.first(100) #check assumption distance_in_hours = 0 dates.each do |d| d.should == start_date + IceCube::ONE_HOUR * distance_in_hours distance_in_hours += 2 end end it 'every 30 minutes over a daylight savings time boundary, checking interval' do start_date = Time.local(2010, 11, 6, 23, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.minutely(30) dates = schedule.first(100) #check assumption distance_in_minutes = 0 dates.each do |d| d.should == start_date + IceCube::ONE_MINUTE * distance_in_minutes distance_in_minutes += 30 end end it 'every 120 seconds over a daylight savings time boundary, checking interval' do start_date = Time.local(2010, 11, 6, 23, 50, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.secondly(120) dates = schedule.first(10) #check assumption distance_in_seconds = 0 dates.each do |d| d.should == start_date + distance_in_seconds distance_in_seconds += 120 end end it 'every other day over a daylight savings time boundary, checking hour/min/sec' do start_date = Time.local(2010, 11, 6, 20, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily(2) dates = schedule.first(10) #check assumption dates.each do |d| d.hour.should == start_date.hour d.min.should == start_date.min d.sec.should == start_date.sec end end it 'every other month over a daylight savings time boundary, checking day/hour/min/sec' do start_date = Time.local(2010, 11, 6, 20, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly(2) dates = schedule.first(10) #check assumption dates.each do |d| d.day.should == start_date.day d.hour.should == start_date.hour d.min.should == start_date.min d.sec.should == start_date.sec end end it 'every other year over a daylight savings time boundary, checking day/hour/min/sec' do start_date = Time.local(2010, 11, 6, 20, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly(2) dates = schedule.first(10) #check assumption dates.each do |d| d.month.should == start_date.month d.day.should == start_date.day d.hour.should == start_date.hour d.min.should == start_date.min d.sec.should == start_date.sec end end it 'LOCAL - has an until date on a rule that is over a DST from the start date' do start_date = Time.local(2010, 3, 13, 5, 0, 0) end_date = Time.local(2010, 3, 15, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_date) #make sure we end on the proper time schedule.all_occurrences.last.should == end_date end it 'UTC - has an until date on a rule that is over a DST from the start date' do start_date = Time.utc(2010, 3, 13, 5, 0, 0) end_date = Time.utc(2010, 3, 15, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_date) #make sure we end on the proper time schedule.all_occurrences.last.should == end_date end it 'LOCAL - has an until date on a rule that is over a DST from the start date (other direction)' do start_date = Time.local(2010, 11, 5, 5, 0, 0) end_date = Time.local(2010, 11, 10, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_date) #make sure we end on the proper time schedule.all_occurrences.last.should == end_date end it 'UTC - has an until date on a rule that is over a DST from the start date (other direction)' do start_date = Time.utc(2010, 11, 5, 5, 0, 0) end_date = Time.utc(2010, 11, 10, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_date) #make sure we end on the proper time schedule.all_occurrences.last.should == end_date end it 'LOCAL - has an end date on a rule that is over a DST from the start date' do start_date = Time.local(2010, 3, 13, 5, 0, 0) end_date = Time.local(2010, 3, 15, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily #make sure we end on the proper time schedule.occurrences(end_date).last.should == end_date end it 'UTC - has an end date on a rule that is over a DST from the start date' do start_date = Time.utc(2010, 3, 13, 5, 0, 0) end_date = Time.utc(2010, 3, 15, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily #make sure we end on the proper time schedule.occurrences(end_date).last.should == end_date end it 'LOCAL - has an end date on a rule that is over a DST from the start date (other direction)' do start_date = Time.local(2010, 11, 5, 5, 0, 0) end_date = Time.local(2010, 11, 10, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily #make sure we end on the proper time schedule.occurrences(end_date).last.should == end_date end it 'UTC - has an end date on a rule that is over a DST from the start date (other direction)' do start_date = Time.utc(2010, 11, 5, 5, 0, 0) end_date = Time.utc(2010, 11, 10, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily #make sure we end on the proper time schedule.occurrences(end_date).last.should == end_date end it 'local - should make dates on interval over dst - github issue 4' do start_date = Time.local(2010, 3, 12, 19, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily(3) schedule.first(3).should == [Time.local(2010, 3, 12, 19, 0, 0), Time.local(2010, 3, 15, 19, 0, 0), Time.local(2010, 3, 18, 19, 0, 0)] end it 'local - should make dates on monthly interval over dst - github issue 4' do start_date = Time.local(2010, 3, 12, 19, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly(2) schedule.first(6).should == [Time.local(2010, 3, 12, 19, 0, 0), Time.local(2010, 5, 12, 19, 0, 0), Time.local(2010, 7, 12, 19, 0, 0), Time.local(2010, 9, 12, 19, 0, 0), Time.local(2010, 11, 12, 19, 0, 0), Time.local(2011, 1, 12, 19, 0, 0)] end it 'local - should make dates on monthly interval over dst - github issue 4' do start_date = Time.local(2010, 3, 12, 19, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly schedule.first(10).should == [Time.local(2010, 3, 12, 19, 0, 0), Time.local(2010, 4, 12, 19, 0, 0), Time.local(2010, 5, 12, 19, 0, 0), Time.local(2010, 6, 12, 19, 0, 0), Time.local(2010, 7, 12, 19, 0, 0), Time.local(2010, 8, 12, 19, 0, 0), Time.local(2010, 9, 12, 19, 0, 0), Time.local(2010, 10, 12, 19, 0, 0), Time.local(2010, 11, 12, 19, 0, 0), Time.local(2010, 12, 12, 19, 0, 0)] end it 'local - should make dates on yearly interval over dst - github issue 4' do start_date = Time.local(2010, 3, 12, 19, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly(2) schedule.first(3).should == [Time.local(2010, 3, 12, 19, 0, 0), Time.local(2012, 3, 12, 19, 0, 0), Time.local(2014, 3, 12, 19, 0, 0)] end it "local - should make dates on monthly (day of week) inverval over dst - github issue 5" do start_date = Time.local(2010, 3, 7, 12, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_week(:sunday => [1]) schedule.first(3).should == [Time.local(2010, 3, 7, 12, 0, 0), Time.local(2010, 4, 4, 12, 0, 0), Time.local(2010, 5, 2, 12, 0, 0)] end it "local - should make dates on monthly (day of month) inverval over dst - github issue 5" do start_date = Time.local(2010, 3, 1, 12, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_month(1) schedule.first(3).should == [Time.local(2010, 3, 1, 12, 0, 0), Time.local(2010, 4, 1, 12, 0, 0), Time.local(2010, 5, 1, 12, 0, 0)] end it "local - should make dates on weekly (day) inverval over dst - github issue 5" do start_date = Time.local(2010, 3, 7, 12, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:sunday) schedule.first(3).should == [Time.local(2010, 3, 7, 12, 0, 0), Time.local(2010, 3, 14, 12, 0, 0), Time.local(2010, 3, 21, 12, 0, 0)] end it "local - should make dates on monthly (day of year) inverval over dst - github issue 5" do start_date = Time.local(2010, 3, 7, 12, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_year(1) schedule.first(3).should == [Time.local(2011, 1, 1, 12, 0, 0), Time.local(2012, 1, 1, 12, 0, 0), Time.local(2013, 1, 1, 12, 0, 0)] end it "local - should make dates on monthly (month_of_year) inverval over dst - github issue 5" do start_date = Time.local(2010, 3, 7, 12, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:april).day_of_month(10) schedule.first(3).should == [Time.local(2010, 4, 10, 12, 0, 0), Time.local(2011, 4, 10, 12, 0, 0), Time.local(2012, 4, 10, 12, 0, 0)] end it "skips double occurrences from end of DST" do Time.zone = "America/Denver" t0 = Time.zone.parse("Sun, 03 Nov 2013 01:30:00 MDT -06:00") schedule = IceCube::Schedule.new(t0) { |s| s.rrule IceCube::Rule.daily.count(3) } schedule.all_occurrences.should == [t0, t0 + 25*ONE_HOUR, t0 + 49*ONE_HOUR] end it "does not skip hourly rules over DST" do Time.zone = "America/Denver" t0 = Time.zone.parse("Sun, 03 Nov 2013 01:30:00 MDT -06:00") schedule = IceCube::Schedule.new(t0) { |s| s.rrule IceCube::Rule.hourly.count(3) } schedule.all_occurrences.should == [t0, t0 + ONE_HOUR, t0 + 2*ONE_HOUR] end it "does not skip minutely rules with minute of hour over DST" do Time.zone = "America/Denver" t0 = Time.zone.parse("Sun, 03 Nov 2013 01:30:00 MDT -06:00") schedule = IceCube::Schedule.new(t0) { |s| s.rrule IceCube::Rule.hourly.count(3) } schedule.rrule IceCube::Rule.minutely.minute_of_hour([0, 15, 30, 45]) schedule.first(5).should == [t0, t0 + 15*60, t0 + 30*60, t0 + 45*60, t0 + 60*60] end it "does not skip minutely rules with second of minute over DST" do Time.zone = "America/Denver" t0 = Time.zone.parse("Sun, 03 Nov 2013 01:30:00 MDT -06:00") schedule = IceCube::Schedule.new(t0) { |s| s.rrule IceCube::Rule.hourly.count(3) } schedule.rrule IceCube::Rule.minutely(15).second_of_minute(0) schedule.first(5).should == [t0, t0 + 15*60, t0 + 30*60, t0 + 45*60, t0 + 60*60] end end ice_cube-0.12.1/spec/examples/flexible_hash_spec.rb000066400000000000000000000032211235562124600222410ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe FlexibleHash do subject(:hash) { described_class.new(:sym => true, "str" => true, 1 => true) } describe "#[]" do specify ":sym => :sym is found" do hash[:sym].should be true end specify "'sym' => :sym is found" do hash["sym"].should be true end specify "'str' => 'str' is found" do hash["str"].should be true end specify ":str => 'str' is found" do hash[:str].should be true end specify "other types are found" do hash[1].should be true end specify "missing keys are nil" do hash[-1].should be nil end end describe "#fetch" do it "yields missing keys" do hash.fetch(-1) { |k| k == -1 }.should be true end end describe "#delete" do specify ":sym => :sym is found and removed" do hash.delete(:sym).should be true hash[:sym].should be nil end specify "'sym' => :sym is found and removed" do hash.delete("sym").should be true hash["sym"].should be nil end specify "'str' => 'str' is found and removed" do hash.delete("str").should be true hash["str"].should be nil end specify ":str => 'str' is found and removed" do hash.delete(:str).should be true hash[:str].should be nil end specify "other types are found and removed" do hash.delete(1).should be true hash[1].should be nil end specify "missing keys are nil" do hash.delete(-1).should be nil end end end end ice_cube-0.12.1/spec/examples/hash_parser_spec.rb000066400000000000000000000010561235562124600217470ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe HashParser do let(:t) { Time.utc(2014, 3, 22) } describe "#to_schedule" do subject(:schedule) { HashParser.new(hash).to_schedule } let(:hash) { {start_time: t, duration: 3600} } its(:start_time) { should == t } its(:duration) { should == 3600 } describe "end_time overrules duration" do let(:hash) { {start_time: t, end_time: t + 1800, duration: 3600} } its(:duration) { should == 1800 } end end end end ice_cube-0.12.1/spec/examples/hourly_rule_spec.rb000066400000000000000000000051341235562124600220220ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe HourlyRule do describe 'interval validation' do it 'converts a string integer to an actual int when using the interval method' do rule = Rule.hourly.interval("2") rule.validations_for(:interval).first.interval.should == 2 end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.hourly("3") rule.validations_for(:interval).first.interval.should == 3 end it 'raises an argument error when a bad value is passed' do expect { rule = Rule.hourly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { rule = Rule.hourly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end end context :system_time_zone => 'America/Vancouver' do it 'should work across DST start hour' do schedule = Schedule.new(t0 = Time.local(2013, 3, 10, 1, 0, 0)) schedule.add_recurrence_rule Rule.hourly schedule.first(3).should == [ Time.local(2013, 3, 10, 1, 0, 0), # -0800 Time.local(2013, 3, 10, 3, 0, 0), # -0700 Time.local(2013, 3, 10, 4, 0, 0) # -0700 ] end it 'should not skip times in DST end hour' do schedule = Schedule.new(t0 = Time.local(2013, 11, 3, 0, 0, 0)) schedule.add_recurrence_rule Rule.hourly schedule.first(4).should == [ Time.local(2013, 11, 3, 0, 0, 0), # -0700 Time.local(2013, 11, 3, 1, 0, 0) - ONE_HOUR, # -0700 Time.local(2013, 11, 3, 1, 0, 0), # -0800 Time.local(2013, 11, 3, 2, 0, 0), # -0800 ] end end it 'should update previous interval' do schedule = double(start_time: t0 = Time.now) rule = Rule.hourly(7) rule.interval(5) rule.next_time(t0 + 1, schedule, nil).should == t0 + 5.hours end it 'should produce the correct days for @interval = 3' do start_date = DAY schedule = Schedule.new(start_date) schedule = Schedule.from_yaml(schedule.to_yaml) schedule.add_recurrence_rule Rule.hourly(3) #check assumption (3) -- (1) 2 (3) 4 (5) 6 dates = schedule.first(3) dates.size.should == 3 dates.should == [DAY, DAY + 3 * ONE_HOUR, DAY + 6 * ONE_HOUR] end end end ice_cube-0.12.1/spec/examples/ice_cube_spec.rb000066400000000000000000000676441235562124600212250ustar00rootroot00000000000000require 'active_support/time' require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule do it 'should work with a simple schedule' do rule = IceCube::Rule.daily.day(:monday) schedule = IceCube::Schedule.new(Time.now) schedule.add_recurrence_rule rule lambda { schedule.first(3) }.should_not raise_error end it 'should respond to complex combinations (1)' do start_date = Time.utc(2010, 1, 1) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly(2).day(:wednesday).month_of_year(:april) #check assumptions dates = schedule.occurrences(Time.utc(2011, 12, 31)) #two years dates.size.should == 4 dates.each do |date| date.wday.should == 3 date.month.should == 4 date.year.should == start_date.year #since we're doing every other end end it 'should return an added occurrence time' do schedule = IceCube::Schedule.new(t0 = Time.now) schedule.add_recurrence_time(t0 + 2) schedule.occurrences(t0 + 50).should == [t0, t0 + 2] end it 'should not return an occurrence time that is excluded' do schedule = IceCube::Schedule.new(t0 = Time.now) schedule.add_recurrence_time(t0 + 2) schedule.add_exception_time(t0 + 2) schedule.occurrences(t0 + 50).should == [t0] end it 'should return properly with a combination of a recurrence and exception rule' do schedule = IceCube::Schedule.new(DAY) schedule.add_recurrence_rule IceCube::Rule.daily # every day schedule.add_exception_rule IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday) # except these #check assumption - in 2 weeks, we should have 8 days schedule.occurrences(DAY + 13 * IceCube::ONE_DAY).size.should == 8 end it 'should be able to exclude a certain date from a range' do start_date = Time.local 2012, 3, 1 schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily schedule.add_exception_time(start_date + 1 * IceCube::ONE_DAY) # all days except tomorrow # check assumption dates = schedule.occurrences(start_date + 13 * IceCube::ONE_DAY) # 2 weeks dates.size.should == 13 # 2 weeks minus 1 day dates.should_not include(start_date + 1 * IceCube::ONE_DAY) end it 'make a schedule with a start_date not included in a rule, and make sure that count behaves properly' do start_date = WEDNESDAY schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:thursday).count(5) dates = schedule.all_occurrences dates.uniq.size.should == 5 dates.each { |d| d.wday.should == 4 } dates.should_not include(WEDNESDAY) end it 'make a schedule with a start_date included in a rule, and make sure that count behaves properly' do start_date = WEDNESDAY + IceCube::ONE_DAY schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:thursday).count(5) dates = schedule.all_occurrences dates.uniq.size.should == 5 dates.each { |d| d.wday.should == 4 } dates.should include(WEDNESDAY + IceCube::ONE_DAY) end it 'should work as expected with a second_of_minute rule specified' do start_date = DAY schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly.second_of_minute(30) dates = schedule.occurrences(start_date + 30 * 60) dates.each { |date| date.sec.should == 30 } end it 'ensure that when count on a rule is set to 0, 0 occurrences come back' do start_date = DAY schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.count(0) schedule.all_occurrences.should == [] end it 'should be able to be schedules at 1:st:st and 2:st:st every day' do start_date = Time.utc(2007, 9, 2, 9, 15, 25) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.hour_of_day(1, 2).count(6) dates = schedule.all_occurrences dates.should == [Time.utc(2007, 9, 3, 1, 15, 25), Time.utc(2007, 9, 3, 2, 15, 25), Time.utc(2007, 9, 4, 1, 15, 25), Time.utc(2007, 9, 4, 2, 15, 25), Time.utc(2007, 9, 5, 1, 15, 25), Time.utc(2007, 9, 5, 2, 15, 25)] end it 'should be able to be schedules at 1:0:st and 2:0:st every day' do start_date = Time.utc(2007, 9, 2, 9, 15, 25) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.hour_of_day(1, 2).minute_of_hour(0).count(6) dates = schedule.all_occurrences dates.should == [Time.utc(2007, 9, 3, 1, 0, 25), Time.utc(2007, 9, 3, 2, 0, 25), Time.utc(2007, 9, 4, 1, 0, 25), Time.utc(2007, 9, 4, 2, 0, 25), Time.utc(2007, 9, 5, 1, 0, 25), Time.utc(2007, 9, 5, 2, 0, 25)] end it 'will only return count# if you specify a count and use .first' do start_date = Time.now schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.count(10) dates = schedule.first(200) dates.size.should == 10 end it 'occurs yearly' do start_date = DAY schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly dates = schedule.first(10) dates.each do |date| date.month.should == start_date.month date.day.should == start_date.day date.hour.should == start_date.hour date.min.should == start_date.min date.sec.should == start_date.sec end end it 'occurs daily' do start_date = Time.now schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily dates = schedule.first(10) dates.each do |date| date.hour.should == start_date.hour date.min.should == start_date.min date.sec.should == start_date.sec end end it 'occurs hourly' do start_date = Time.now schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.hourly dates = schedule.first(10) dates.each do |date| date.min.should == start_date.min date.sec.should == start_date.sec end end it 'occurs minutely' do start_date = Time.now schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.minutely dates = schedule.first(10) dates.each do |date| date.sec.should == start_date.sec end end it 'occurs every second for an hour' do start_date = Time.now schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.secondly.count(60) # build the expectation list expectation = [] 0.upto(59) { |i| expectation << start_date + i } # compare with what we get dates = schedule.all_occurrences dates.size.should == 60 schedule.all_occurrences.should == expectation end it 'perform a every day LOCAL and make sure we get back LOCAL' do Time.zone = 'Eastern Time (US & Canada)' start_date = Time.zone.local(2010, 9, 2, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily schedule.first(10).each do |d| d.utc?.should == false d.hour.should == 5 (d.utc_offset == -5 * IceCube::ONE_HOUR || d.utc_offset == -4 * IceCube::ONE_HOUR).should be_true end end it 'perform a every day LOCAL and make sure we get back LOCAL' do start_date = Time.utc(2010, 9, 2, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily schedule.first(10).each do |d| d.utc?.should == true d.utc_offset.should == 0 d.hour.should == 5 end end # here we purposely put a UTC time that is before the range ends, to # verify ice_cube is properly checking until bounds it 'works with a until date that is UTC, but the start date is local' do Time.zone = 'Eastern Time (US & Canada)' start_date = Time.zone.local(2010, 11, 6, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.until(Time.utc(2010, 11, 10, 8, 0, 0)) #4 o clocal local #check assumptions dates = schedule.all_occurrences dates.each { |d| d.utc?.should == false } dates.should == [Time.zone.local(2010, 11, 6, 5, 0, 0), Time.zone.local(2010, 11, 7, 5, 0, 0), Time.zone.local(2010, 11, 8, 5, 0, 0), Time.zone.local(2010, 11, 9, 5, 0, 0)] end # here we purposely put a local time that is before the range ends, to # verify ice_cube is properly checking until bounds it 'works with a until date that is local, but the start date is UTC' do start_date = Time.utc(2010, 11, 6, 5, 0, 0) Time.zone = 'Eastern Time (US & Canada)' schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.until(Time.zone.local(2010, 11, 9, 23, 0, 0)) #4 o UTC time #check assumptions dates = schedule.all_occurrences dates.each { |d| d.utc?.should == true } dates.should == [Time.utc(2010, 11, 6, 5, 0, 0), Time.utc(2010, 11, 7, 5, 0, 0), Time.utc(2010, 11, 8, 5, 0, 0), Time.utc(2010, 11, 9, 5, 0, 0)] end it 'works with a monthly rule iterating on UTC' do start_date = Time.utc(2010, 4, 24, 15, 45, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly dates = schedule.first(10) dates.each do |d| d.day.should == 24 d.hour.should == 15 d.min.should == 45 d.sec.should == 0 d.utc?.should be_true end end it 'can retrieve rrules from a schedule' do schedule = IceCube::Schedule.new(Time.now) rules = [IceCube::Rule.daily, IceCube::Rule.monthly, IceCube::Rule.yearly] rules.each { |r| schedule.add_recurrence_rule(r) } # pull the rules back out of the schedule and compare schedule.rrules.should == rules end it 'can retrieve exrules from a schedule' do schedule = IceCube::Schedule.new(Time.now) rules = [IceCube::Rule.daily, IceCube::Rule.monthly, IceCube::Rule.yearly] rules.each { |r| schedule.add_exception_rule(r) } # pull the rules back out of the schedule and compare schedule.exrules.should == rules end it 'can retrieve recurrence times from a schedule' do schedule = IceCube::Schedule.new(Time.now) times = [Time.now, Time.now + 5, Time.now + 10] times.each { |d| schedule.add_recurrence_time(d) } # pull the dates back out of the schedule and compare schedule.rtimes.should == times end it 'can retrieve exception_times from a schedule' do schedule = IceCube::Schedule.new(Time.now) times = [Time.now, Time.now + 5, Time.now + 10] times.each { |d| schedule.add_exception_time(d) } # pull the dates back out of the schedule and compare schedule.extimes.should == times end it 'can reuse the same rule' do schedule = IceCube::Schedule.new(Time.now) rule = IceCube::Rule.daily schedule.add_recurrence_rule rule result1 = schedule.first(10) rule.day(:monday) # check to make sure the change affected the rule schedule.first(10).should_not == result1 end it 'ensures that month of year (3) is march' do schedule = IceCube::Schedule.new(DAY) schedule.add_recurrence_rule IceCube::Rule.daily.month_of_year(:march) schedule2 = IceCube::Schedule.new(DAY) schedule2.add_recurrence_rule IceCube::Rule.daily.month_of_year(3) schedule.first(10).should == schedule2.first(10) end it 'ensures that day of week (1) is monday' do schedule = IceCube::Schedule.new(DAY) schedule.add_recurrence_rule IceCube::Rule.daily.day(:monday) schedule2 = IceCube::Schedule.new(DAY) schedule2.add_recurrence_rule IceCube::Rule.daily.day(1) schedule.first(10).should == schedule2.first(10) end it 'should be able to find occurrences between two dates which are both in the future' do start_time = Time.local(2012, 5, 1) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily dates = schedule.occurrences_between(start_time + IceCube::ONE_DAY * 2, start_time + IceCube::ONE_DAY * 4) dates.should == [start_time + IceCube::ONE_DAY * 2, start_time + IceCube::ONE_DAY * 3, start_time + IceCube::ONE_DAY * 4] end it 'should use start date on a bi-weekly recurrence pattern to find the occurrences_between when interval > 1' do start_date = Time.local(2011, 3, 20) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly(2).day(:sunday) occurrences = schedule.occurrences_between(Time.local(2012, 7, 7), Time.local(2012, 7, 9)) occurrences.should == [Time.local(2012, 7, 8)] end it 'should be able to tell us when there is at least one occurrence between two dates' do start_date = WEDNESDAY schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:friday) true.should == schedule.occurs_between?(start_date, start_date + IceCube::ONE_DAY * 3) end it 'should be able to tell us when there is no occurrence between two dates' do start_date = WEDNESDAY schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:friday) false.should == schedule.occurs_between?(start_date, start_date + IceCube::ONE_DAY) end it 'should be able to get back rdates from a schedule' do schedule = IceCube::Schedule.new DAY schedule.add_recurrence_time DAY schedule.add_recurrence_time(DAY + 2) schedule.rtimes.should == [DAY, DAY + 2] end it 'should be able to get back exception times from a schedule' do schedule = IceCube::Schedule.new DAY schedule.add_exception_time DAY schedule.add_exception_time(DAY + 2) schedule.extimes.should == [DAY, DAY + 2] end it 'should allow calling of .first on a schedule with no arguments' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_time start_time schedule.first.should == start_time end it 'should be able to ignore nil dates that are inserted as part of a collection to add_recurrence_time' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_time start_time schedule.add_recurrence_time start_time + IceCube::ONE_DAY schedule.add_recurrence_time nil schedule.all_occurrences.should == [start_time, start_time + IceCube::ONE_DAY] end it 'should be able to use all_occurrences with no rules' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_time start_time lambda do schedule.all_occurrences.should == [start_time] end.should_not raise_error end it 'should use occurs_at? when calling occurring_at? with no duration' do schedule = IceCube::Schedule.new schedule.should_receive(:occurs_at?) schedule.occurring_at?(Time.now) end it 'should be able to specify a duration on a schedule use occurring_at? on the schedule to find out if a given time is included' do start_time = Time.local 2010, 5, 6, 10, 0, 0 schedule = IceCube::Schedule.new(start_time, :duration => 3600) schedule.add_recurrence_rule IceCube::Rule.daily schedule.occurring_at?(Time.local(2010, 5, 6, 10, 30, 0)).should be_true #true end it 'should be able to specify a duration on a schedule and use occurring_at? on that schedule to make sure a time is not included' do start_time = Time.local 2010, 5, 6, 10, 0, 0 schedule = IceCube::Schedule.new(start_time, :duration => 3600) schedule.add_recurrence_rule IceCube::Rule.daily schedule.occurring_at?(Time.local(2010, 5, 6, 9, 59, 0)).should be_false schedule.occurring_at?(Time.local(2010, 5, 6, 11, 0, 0)).should be_false end it 'should be able to specify a duration on a schedule and use occurring_at? on that schedule to make sure the outer bounds are included' do start_time = Time.local 2010, 5, 6, 10, 0, 0 schedule = IceCube::Schedule.new(start_time, :duration => 3600) schedule.add_recurrence_rule IceCube::Rule.daily schedule.occurring_at?(Time.local(2010, 5, 6, 10, 0, 0)).should be_true schedule.occurring_at?(Time.local(2010, 5, 6, 10, 59, 59)).should be_true end it 'should be able to explicity remove a certain minute from a duration' do start_time = Time.local 2010, 5, 6, 10, 0, 0 schedule = IceCube::Schedule.new(start_time, :duration => 3600) schedule.add_recurrence_rule IceCube::Rule.daily schedule.add_exception_time Time.local(2010, 5, 6, 10, 21, 30) schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 29)).should be_true schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 30)).should be_false schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 31)).should be_true end it 'should be able to specify an end time for the schedule' do start_time = DAY end_time = DAY + IceCube::ONE_DAY * 2 schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time) schedule.all_occurrences.should == [DAY, DAY + 1*IceCube::ONE_DAY, DAY + 2*IceCube::ONE_DAY] end it 'should be able to specify an end time for the schedule and only get those on .first' do start_time = DAY # ensure proper response without the end time schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily schedule.first(5).should == [DAY, DAY + 1*IceCube::ONE_DAY, DAY + 2*IceCube::ONE_DAY, DAY + 3*IceCube::ONE_DAY, DAY + 4*IceCube::ONE_DAY] # and then ensure that with the end time it stops it at the right day schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.until(DAY + IceCube::ONE_DAY * 2 + 1) schedule.first(5).should == [DAY, DAY + 1 * IceCube::ONE_DAY, DAY + 2 * IceCube::ONE_DAY] end it 'should be able to specify an end date and go to/from yaml' do start_time = DAY end_time = DAY + IceCube::ONE_DAY * 2 schedule = IceCube::Schedule.new(start_time, :end_time => end_time) schedule.add_recurrence_rule IceCube::Rule.daily schedule2 = IceCube::Schedule.from_yaml schedule.to_yaml schedule2.end_time.should == end_time end it 'should be able to specify an end date for the schedule and only get those on .occurrences_between' do start_time = DAY end_time = DAY + IceCube::ONE_DAY * 2 schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time) expectation = [DAY, DAY + IceCube::ONE_DAY, DAY + 2*IceCube::ONE_DAY] schedule.occurrences_between(start_time - IceCube::ONE_DAY, start_time + 4 * IceCube::ONE_DAY).should == expectation end it 'should be able to specify an end date for the schedule and only get those on .occurrences' do start_time = DAY end_time = DAY + IceCube::ONE_DAY * 2 schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time) expectation = [DAY, DAY + IceCube::ONE_DAY, DAY + 2*IceCube::ONE_DAY] schedule.occurrences(start_time + 4 * IceCube::ONE_DAY).should == expectation end it 'should be able to work with an end date and .occurs_at' do start_time = DAY end_time = DAY + IceCube::ONE_DAY * 2 schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time) schedule.occurs_at?(DAY + 4*IceCube::ONE_DAY).should be_false # out of range end it 'should be able to work with an end date and .occurring_at' do start_time = DAY end_time = DAY + IceCube::ONE_DAY * 2 schedule = IceCube::Schedule.new(start_time, :duration => 20) schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time) schedule.occurring_at?((DAY + 2*IceCube::ONE_DAY + 10)).should be_true # in range schedule.occurring_at?((DAY + 4*IceCube::ONE_DAY + 10)).should be_false # out of range end it 'should not create an infinite loop crossing over february - github issue 6' do schedule = IceCube::Schedule.new(Time.parse('2010-08-30')) schedule.add_recurrence_rule IceCube::Rule.monthly(6) schedule.occurrences_between(Time.parse('2010-07-01'), Time.parse('2010-09-01')) end it 'should be able to exist on the 28th of each month crossing over february - github issue 6a' do schedule = IceCube::Schedule.new(Time.local(2010, 1, 28)) schedule.add_recurrence_rule IceCube::Rule.monthly schedule.first(3).should == [Time.local(2010, 1, 28), Time.local(2010, 2, 28), Time.local(2010, 3, 28)] end it 'should be able to exist on the 29th of each month crossing over february - github issue 6a' do schedule = IceCube::Schedule.new(Time.zone.local(2010, 1, 29)) schedule.add_recurrence_rule IceCube::Rule.monthly schedule.first(3).should == [Time.zone.local(2010, 1, 29), Time.zone.local(2010, 2, 28), Time.zone.local(2010, 3, 29)] end it 'should be able to exist on the 30th of each month crossing over february - github issue 6a' do schedule = IceCube::Schedule.new(Time.zone.local(2010, 1, 30)) schedule.add_recurrence_rule IceCube::Rule.monthly schedule.first(3).should == [Time.zone.local(2010, 1, 30), Time.zone.local(2010, 2, 28), Time.zone.local(2010, 3, 30)] end it 'should be able to exist ont he 31st of each month crossing over february - github issue 6a' do schedule = IceCube::Schedule.new(Time.zone.local(2010, 1, 31)) schedule.add_recurrence_rule IceCube::Rule.monthly schedule.first(3).should == [Time.zone.local(2010, 1, 31), Time.zone.local(2010, 2, 28), Time.zone.local(2010, 3, 31)] end it 'should deal with a yearly rule that has februaries with different mdays' do schedule = IceCube::Schedule.new(Time.local(2008, 2, 29)) schedule.add_recurrence_rule IceCube::Rule.yearly schedule.first(3).should == [Time.local(2008, 2, 29), Time.local(2009, 2, 28), Time.local(2010, 2, 28)] end it 'should work with every other month even when the day of the month iterating on does not exist' do schedule = IceCube::Schedule.new(Time.zone.local(2010, 1, 31)) schedule.add_recurrence_rule IceCube::Rule.monthly(2) schedule.first(6).should == [Time.zone.local(2010, 1, 31), Time.zone.local(2010, 3, 31), Time.zone.local(2010, 5, 31), Time.zone.local(2010, 7, 31), Time.zone.local(2010, 9, 30), Time.zone.local(2010, 11, 30)] end it 'should be able to go into february and stay on the same day' do schedule = IceCube::Schedule.new(Time.local(2010, 1, 5)) schedule.add_recurrence_rule IceCube::Rule.monthly schedule.first(2).should == [Time.local(2010, 1, 5), Time.local(2010, 2, 5)] end it 'should be able to know when to stop with an end date and a rule that misses a few times' do schedule = IceCube::Schedule.new(Time.local(2010, 2, 29)) schedule.add_recurrence_rule IceCube::Rule.yearly.until(Time.local(2010, 10, 30)) schedule.first(10).should == [Time.local(2010, 2, 29)] end it 'should be able to know when to stop with an end date and a rule that misses a few times' do schedule = IceCube::Schedule.new(Time.local(2010, 2, 29)) schedule.add_recurrence_rule IceCube::Rule.yearly.until(Time.local(2010, 10, 30)) schedule.first(10).should == [Time.local(2010, 2, 29)] end it 'should be able to know when to stop with an end date and a rule that misses a few times' do schedule = IceCube::Schedule.new(Time.local(2010, 2, 29)) schedule.add_recurrence_rule IceCube::Rule.yearly.count(1) schedule.first(10).should == [Time.local(2010, 2, 29)] end it 'should have some convenient aliases' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.start_time.should == schedule.start_time schedule.end_time.should == schedule.end_time end it 'should have some convenient alias for rrules' do schedule = IceCube::Schedule.new(Time.now) daily = IceCube::Rule.daily; monthly = IceCube::Rule.monthly schedule.add_recurrence_rule daily schedule.rrule monthly schedule.rrules.should == [daily, monthly] end it 'should have some convenient alias for exrules' do schedule = IceCube::Schedule.new(Time.now) daily = IceCube::Rule.daily; monthly = IceCube::Rule.monthly schedule.add_exception_rule daily schedule.exrule monthly schedule.exrules.should == [daily, monthly] end it 'should have some convenient alias for recurrence_times' do schedule = IceCube::Schedule.new(Time.now) schedule.add_recurrence_time Time.local(2010, 8, 13) schedule.rtime Time.local(2010, 8, 14) schedule.rtimes.should == [Time.local(2010, 8, 13), Time.local(2010, 8, 14)] end it 'should have some convenient alias for extimes' do schedule = IceCube::Schedule.new(Time.now) schedule.add_exception_time Time.local(2010, 8, 13) schedule.extime Time.local(2010, 8, 14) schedule.extimes.should == [Time.local(2010, 8, 13), Time.local(2010, 8, 14)] end it 'should be able to have a rule and an exrule' do schedule = IceCube::Schedule.new(Time.local(2010, 8, 27, 10)) schedule.rrule IceCube::Rule.daily schedule.exrule IceCube::Rule.daily.day(:friday) schedule.occurs_on?(Date.new(2010, 8, 27)).should be_false schedule.occurs_on?(Date.new(2010, 8, 28)).should be_true end it 'should always generate the correct number of days for .first' do s = IceCube::Schedule.new(Time.zone.parse('1-1-1985')) r = IceCube::Rule.weekly(3).day(:monday, :wednesday, :friday) s.add_recurrence_rule(r) # test sizes s.first(3).size.should == 3 s.first(4).size.should == 4 s.first(5).size.should == 5 end it 'should always generate the date based off the start_date_override when specified in from_yaml' do start_date = DAY # zero seconds schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.minutely start_date_override = DAY + 20 schedule2 = IceCube::Schedule.from_yaml(schedule.to_yaml, :start_date_override => start_date_override) dates = schedule2.first(10) dates.each do |date| date.sec.should == start_date_override.sec end end it 'should always generate the date based off the start_date_override when specified in from_hash' do start_date = DAY # zero seconds schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.minutely start_date_override = DAY + 20 schedule2 = IceCube::Schedule.from_hash(schedule.to_hash, :start_date_override => start_date_override) dates = schedule2.first(10) dates.each do |date| date.sec.should == start_date_override.sec end end it 'should use current date as start date when invoked with a nil parameter' do schedule = IceCube::Schedule.new nil (Time.now - schedule.start_time).should be < 100 end it 'should be able to get the occurrence count for a rule' do rule = IceCube::Rule.daily.count(5) rule.occurrence_count.should == 5 end it 'should be able to remove a count validation from a rule' do rule = IceCube::Rule.daily.count(5) rule.count nil rule.to_hash.should_not have_key(:count) rule.occurrence_count.should be_nil end it 'should be able to remove an until validation from a rule' do rule = IceCube::Rule.daily.until(Time.now + IceCube::ONE_DAY) rule.to_hash[:until].should_not be_nil rule.until nil rule.to_hash.should_not have_key(:until) end it 'should not have ridiculous load times for minutely on next_occurrence (from sidetiq)' do quick_attempt_test do IceCube::Schedule.new(Time.utc(2010, 1, 1)) do |s| s.add_recurrence_rule(IceCube::Rule.minutely(1800)) end end end it 'should not have ridiculous load times for every 10 on next_occurrence #210' do quick_attempt_test do IceCube::Schedule.new(Time.utc(2010, 1, 1)) do |s| s.add_recurrence_rule(IceCube::Rule.hourly.minute_of_hour(0, 10, 20, 30, 40, 50)) end end quick_attempt_test do IceCube::Schedule.new(Time.utc(2010, 1, 1)) do |s| s.add_recurrence_rule(IceCube::Rule.daily) end end end def quick_attempt_test time = Time.now 10.times do (yield).next_occurrence(Time.now) end total = Time.now - time total.should be < 0.1 end end ice_cube-0.12.1/spec/examples/minutely_rule_spec.rb000066400000000000000000000051271235562124600223500ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe MinutelyRule, 'interval validation' do it 'converts a string integer to an actual int when using the interval method' do rule = Rule.minutely.interval("2") rule.validations_for(:interval).first.interval.should == 2 end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.minutely("3") rule.validations_for(:interval).first.interval.should == 3 end it 'raises an argument error when a bad value is passed' do expect { rule = Rule.minutely("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end it 'raises an argument error when a bad value is passed when using the interval method' do expect { rule = Rule.minutely.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end end describe MinutelyRule do it 'should update previous interval' do schedule = double(start_time: t0 = Time.now) rule = Rule.minutely(7) rule.interval(5) rule.next_time(t0 + 1, schedule, nil).should == t0 + 5 * IceCube::ONE_MINUTE end it 'should work across DST start hour' do std_end = Time.local(2013, 3, 10, 1, 59, 0) schedule = Schedule.new(std_end) schedule.add_recurrence_rule Rule.minutely schedule.first(3).should == [ std_end, std_end + ONE_MINUTE, std_end + ONE_MINUTE * 2 ] end it 'should not skip DST end hour' do std_start = Time.local(2013, 11, 3, 1, 0, 0) schedule = Schedule.new(std_start - 60) schedule.add_recurrence_rule Rule.minutely schedule.first(3).should == [ std_start - ONE_MINUTE, std_start, std_start + ONE_MINUTE ] end it 'should produce the correct days for @interval = 3' do start_date = DAY schedule = Schedule.new(start_date) schedule = Schedule.from_yaml(schedule.to_yaml) schedule.add_recurrence_rule Rule.hourly(3) #check assumption (3) -- (1) 2 (3) 4 (5) 6 dates = schedule.first(3) dates.size.should == 3 dates.should == [DAY, DAY + 3 * ONE_HOUR, DAY + 6 * ONE_HOUR] end it 'should produce the correct minutes starting with an offset' do schedule = Schedule.new Time.new(2013, 11, 1, 1, 3, 0) schedule.rrule Rule.minutely(5) schedule.next_occurrence(Time.new(2013, 11, 1, 1, 4, 0)).should == Time.new(2013, 11, 1, 1, 8, 0) end end end ice_cube-0.12.1/spec/examples/monthly_rule_spec.rb000066400000000000000000000144401235562124600221720ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe MonthlyRule, 'interval validation' do it 'converts a string integer to an actual int when using the interval method' do rule = Rule.monthly.interval("2") rule.validations_for(:interval).first.interval.should == 2 end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.monthly("3") rule.validations_for(:interval).first.interval.should == 3 end it 'converts a string integer to an actual int' do rule = Rule.monthly("1") rule.instance_variable_get(:@interval).should == 1 end it 'raises an argument error when a bad value is passed' do expect { rule = Rule.monthly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { rule = Rule.monthly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end end describe MonthlyRule do it 'should update previous interval' do schedule = double(start_time: t0 = Time.utc(2013, 5, 17)) rule = Rule.monthly(3) rule.interval(1) rule.next_time(t0 + 1, schedule, nil).should == t0 + 31.days end it 'should produce the correct number of days for @interval = 1' do schedule = Schedule.new(t0 = Time.now) schedule.add_recurrence_rule Rule.monthly #check assumption schedule.occurrences(t0 + 50 * ONE_DAY).size.should == 2 end it 'should produce the correct number of days for @interval = 2' do schedule = Schedule.new(t0 = Time.now) schedule.add_recurrence_rule Rule.monthly(2) schedule.occurrences(t0 + 50 * ONE_DAY).size.should == 1 end it 'should produce the correct number of days for @interval = 1 with only the 1st and 15th' do schedule = Schedule.new(t0 = Time.utc(2010, 1, 1)) schedule.add_recurrence_rule Rule.monthly.day_of_month(1, 15) #check assumption (1) (15) (1) (15) schedule.occurrences(t0 + 50 * ONE_DAY).map(&:day).should == [1, 15, 1, 15] end it 'should produce the correct number of days for @interval = 1 with only the 1st and last' do schedule = Schedule.new(t0 = Time.utc(2010, 1, 1)) schedule.add_recurrence_rule Rule.monthly.day_of_month(1, -1) #check assumption (1) (31) (1) schedule.occurrences(t0 + 60 * ONE_DAY).map(&:day).should == [1, 31, 1, 28, 1] end it 'should produce the correct number of days for @interval = 1 with only the first mondays' do schedule = Schedule.new(t0 = Time.utc(2010, 1, 1)) schedule.add_recurrence_rule Rule.monthly.day_of_week(:monday => [1]) #check assumption (month 1 monday) (month 2 monday) schedule.occurrences(t0 + 50 * ONE_DAY).size.should == 2 end it 'should produce the correct number of days for @interval = 1 with only the last mondays' do schedule = Schedule.new(t0 = Time.utc(2010, 1, 1)) schedule.add_recurrence_rule Rule.monthly.day_of_week(:monday => [-1]) #check assumption (month 1 monday) schedule.occurrences(t0 + 40 * ONE_DAY).size.should == 1 end it 'should produce the correct number of days for @interval = 1 with only the first and last mondays' do t0 = Time.utc(2010, 1, 1) t1 = Time.utc(2010, 12, 31) schedule = Schedule.new(t0) schedule.add_recurrence_rule Rule.monthly.day_of_week(:monday => [1, -2]) #check assumption (12 months - 2 dates each) schedule.occurrences(t1).size.should == 24 end [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday].each_with_index do |weekday, wday| context "for every first #{weekday} of a month" do let(:schedule) { schedule = Schedule.new(t0 = Time.local(2011, 8, 1)) schedule.add_recurrence_rule Rule.monthly.day_of_week(weekday => [1]) } it "should not skip a month when DST ends" do schedule.first(48).inject(nil) do |last_date, current_date| next current_date unless last_date month_interval(current_date, last_date).should == 1 end end it "should not change day when DST ends" do schedule.first(48).inject(nil) do |last_date, current_date| next current_date unless last_date current_date.wday.should == wday end end it "should not change hour when DST ends" do schedule.first(48).inject(nil) do |last_date, current_date| next current_date unless last_date current_date.hour.should == 0 end end end end it 'should produce dates on a monthly interval for the last day of the month' do schedule = Schedule.new(t0 = Time.utc(2010, 3, 31, 0, 0, 0)) schedule.add_recurrence_rule Rule.monthly schedule.first(10).should == [ Time.utc(2010, 3, 31, 0, 0, 0), Time.utc(2010, 4, 30, 0, 0, 0), Time.utc(2010, 5, 31, 0, 0, 0), Time.utc(2010, 6, 30, 0, 0, 0), Time.utc(2010, 7, 31, 0, 0, 0), Time.utc(2010, 8, 31, 0, 0, 0), Time.utc(2010, 9, 30, 0, 0, 0), Time.utc(2010, 10, 31, 0, 0, 0), Time.utc(2010, 11, 30, 0, 0, 0), Time.utc(2010, 12, 31, 0, 0, 0) ] end it 'should produce dates on a monthly interval for latter days in the month near February' do schedule = Schedule.new(t0 = Time.utc(2010, 1, 29, 0, 0, 0)) schedule.add_recurrence_rule Rule.monthly schedule.first(3).should == [ Time.utc(2010, 1, 29, 0, 0, 0), Time.utc(2010, 2, 28, 0, 0, 0), Time.utc(2010, 3, 29, 0, 0, 0) ] end it 'should restrict to available days of month when specified' do schedule = Schedule.new(t0 = Time.utc(2013,1,31)) schedule.add_recurrence_rule Rule.monthly.day_of_month(31) schedule.first(3).should == [ Time.utc(2013, 1, 31), Time.utc(2013, 3, 31), Time.utc(2013, 5, 31) ] end def month_interval(current_date, last_date) current_month = current_date.year * 12 + current_date.month last_month = last_date.year * 12 + last_date.month current_month - last_month end end end ice_cube-0.12.1/spec/examples/occurrence_spec.rb000066400000000000000000000105551235562124600216040ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' include IceCube describe Occurrence do it "reports as a Time" do occurrence = Occurrence.new(t0 = Time.now, t0 + 3600) occurrence.class.name.should == 'Time' occurrence.is_a?(Time).should be_true occurrence.kind_of?(Time).should be_true end describe :to_s do it "looks like a Time for a zero duration" do start_time = Time.now occurrence = Occurrence.new(start_time) occurrence.to_s.should == start_time.to_s end it "looks like a range for a non-zero duration" do start_time = Time.now end_time = start_time + ONE_HOUR occurrence = Occurrence.new(start_time, end_time) occurrence.to_s.should == "#{start_time} - #{end_time}" end it "accepts a format option to comply with ActiveSupport" do occurrence = Occurrence.new(Time.now) expect { occurrence.to_s(:short) }.not_to raise_error end end describe :end_time do it 'defaults to start_time' do start_time = Time.now occurrence = Occurrence.new(start_time) occurrence.end_time.should == start_time end it 'returns specified end_time' do start_time = Time.now end_time = start_time + 3600 occurrence = Occurrence.new(start_time, end_time) occurrence.end_time.should == end_time end end describe :arithmetic do let(:start_time) { Time.now } let(:occurrence) { Occurrence.new(start_time) } it 'returns a time when adding' do new_time = occurrence + 60 new_time.should == start_time + 60 end it 'can get difference from a time' do difference = occurrence - (start_time - 60) difference.should == 60 end end describe :intersects? do let(:start_time) { Time.now } let(:end_time) { start_time + 3600 } it 'is true for a time during the occurrence' do occurrence = Occurrence.new(start_time, end_time) inclusion = occurrence.intersects? start_time + 1800 inclusion.should be_true end it 'is false for a time outside the occurrence' do occurrence = Occurrence.new(start_time, end_time) inclusion = occurrence.intersects? start_time + 3601 inclusion.should be_false end it 'is true for an intersecting occurrence' do occurrence1 = Occurrence.new(start_time, end_time) occurrence2 = Occurrence.new(start_time + 1, end_time + 1) inclusion = occurrence1.intersects? occurrence2 inclusion.should be_true end it 'is false for a non-intersecting occurrence' do occurrence1 = Occurrence.new(start_time, end_time) occurrence2 = Occurrence.new(end_time) inclusion = occurrence1.intersects? occurrence2 inclusion.should be_false end end describe :overnight? do it 'is false for a zero-length occurrence' do occurrence = Occurrence.new(Time.local(2013, 12, 24)) occurrence.overnight?.should be_false end it 'is false for a zero-length occurrence on the last day of a month' do occurrence = Occurrence.new(Time.local(2013, 3, 31)) occurrence.overnight?.should be_false end it 'is false for a duration within a single day' do t0 = Time.local(2013, 2, 24, 8, 0, 0) occurrence = Occurrence.new(t0, t0 + 3600) occurrence.overnight?.should be_false end it 'is false for a duration that starts at midnight' do t0 = Time.local(2013, 2, 24, 0, 0, 0) occurrence = Occurrence.new(t0, t0 + 3600) occurrence.overnight?.should be_false end it 'is false for a duration that starts at midnight on the last day of a month' do t0 = Time.local(2013, 3, 31, 0, 0, 0) occurrence = Occurrence.new(t0, t0 + 3600) occurrence.overnight?.should be_false end it 'is false for a duration that ends at midnight' do t0 = Time.local(2013, 2, 24, 23, 0, 0) occurrence = Occurrence.new(t0, t0 + 3600) occurrence.overnight?.should be_false end it 'is true for a duration that crosses midnight' do t0 = Time.local(2013, 2, 24, 23, 0, 0) occurrence = Occurrence.new(t0, t0 + 3601) occurrence.overnight?.should be_true end it 'is true for a duration that crosses midnight on the last day of a month' do t0 = Time.local(2013, 3, 31, 23, 0, 0) occurrence = Occurrence.new(t0, t0 + 3601) occurrence.overnight?.should be_true end end end ice_cube-0.12.1/spec/examples/recur_spec.rb000066400000000000000000000151761235562124600206000ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' include IceCube describe :remaining_occurrences do it 'should get the proper remaining occurrences from now' do start_time = Time.now end_time = Time.local(start_time.year, start_time.month, start_time.day, 23, 59, 59) schedule = Schedule.new(start_time) schedule.add_recurrence_rule(Rule.hourly.until(end_time)) schedule.remaining_occurrences(start_time).size.should == 24 - schedule.start_time.hour end it 'should get the proper ramining occurrences past the end of the year' do start_time = Time.now schedule = Schedule.new(start_time) schedule.add_recurrence_rule(Rule.hourly.until(start_time + ONE_DAY)) schedule.remaining_occurrences(start_time + 366 * ONE_DAY).size.should == 0 end it 'should raise an error if there is nothing to stop it' do schedule = IceCube::Schedule.new schedule.add_recurrence_rule IceCube::Rule.daily expect { schedule.remaining_occurrences }.to raise_error ArgumentError end end describe :occurring_between? do let(:start_time) { Time.local(2012, 7, 7, 7) } let(:end_time) { start_time + 30 } let(:schedule) do IceCube::Schedule.new(start_time, :duration => 30).tap do |schedule| schedule.rrule IceCube::Rule.daily end end it 'should affirm an occurrence that spans the range exactly' do schedule.occurring_between?(start_time, end_time).should be_true end it 'should affirm a zero-length occurrence at the start of the range' do schedule.duration = 0 schedule.occurring_between?(start_time, start_time).should be_true end it 'should deny a zero-length occurrence at the end of the range' do schedule.duration = 0 schedule.occurring_between?(end_time, end_time).should be_false end it 'should affirm an occurrence entirely contained within the range' do schedule.occurring_between?(start_time + 1, end_time - 1).should be_true end it 'should affirm an occurrence spanning across the start of the range' do schedule.occurring_between?(start_time - 1, start_time + 1).should be_true end it 'should affirm an occurrence spanning across the end of the range' do schedule.occurring_between?(end_time - 1, end_time + 1).should be_true end it 'should affirm an occurrence spanning across the range entirely' do schedule.occurring_between?(start_time - 1, end_time + 1).should be_true end it 'should deny an occurrence before the range' do schedule.occurring_between?(end_time + 1, end_time + 2).should be_false end it 'should deny an occurrence after the range' do schedule.occurring_between?(start_time - 2, start_time - 1).should be_false end end describe :next_occurrence do it 'should get the next occurrence from now' do start_time = Time.local(2010, 10, 10, 10, 0, 0) schedule = Schedule.new(start_time, :end_time => start_time + 24 * ONE_HOUR) schedule.add_recurrence_rule(Rule.hourly) schedule.next_occurrence(schedule.start_time).should == schedule.start_time + 1 * ONE_HOUR end it 'should get the next occurrence past the end of the year' do start_time = Time.now schedule = Schedule.new(start_time, :end_time => start_time + 24 * ONE_HOUR) schedule.add_recurrence_rule(Rule.hourly) schedule.next_occurrence(schedule.end_time + 366 * ONE_DAY).should == schedule.end_time + 366 * ONE_DAY + 1 * ONE_HOUR end it 'should be able to use next_occurrence on a never-ending schedule' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.hourly schedule.next_occurrence(schedule.start_time).should == schedule.start_time + ONE_HOUR end it 'should get the next occurrence when a recurrence date is also added' do schedule = Schedule.new(Time.now) schedule.add_recurrence_time(schedule.start_time + 30 * ONE_MINUTE) schedule.add_recurrence_rule Rule.hourly schedule.next_occurrence(schedule.start_time).should == schedule.start_time + 30 * ONE_MINUTE end it 'should get the next occurrence and ignore recurrence dates that are before the desired time' do schedule = Schedule.new(Time.now) schedule.add_recurrence_time(schedule.start_time + 30 * ONE_MINUTE) schedule.add_recurrence_time(schedule.start_time - 30 * ONE_MINUTE) schedule.add_recurrence_rule Rule.hourly schedule.next_occurrence(schedule.start_time).should == schedule.start_time + 30 * ONE_MINUTE end end describe :next_occurrences do it 'should get the next 3 occurrence from now' do start_time = Time.local(2010, 1, 1, 10, 0, 0) schedule = Schedule.new(start_time, :end_time => start_time + ONE_HOUR * 24) schedule.add_recurrence_rule(Rule.hourly) schedule.next_occurrences(3, start_time).should == [ schedule.start_time + 1 * ONE_HOUR, schedule.start_time + 2 * ONE_HOUR, schedule.start_time + 3 * ONE_HOUR] end it 'should get the next 3 occurrence past the end of the year' do schedule = Schedule.new(Time.now, :end_time => Time.now + ONE_HOUR * 24) schedule.add_recurrence_rule(Rule.hourly.until(Time.now + 365 * ONE_DAY)) schedule.next_occurrences(3, schedule.end_time + 366 * ONE_DAY).should == [] end it 'should be able to use next_occurrences on a never-ending schedule' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.hourly schedule.next_occurrences(3, schedule.start_time).should == [ schedule.start_time + 1 * ONE_HOUR, schedule.start_time + 2 * ONE_HOUR, schedule.start_time + 3 * ONE_HOUR] end it 'should get the next 3 occurrences when a recurrence date is also added' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.hourly schedule.add_recurrence_time(schedule.start_time + 30 * ONE_MINUTE) schedule.next_occurrences(3, schedule.start_time).should == [ schedule.start_time + 30 * ONE_MINUTE, schedule.start_time + 1 * ONE_HOUR, schedule.start_time + 2 * ONE_HOUR] end it 'should get the next 3 occurrences and ignore recurrence dates that are before the desired time' do schedule = Schedule.new(Time.now) schedule.add_recurrence_time(schedule.start_time + 30 * ONE_MINUTE) schedule.add_recurrence_time(schedule.start_time - 30 * ONE_MINUTE) schedule.add_recurrence_rule Rule.hourly schedule.next_occurrences(3, schedule.start_time).should == [ schedule.start_time + 30 * ONE_MINUTE, schedule.start_time + ONE_HOUR, schedule.start_time + ONE_HOUR * 2] end it 'should generate the same comparable time objects (down to millisecond) on two runs' do schedule = Schedule.new Time.now schedule.rrule Rule.daily schedule.next_occurrences(5).should == schedule.next_occurrences(5) end end ice_cube-0.12.1/spec/examples/regression_spec.rb000066400000000000000000000235671235562124600216430ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe Schedule do WORLD_TIME_ZONES.each do |zone| context "in #{zone}", :system_time_zone => zone do it 'should produce the correct result for every day in may [#31]' do schedule = Schedule.new { |s| s.add_recurrence_rule Rule.daily.month_of_year(:may) } schedule.first(31).all? { |d| d.year == schedule.start_time.year } end it 'should consider recurrence times properly in find_occurreces [#43]' do schedule = Schedule.new(t0 = Time.local(2011, 10, 1, 18, 25)) schedule.add_recurrence_time Time.local(2011, 12, 3, 15, 0, 0) schedule.add_recurrence_time Time.local(2011, 12, 3, 10, 0, 0) schedule.add_recurrence_time Time.local(2011, 12, 4, 10, 0, 0) schedule.occurs_at?(Time.local(2011, 12, 3, 15, 0, 0)).should be_true end it 'should work well with occurrences_between [#33]' do schedule = Schedule.new(t0 = Time.local(2011, 10, 11, 12)) schedule.add_recurrence_rule Rule.weekly.day(1).hour_of_day(12).minute_of_hour(0) schedule.add_recurrence_rule Rule.weekly.day(2).hour_of_day(15).minute_of_hour(0) schedule.add_exception_time Time.local(2011, 10, 13, 21) schedule.add_exception_time Time.local(2011, 10, 18, 21) schedule.occurrences_between(Time.local(2012, 1, 1), Time.local(2012, 12, 1)).should be_an Array end it 'should work with all validation locks [#45]' do schedule = Schedule.new schedule.rrule Rule.monthly. month_of_year(10).day_of_month(13).day(5). hour_of_day(14).minute_of_hour(0).second_of_minute(0) schedule.occurrences(Date.today >> 12).should be_an Array end it 'should not regress [#40]' do schedule = Schedule.new(t0 = Time.local(2011, 11, 16, 11, 31, 58), :duration => 3600) schedule.add_recurrence_rule Rule.minutely(60).day(4).hour_of_day(14, 15, 16).minute_of_hour(0) schedule.occurring_at?(Time.local(2011, 11, 17, 15, 30)).should be_false end it 'should not choke on parsing [#26]' do schedule = Schedule.new(t0 = Time.local(2011, 8, 9, 14, 52, 14)) schedule.rrule Rule.weekly(1).day(1, 2, 3, 4, 5) expect { Schedule.from_yaml(schedule.to_yaml) }.to_not raise_error end it 'should parse an old schedule properly' do file = File.read(File.dirname(__FILE__) + '/../data/issue40.yml') schedule = Schedule.from_yaml(file) schedule.start_time.year.should == 2011 schedule.start_time.month.should == 11 schedule.start_time.day.should == 16 schedule.start_time.utc_offset.should == -5 * 3600 schedule.duration.should == 3600 schedule.rrules.should == [ Rule.minutely(60).day(4).hour_of_day(14, 15, 16).minute_of_hour(0) ] end it 'should handle a simple weekly schedule [#52]' do t0 = Time.new(2011, 12, 1, 18, 0, 0) t1 = Time.new(2012, 1, 1, 18, 0, 0) schedule = Schedule.new(t0) schedule.add_recurrence_rule Rule.weekly(1).day(4).until(t1) schedule.all_occurrences.should == [ Time.new(2011, 12, 1, 18), Time.new(2011, 12, 8, 18), Time.new(2011, 12, 15, 18), Time.new(2011, 12, 22, 18), Time.new(2011, 12, 29, 18) ] end it 'should produce all occurrences between dates, not breaking on exceptions [#82]' do schedule = Schedule.new(t0 = Time.new(2012, 5, 1)) schedule.add_recurrence_rule Rule.daily.day(:sunday, :tuesday, :wednesday, :thursday, :friday, :saturday) times = schedule.occurrences_between(Time.new(2012, 5, 19), Time.new(2012, 5, 24)) times.should == [ Time.new(2012, 5, 19), Time.new(2012, 5, 20), # No 21st Time.new(2012, 5, 22), Time.new(2012, 5, 23), Time.new(2012, 5, 24) ] end it 'should be able to use count with occurrences_between falling over counts last occurrence [#54]' do schedule = Schedule.new(t0 = Time.now) schedule.add_recurrence_rule Rule.daily.count(5) schedule.occurrences_between(t0, t0 + ONE_WEEK).count.should == 5 schedule.occurrences_between(t0 + ONE_WEEK, t0 + 2 * ONE_WEEK).count.should == 0 end it 'should produce occurrences regardless of time being specified [#81]' do schedule = Schedule.new(t0 = Time.new(2012, 5, 1)) schedule.add_recurrence_rule Rule.daily.hour_of_day(8) times = schedule.occurrences_between(Time.new(2012, 05, 20), Time.new(2012, 05, 22)) times.should == [ Time.new(2012, 5, 20, 8, 0, 0), Time.new(2012, 5, 21, 8, 0, 0) ] end it 'should not include exception times due to rounding errors [#83]' do schedule = Schedule.new(t0 = Time.new(2012, 12, 21, 21, 12, 21.212121)) schedule.rrule Rule.daily schedule.extime((t0 + ONE_DAY).round) schedule.first(2)[0].should == t0 schedule.first(2)[1].should == t0 + 2 * ONE_DAY end it 'should return true if a recurring schedule occurs_between? a time range [#88]' do t0 = Time.new(2012, 7, 7, 8) schedule = Schedule.new(t0, :duration => 2 * ONE_HOUR) schedule.add_recurrence_rule Rule.weekly t0 = Time.new(2012, 7, 14, 9) t1 = Time.new(2012, 7, 14, 11) schedule.occurring_between?(t0, t1).should be_true end require 'active_support/time' it 'should not hang next_time on DST boundary [#98]' do # set local to Sweden schedule = Schedule.from_yaml <<-EOS :start_date: 2012-09-03 0:00:00.000000000 +00:00 :end_time: 2022-09-15 0:00:00.000000000 +00:00 :rrules: - :validations: {} :rule_type: IceCube::DailyRule :interval: 1 :exrules: [] :rtimes: [] :extimes: [] EOS times = schedule.occurrences(Date.new(2013, 07, 13).to_time) end it 'should still include date over DST boundary [#98]' do # set local to Sweden schedule = Schedule.from_yaml <<-EOS :start_date: 2012-09-03 15:00:00.000000000 +00:00 :end_time: 2022-09-15 15:00:00.000000000 +00:00 :rrules: - :validations: {} :rule_type: IceCube::DailyRule :interval: 1 :exrules: [] :rtimes: [] :extimes: [] EOS times = schedule.occurrences(Date.new(2013, 07, 13).to_time) times.detect { |o| Date.new(o.year, o.month, o.day) == Date.new(2013, 3, 31) }.should be_true end it "failing spec for hanging on DST boundary [#98]" do Time.zone = "Europe/London" t0 = Time.zone.parse("Sun, 31 Mar 2013 00:00:00 GMT +00:00") schedule = Schedule.new(t0) schedule.add_recurrence_rule Rule.monthly schedule.next_occurrence(t0).should == Time.zone.local(2013, 4, 30) end it 'should exclude a date from a weekly schedule [#55]' do Time.zone = 'Eastern Time (US & Canada)' t0 = Time.zone.local(2011, 12, 27, 14) schedule = Schedule.new(t0) do |schedule| schedule.add_recurrence_rule Rule.weekly.day(:tuesday, :thursday) schedule.add_exception_time t0 end schedule.first.should == Time.zone.local(2011, 12, 29, 14) end it 'should not raise an exception after setting the rule until to nil' do rule = Rule.daily.until(Time.local(2012, 10, 1)) rule.until(nil) schedule = Schedule.new Time.local(2011, 10, 11, 12) schedule.add_recurrence_rule rule expect { schedule.occurrences_between(Time.local(2012, 1, 1), Time.local(2012, 12, 1)) }.to_not raise_error end it 'should not infinite loop [#109]' do schedule = Schedule.new(t0 = Time.new(2012, 4, 27, 0, 0, 0)) schedule.rrule Rule.weekly.day(:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday).hour_of_day(0).minute_of_hour(0).second_of_minute(0) schedule.duration = 3600 t1 = Time.new(2012, 10, 20, 0, 0, 0) t2 = Time.new(2012, 10, 20, 23, 59, 59) schedule.occurrences_between(t1, t2).first.should == t1 end it 'should return next_occurrence in utc if start_time is utc [#115]' do schedule = Schedule.new(t0 = Time.utc(2012, 10, 10, 20, 15, 0)) schedule.rrule Rule.daily schedule.next_occurrence.should be_utc end it 'should return next_occurrence in local if start_time is local [#115]' do schedule = Schedule.new Time.new(2012, 10, 10, 20, 15, 0) schedule.rrule Rule.daily schedule.next_occurrence.should_not be_utc end it 'should return next_occurrence in local by default [#115]' do schedule = Schedule.new schedule.rrule Rule.daily schedule.next_occurrence.should_not be_utc end it 'should include occurrences on until _date_ [#118]' do schedule = Schedule.new Time.new(2012, 4, 27) schedule.rrule Rule.daily.hour_of_day(12).until(Date.new(2012, 4, 28)) schedule.all_occurrences.should == [Time.new(2012, 4, 27, 12), Time.new(2012, 4, 28, 12)] end it 'should strip usecs from arguments when finding occurrences' do schedule = Schedule.new(Time.utc(2012, 4, 1, 10, 00)) schedule.rrule Rule.weekly time = schedule.occurrences_between(Time.utc(2012,5,1,10,00,00,4), Time.utc(2012, 5, 15)).first time.usec.should == 0 end end end end end ice_cube-0.12.1/spec/examples/rfc_spec.rb000066400000000000000000000425751235562124600202350ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule, 'occurs_on?' do it 'should ~ daily for 10 occurrences' do schedule = IceCube::Schedule.new(Time.utc(2010, 9, 2)) schedule.add_recurrence_rule IceCube::Rule.daily.count(10) test_expectations(schedule, {2010 => {9 => [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]}}) end it 'should ~ daily until a certain date' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 2)) schedule.add_recurrence_rule IceCube::Rule.daily.until(Time.utc(1997, 12, 24)) dates = schedule.all_occurrences expectation = (Date.civil(1997, 9, 2)..Date.civil(1997, 12, 24)).to_a expectation = expectation.map { |d| Time.utc(d.year, d.month, d.day) } dates.should == expectation end it 'should ~ every other day' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 2)) schedule.add_recurrence_rule IceCube::Rule.daily(2).until(Time.utc(1997, 12, 24)) dates = schedule.occurrences(Time.utc(1997, 12, 31)) offset = 0 (Date.new(1997, 9, 2)..Date.new(1997, 12, 24)).each do |date| dates.should include(Time.utc(date.year, date.month, date.day)) if offset % 2 == 0 dates.should_not include(Time.utc(date.year, date.month, date.day)) if offset % 2 != 0 offset += 1 end end it 'should ~ interval 10, count 5' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 2)) schedule.add_recurrence_rule IceCube::Rule.daily(10).count(5) dates = schedule.occurrences(Time.utc(1998, 1, 1)) dates.should == [Time.utc(1997, 9, 2), Time.utc(1997, 9, 12), Time.utc(1997, 9, 22), Time.utc(1997, 10, 2), Time.utc(1997, 10, 12)] end it 'should ~ everyday in january, for 3 years (a)' do schedule = IceCube::Schedule.new(Time.utc(1998, 1, 1)) schedule.add_recurrence_rule IceCube::Rule.yearly.until(Time.utc(2000, 1, 31)).month_of_year(:january).day(:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday) dates = schedule.occurrences(Time.utc(2000, 1, 31)) dates.each do |date| date.month.should == 1 [1998, 1999, 2000].should include(date.year) end end it 'should ~ everyday in january, for 3 years (b)' do schedule = IceCube::Schedule.new(Time.utc(1998, 1, 1)) schedule.add_recurrence_rule IceCube::Rule.daily.month_of_year(:january).until(Time.utc(2000, 1, 31)) dates = schedule.occurrences(Time.utc(2000, 1, 31)) dates.each do |date| date.month.should == 1 [1998, 1999, 2000].should include(date.year) end end it 'should ~ weekly for 10 occurrences' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 2)) schedule.add_recurrence_rule IceCube::Rule.weekly.count(10) dates = schedule.occurrences(Time.utc(2000, 1, 1)) dates.should == [Time.utc(1997, 9, 2), Time.utc(1997, 9, 9), Time.utc(1997, 9, 16), Time.utc(1997, 9, 23), Time.utc(1997, 9, 30), Time.utc(1997, 10, 7), Time.utc(1997, 10, 14), Time.utc(1997, 10, 21), Time.utc(1997, 10, 28), Time.utc(1997, 11, 4)] end it 'should ~ weekly until december 24, 1997' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 2)) schedule.add_recurrence_rule IceCube::Rule.weekly.until(Time.utc(1997, 12, 24)) dates = schedule.occurrences(Time.utc(1997, 12, 24)) #test expectations test_expectations(schedule, {1997 => {9 => [2, 9, 16, 23, 30], 10 => [7, 14, 21, 28], 11 => [4, 11, 18, 25], 12 => [2, 9, 16, 23]}}) end it 'should ~ every other week' do start_date = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly(2) dates = schedule.occurrences(Time.utc(1997, 12, 31)) #check assumption previous_date = dates.shift dates.each do |date| date.yday.should == previous_date.yday + 14 previous_date = date end end # it 'should ~ weekly on tuesday and thursday for 5 weeks (a)' do start_date = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly.until(Time.utc(1997, 10, 6)).day(:tuesday, :thursday) dates = schedule.occurrences(Time.utc(1997, 12, 1)) expectation = [] expectation << [2, 4, 9, 11, 16, 18, 23, 25, 30].map { |d| Time.utc(1997, 9, d) } expectation << [2].map { |d| Time.utc(1997, 10, d) } dates.should == expectation.flatten end it 'should ~ weekly on tuesday and thursday for 5 weeks (b)' do start_date = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:tuesday, :thursday).count(10) dates = schedule.occurrences(Time.utc(1997, 12, 1)) expectation = [] expectation << [2, 4, 9, 11, 16, 18, 23, 25, 30].map { |d| Time.utc(1997, 9, d) } expectation << [2].map { |d| Time.utc(1997, 10, d) } dates.should == expectation.flatten end # it 'should ~ every other week on monday, wednesday and friday until december 24, 1997 but starting on tuesday september 2, 1997' do start_date = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly(2).until(Time.utc(1997, 12, 24)).day(:monday, :wednesday, :friday) dates = schedule.occurrences(Time.utc(1997, 12, 24)) expectation = [] expectation << [3, 5, 15, 17, 19, 29].map { |d| Time.utc(1997, 9, d) } expectation << [1, 3, 13, 15, 17, 27, 29, 31].map { |d| Time.utc(1997, 10, d) } expectation << [10, 12, 14, 24, 26, 28].map { |d| Time.utc(1997, 11, d) } expectation << [8, 10, 12, 22, 24].map { |d| Time.utc(1997, 12, d) } dates.should == expectation.flatten end it 'should ~ every other week on tuesday and thursday for 8 occurrences' do start_date = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.weekly(2).day(:tuesday, :thursday).count(8) dates = schedule.occurrences(Time.utc(1997, 11, 1)) expectation = [] expectation << [2, 4, 16, 18, 30].map { |d| Time.utc(1997, 9, d) } expectation << [2, 14, 16].map { |d| Time.utc(1997, 10, d) } dates.should == expectation.flatten end it 'should ~ monthly on the 1st friday for ten occurrences' do start_date = Time.utc(1997, 9, 5) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_week(:friday => [1]).count(10) dates = schedule.occurrences(Time.utc(1998, 7, 1)) expectation = [Time.utc(1997, 9, 5), Time.utc(1997, 10, 3), Time.utc(1997, 11, 7), Time.utc(1997, 12, 5), Time.utc(1998, 1, 2), Time.utc(1998, 2, 6), Time.utc(1998, 3, 6), Time.utc(1998, 4, 3), Time.utc(1998, 5, 1), Time.utc(1998, 6, 5)] dates.should == expectation end it 'should ~ monthly on the first friday until december 24, 1997' do start_date = Time.utc(1997, 9, 5) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.until(Time.utc(1997, 12, 24)).day_of_week(:friday => [1]) dates = schedule.occurrences(Time.utc(1998, 12, 24)) expectation = [Time.utc(1997, 9, 5), Time.utc(1997, 10, 3), Time.utc(1997, 11, 7), Time.utc(1997, 12, 5)] dates.should == expectation end it 'should ~ every other month on the 1st and last sunday of the month for 10 occurrences' do start_date = Time.utc(1997, 9, 7) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly(2).day_of_week(:sunday => [1, -1]).count(10) dates = schedule.occurrences(Time.utc(1998, 12, 1)) expectation = [Time.utc(1997, 9, 7), Time.utc(1997, 9, 28), Time.utc(1997, 11, 2), Time.utc(1997, 11, 30), Time.utc(1998, 1, 4), Time.utc(1998, 1, 25), Time.utc(1998, 3, 1), Time.utc(1998, 3, 29), Time.utc(1998, 5, 3), Time.utc(1998, 5, 31)] dates.should == expectation end it 'should ~ monthly on the second to last monday of the month for 6 months' do start_date = Time.utc(1997, 9, 22) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_week(:monday => [-2]).count(6) dates = schedule.occurrences(Time.utc(1998, 3, 1)) expectation = [Time.utc(1997, 9, 22), Time.utc(1997, 10, 20), Time.utc(1997, 11, 17), Time.utc(1997, 12, 22), Time.utc(1998, 1, 19), Time.utc(1998, 2, 16)] dates.should == expectation end it 'should ~ monthly on the third to last day of the month, 6 times' do start_date = Time.utc(1997, 9, 28) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_month(-3).count(6) dates = schedule.occurrences(Time.utc(1998, 2, 26)) expectation = [Time.utc(1997, 9, 28), Time.utc(1997, 10, 29), Time.utc(1997, 11, 28), Time.utc(1997, 12, 29), Time.utc(1998, 1, 29), Time.utc(1998, 2, 26)] dates.should == expectation end it 'should ~ monthly on the 2nd and 15th of the month for 10 occurrences' do start_date = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_month(2, 15).count(10) dates = schedule.occurrences(Time.utc(1998, 1, 16)) expectation = [Time.utc(1997, 9, 2), Time.utc(1997, 9, 15), Time.utc(1997, 10, 2), Time.utc(1997, 10, 15), Time.utc(1997, 11, 2), Time.utc(1997, 11, 15), Time.utc(1997, 12, 2), Time.utc(1997, 12, 15), Time.utc(1998, 1, 2), Time.utc(1998, 1, 15)] dates.should == expectation end it 'should ~ monthly on the 1st and last days of the month for 10 occurrences' do start_date = Time.utc(1997, 9, 30) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_month(1, -1).count(10) dates = schedule.occurrences(Time.utc(1998, 2, 2)) expectation = [Time.utc(1997, 9, 30), Time.utc(1997, 10, 1), Time.utc(1997, 10, 31), Time.utc(1997, 11, 1), Time.utc(1997, 11, 30), Time.utc(1997, 12, 1), Time.utc(1997, 12, 31), Time.utc(1998, 1, 1), Time.utc(1998, 1, 31), Time.utc(1998, 2, 1)] dates.should == expectation end it 'should ~ every 18 months on the 10th through the 15th of the month for 10 occurrences' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 10)) schedule.add_recurrence_rule IceCube::Rule.monthly(18).day_of_month(10, 11, 12, 13, 14, 15).count(10) dates = schedule.occurrences(Time.utc(1999, 12, 1)) expectation = [] expectation << [10, 11, 12, 13, 14, 15].map { |d| Time.utc(1997, 9, d) } expectation << [10, 11, 12, 13].map { |d| Time.utc(1999, 3, d) } dates.should == expectation.flatten end it 'should ~ every tuesday, every other month' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 2)) schedule.add_recurrence_rule IceCube::Rule.monthly(2).day(:tuesday) dates = schedule.occurrences(Time.utc(1998, 4, 1)) expectation = [] expectation << [2, 9, 16, 23, 30].map { |d| Time.utc(1997, 9, d) } expectation << [4, 11, 18, 25].map { |d| Time.utc(1997, 11, d) } expectation << [6, 13, 20, 27].map { |d| Time.utc(1998, 1, d) } expectation << [3, 10, 17, 24, 31].map { |d| Time.utc(1998, 3, d) } dates.should == expectation.flatten end it 'should ~ yearly in june and july for 10 occurrences' do schedule = IceCube::Schedule.new(Time.utc(1997, 6, 10)) schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:june, :july).count(10) dates = schedule.occurrences(Time.utc(2001, 8, 1)) expectation = [] (1997..2001).each do |year| expectation << Time.utc(year, 6, 10) expectation << Time.utc(year, 7, 10) end dates.should == expectation.flatten end it 'should ~ every other year on january, feburary, and march for 10 occurrences' do schedule = IceCube::Schedule.new(Time.utc(1997, 3, 10)) schedule.add_recurrence_rule IceCube::Rule.yearly(2).month_of_year(:january, :february, :march).count(10) dates = schedule.occurrences(Time.utc(2003, 4, 1)) expectation = [Time.utc(1997, 3, 10), Time.utc(1999, 1, 10), Time.utc(1999, 2, 10), Time.utc(1999, 3, 10), Time.utc(2001, 1, 10), Time.utc(2001, 2, 10), Time.utc(2001, 3, 10), Time.utc(2003, 1, 10), Time.utc(2003, 2, 10), Time.utc(2003, 3, 10)] dates.should == expectation end it 'should ~ every third year on the 1st, 100th and 200th day for 10 occurrences' do schedule = IceCube::Schedule.new(Time.utc(1997, 1, 1)) schedule.add_recurrence_rule IceCube::Rule.yearly(3).day_of_year(1, 100, 200).count(10) dates = schedule.occurrences(Time.utc(2006, 1, 2)) expectation = [Time.utc(1997, 1, 1), Time.utc(1997, 4, 10), Time.utc(1997, 7, 19), Time.utc(2000, 1, 1), Time.utc(2000, 4, 9), Time.utc(2000, 7, 18), Time.utc(2003, 1, 1), Time.utc(2003, 4, 10), Time.utc(2003, 7, 19), Time.utc(2006, 1, 1)] dates.should == expectation end it 'should ~ every thursday in march, forever' do schedule = IceCube::Schedule.new(Time.utc(1997, 3, 13)) schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:march).day(:thursday) dates = schedule.occurrences(Time.utc(1999, 3, 25)) expectation = [] expectation << [13, 20, 27].map { |d| Time.utc(1997, 3, d) } expectation << [5, 12, 19, 26].map { |d| Time.utc(1998, 3, d) } expectation << [4, 11, 18, 25].map { |d| Time.utc(1999, 3, d) } dates.should == expectation.flatten end it 'should ~ every thursday, but only during june, july, and august' do schedule = IceCube::Schedule.new(Time.utc(1997, 6, 5)) schedule.add_recurrence_rule IceCube::Rule.yearly.day(:thursday).month_of_year(:june, :july, :august) dates = schedule.occurrences(Time.utc(1998, 9, 1)) expectation = [] expectation << [5, 12, 19, 26].map { |d| Time.utc(1997, 6, d) } expectation << [3, 10, 17, 24, 31].map { |d| Time.utc(1997, 7, d) } expectation << [7, 14, 21, 28].map { |d| Time.utc(1997, 8, d) } expectation << [4, 11, 18, 25].map { |d| Time.utc(1998, 6, d) } expectation << [2, 9, 16, 23, 30].map { |d| Time.utc(1998, 7, d) } expectation << [6, 13, 20, 27].map { |d| Time.utc(1998, 8, d) } dates.should == expectation.flatten end it 'should ~ every friday the 13th' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 2)) schedule.add_recurrence_rule IceCube::Rule.monthly.day(:friday).day_of_month(13) dates = schedule.occurrences(Time.utc(2000, 10, 13)) expectation = [Time.utc(1998, 2, 13), Time.utc(1998, 3, 13), Time.utc(1998, 11, 13), Time.utc(1999, 8, 13), Time.utc(2000, 10, 13)] dates.should == expectation end it 'should ~ the first saturday that follows the first sunday of the month' do schedule = IceCube::Schedule.new(Time.utc(1997, 9, 13)) schedule.add_recurrence_rule IceCube::Rule.monthly.day(:saturday).day_of_month(7, 8, 9, 10, 11, 12, 13) dates = schedule.occurrences(Time.utc(1997, 12, 13)) expectation = [Time.utc(1997, 9, 13), Time.utc(1997, 10, 11), Time.utc(1997, 11, 8), Time.utc(1997, 12, 13)] dates.should == expectation end it 'should ~ every 4 years, the first tuesday after a monday in november (u.s. presidential election day)' do schedule = IceCube::Schedule.new(Time.utc(1996, 11, 5)) schedule.add_recurrence_rule IceCube::Rule.yearly(4).month_of_year(:november).day(:tuesday).day_of_month(2, 3, 4, 5, 6, 7, 8) dates = schedule.occurrences(Time.utc(2004, 11, 2)) expectation = [Time.utc(1996, 11, 5), Time.utc(2000, 11, 7), Time.utc(2004, 11, 2)] dates.should == expectation end it 'should ~ every 3 hours from 9am to 5pm on a specific day' do start_date = Time.utc(1997, 9, 2, 9, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.hourly(3).until(Time.utc(1997, 9, 2, 17, 0, 0)) dates = schedule.all_occurrences dates.should == [Time.utc(1997, 9, 2, 9, 0, 0), Time.utc(1997, 9, 2, 12, 0, 0), Time.utc(1997, 9, 2, 15, 0, 0)] end it 'should ~ every 15 minutes for 6 occurrences' do start_date = Time.utc(1997, 9, 2, 9, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.minutely(15).count(6) dates = schedule.all_occurrences dates.should == [Time.utc(1997, 9, 2, 9, 0, 0), Time.utc(1997, 9, 2, 9, 15, 0), Time.utc(1997, 9, 2, 9, 30, 0), Time.utc(1997, 9, 2, 9, 45, 0), Time.utc(1997, 9, 2, 10, 0, 0), Time.utc(1997, 9, 2, 10, 15, 0)] end it 'should ~ every hour and a half for 4 occurrences' do start_date = Time.utc(1997, 9, 2, 9, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.minutely(90).count(4) dates = schedule.all_occurrences dates.should == [Time.utc(1997, 9, 2, 9, 0, 0), Time.utc(1997, 9, 2, 10, 30, 0), Time.utc(1997, 9, 2, 12, 0, 0), Time.utc(1997, 9, 2, 13, 30, 0)] end it 'should ~ every 20 minutes from 9am to 4:40pm every day (a)' do start_date = Time.utc(1997, 9, 2, 8, 0, 0) end_date = Time.utc(1997, 9, 2, 10, 20, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.daily.hour_of_day(9, 10, 11, 12, 13, 14, 15, 16).minute_of_hour(0, 20, 40).until(end_date) dates = schedule.all_occurrences expecation = [Time.utc(1997, 9, 2, 9), Time.utc(1997, 9, 2, 9, 20), Time.utc(1997, 9, 2, 9, 40), Time.utc(1997, 9, 2, 10, 0), Time.utc(1997, 9, 2, 10, 20)] dates.should == expecation end end def test_expectations(schedule, dates_array) expectation = [] dates_array.each do |y, months| months.each do |m, days| days.each do |d| expectation << Time.utc(y, m, d) end end end # test equality expectation.sort! schedule.occurrences(expectation.last).should == expectation expectation.each do |date| schedule.should be_occurs_at(date) end end ice_cube-0.12.1/spec/examples/schedule_spec.rb000066400000000000000000000661641235562124600212570ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule do include IceCube it 'yields itself for configuration' do t1 = Time.utc(2013, 2, 12, 12, 34 ,56) schedule = IceCube::Schedule.new do |s| s.start_time = t1 end schedule.start_time.should == t1 end it 'initializes with a start_time' do t1 = Time.local(2013, 2, 14, 0, 32, 0) schedule = IceCube::Schedule.new(t1) schedule.start_time.should be_a Time schedule.start_time.should == t1 end it 'converts initialized DateTime to Time' do dt = DateTime.new(2013, 2, 14, 0, 32, 0) schedule = IceCube::Schedule.new(dt) schedule.start_time.should be_a Time schedule.start_time.should == Time.local(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec) end describe :next_occurrence do it 'should not raise an exception when calling next occurrence with no remaining occurrences' do schedule = IceCube::Schedule.new Time.now lambda { schedule.next_occurrence }.should_not raise_error end end describe :duration do it 'should be based on end_time' do start = Time.now schedule = IceCube::Schedule.new(start) schedule.duration.should == 0 schedule.end_time = start + 3600 schedule.duration.should == 3600 end it 'should give precedence to :end_time option' do start = Time.now conflicting_options = {:end_time => start + 600, :duration => 1200} schedule = IceCube::Schedule.new(start, conflicting_options) schedule.duration.should == 600 end end describe :recurrence_times do it 'should start empty' do IceCube::Schedule.new.recurrence_times.should be_empty end it 'should include added times' do schedule = IceCube::Schedule.new(t0 = Time.now) schedule.add_recurrence_time(t1 = t0 + 3600) schedule.recurrence_times.should == [t1] end it 'can include start time' do schedule = IceCube::Schedule.new(t0 = Time.now) schedule.add_recurrence_time(t0) schedule.recurrence_times.should == [t0] end end describe :conflicts_with? do it 'should raise an error if both are not terminating' do schedules = 2.times.map do schedule = IceCube::Schedule.new(Time.now) schedule.rrule IceCube::Rule.daily schedule end lambda do schedules.first.conflicts_with?(schedules.last) end.should raise_error ArgumentError end it 'should not raise error if both are non-terminating closing time present' do schedule1 = IceCube::Schedule.new Time.now schedule1.rrule IceCube::Rule.weekly schedule2 = IceCube::Schedule.new Time.now schedule2.rrule IceCube::Rule.weekly lambda do schedule1.conflicts_with?(schedule2, Time.now + IceCube::ONE_DAY) end.should_not raise_error end it 'should not raise an error if one is non-terminating' do schedule1 = IceCube::Schedule.new Time.now schedule1.rrule IceCube::Rule.weekly schedule2 = IceCube::Schedule.new Time.now schedule2.rrule IceCube::Rule.weekly.until(Time.now) lambda do schedule1.conflicts_with?(schedule2) end.should_not raise_error end it 'should not raise an error if the other is non-terminating' do schedule1 = IceCube::Schedule.new Time.now schedule1.rrule IceCube::Rule.weekly.until(Time.now) schedule2 = IceCube::Schedule.new Time.now schedule2.rrule IceCube::Rule.weekly lambda do schedule1.conflicts_with?(schedule2) end.should_not raise_error end it 'should return true if conflict is present' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time) schedule1.rrule IceCube::Rule.daily schedule2 = IceCube::Schedule.new(start_time) schedule2.rrule IceCube::Rule.daily conflict = schedule1.conflicts_with?(schedule2, start_time + IceCube::ONE_DAY) conflict.should be_true end it 'should return false if conflict is not present' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time) schedule1.rrule IceCube::Rule.weekly.day(:tuesday) schedule2 = IceCube::Schedule.new(start_time) schedule2.rrule IceCube::Rule.weekly.day(:monday) conflict = schedule1.conflicts_with?(schedule2, start_time + IceCube::ONE_DAY) conflict.should be_false end it 'should return true if conflict is present based on duration' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_DAY + 1) schedule1.rrule IceCube::Rule.weekly.day(:monday) schedule2 = IceCube::Schedule.new(start_time) schedule2.rrule IceCube::Rule.weekly.day(:tuesday) conflict = schedule1.conflicts_with?(schedule2, start_time + IceCube::ONE_WEEK) conflict.should be_true end it 'should return true if conflict is present based on duration - other way' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time) schedule1.rrule IceCube::Rule.weekly.day(:tuesday) schedule2 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_DAY + 1) schedule2.rrule IceCube::Rule.weekly.day(:monday) conflict = schedule1.conflicts_with?(schedule2, start_time + IceCube::ONE_WEEK) conflict.should be_true end it 'should return false if conflict is past closing_time' do start_time = Time.local(2011, 1, 1, 12) # Sunday schedule1 = IceCube::Schedule.new(start_time) schedule1.rrule IceCube::Rule.weekly.day(:friday) schedule2 = IceCube::Schedule.new(start_time) schedule2.rrule IceCube::Rule.weekly.day(:friday) schedule2.conflicts_with?(schedule1, start_time + IceCube::ONE_WEEK). should be_true schedule2.conflicts_with?(schedule1, start_time + IceCube::ONE_DAY). should be_false end it 'should return false if conflict is not present based on duration' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule1.rrule IceCube::Rule.weekly.day(:monday) schedule2 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule2.rrule IceCube::Rule.weekly.day(:tuesday) conflict = schedule1.conflicts_with?(schedule2, start_time + IceCube::ONE_WEEK) conflict.should be_false end it 'should return false if conflict is not present on same day based on duration' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule1.rrule IceCube::Rule.daily schedule2 = IceCube::Schedule.new(start_time + 3600, :duration => IceCube::ONE_HOUR) schedule2.rrule IceCube::Rule.daily conflict = schedule1.conflicts_with?(schedule2, start_time + IceCube::ONE_WEEK) conflict.should be_false end it 'should return true if conflict is present on same day based on duration' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule1.rrule IceCube::Rule.daily schedule2 = IceCube::Schedule.new(start_time + 600, :duration => IceCube::ONE_HOUR) schedule2.rrule IceCube::Rule.daily conflict = schedule1.conflicts_with?(schedule2, start_time + IceCube::ONE_WEEK) conflict.should be_true end it 'should return true if conflict is present and no recurrence' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule1.add_recurrence_time(start_time) schedule2 = IceCube::Schedule.new(start_time + 600, :duration => IceCube::ONE_HOUR) schedule2.add_recurrence_time(start_time + 600) conflict = schedule1.conflicts_with?(schedule2) conflict.should be_true conflict = schedule2.conflicts_with?(schedule1) conflict.should be_true end it 'should return false if conflict is not present and no recurrence' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule1.add_recurrence_time(start_time) schedule2 = IceCube::Schedule.new(start_time + IceCube::ONE_HOUR, :duration => IceCube::ONE_HOUR) schedule2.add_recurrence_time(start_time + IceCube::ONE_HOUR) conflict = schedule1.conflicts_with?(schedule2) conflict.should be_false conflict = schedule2.conflicts_with?(schedule1) conflict.should be_false end it 'should return false if conflict is not present and single recurrence' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule1.add_recurrence_time(start_time) schedule2 = IceCube::Schedule.new(start_time + IceCube::ONE_HOUR, :duration => IceCube::ONE_HOUR) schedule2.rrule IceCube::Rule.daily conflict = schedule1.conflicts_with?(schedule2) conflict.should be_false conflict = schedule2.conflicts_with?(schedule1) conflict.should be_false end it 'should return true if conflict is present and single recurrence' do start_time = Time.now schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule1.add_recurrence_time(start_time) schedule2 = IceCube::Schedule.new(start_time + 600, :duration => IceCube::ONE_HOUR) schedule2.rrule IceCube::Rule.daily conflict = schedule1.conflicts_with?(schedule2) conflict.should be_true conflict = schedule2.conflicts_with?(schedule1) conflict.should be_true end it 'should return false if conflict is not present and single recurrence and time originally specified as Time' do start_time = Time.local(2020, 9, 21, 11, 30, 0) schedule1 = IceCube::Schedule.new(start_time, :duration => IceCube::ONE_HOUR) schedule1.add_recurrence_time(start_time) schedule2 = IceCube::Schedule.new(start_time + IceCube::ONE_HOUR, :duration => IceCube::ONE_HOUR) schedule2.add_recurrence_time(start_time + IceCube::ONE_HOUR) conflict = schedule1.conflicts_with?(schedule2) conflict.should be_false conflict = schedule2.conflicts_with?(schedule1) conflict.should be_false end end describe :each do it 'should be able to yield occurrences for a schedule' do schedule = IceCube::Schedule.new schedule.add_recurrence_rule IceCube::Rule.daily i = 0 answers = [] schedule.each_occurrence do |time| answers << time i += 1 break if i > 9 end answers.should == schedule.first(10) end it 'should return self' do schedule = IceCube::Schedule.new schedule.each_occurrence { |s| }.should == schedule end it 'should stop itself when hitting the end of a schedule' do schedule = IceCube::Schedule.new(t0 = Time.now) t1 = t0 + 24 * IceCube::ONE_DAY schedule.add_recurrence_time t1 answers = [] schedule.each_occurrence { |t| answers << t } answers.should == [t0, t1] end end describe :all_occurrences_enumerator do it 'should be equivalent to all_occurrences in terms of arrays' do schedule = IceCube::Schedule.new(Time.now, :duration => IceCube::ONE_HOUR) schedule.add_recurrence_rule IceCube::Rule.daily.until(Time.now + 3 * IceCube::ONE_DAY) schedule.all_occurrences == schedule.all_occurrences_enumerator.to_a end end describe :remaining_occurrences_enumerator do it 'should be equivalent to remaining_occurrences in terms of arrays' do schedule = IceCube::Schedule.new(Time.now, :duration => IceCube::ONE_HOUR) schedule.add_recurrence_rule IceCube::Rule.daily.until(Time.now + 3 * IceCube::ONE_DAY) schedule.remaining_occurrences == schedule.remaining_occurrences_enumerator.to_a end end describe :all_occurrences do it 'has end times for each occurrence' do schedule = IceCube::Schedule.new(Time.now, :duration => IceCube::ONE_HOUR) schedule.add_recurrence_rule IceCube::Rule.daily.until(Time.now + 3 * IceCube::ONE_DAY) schedule.all_occurrences.all? { |o| o.end_time.should == o + IceCube::ONE_HOUR } end it 'should include its start time when empty' do schedule = IceCube::Schedule.new(t0 = Time.now) schedule.all_occurrences.should == [t0] end it 'should have one occurrence with one recurrence time at start_time' do schedule = IceCube::Schedule.new(t0 = Time.local(2012, 12, 12, 12, 12, 12)) schedule.add_recurrence_time t0 schedule.all_occurrences.should == [t0] end it 'should have two occurrences with a recurrence time after start_time' do schedule = IceCube::Schedule.new(t0 = Time.local(2012, 12, 12, 12, 12, 12)) schedule.add_recurrence_time t1 = Time.local(2013, 1, 13, 1, 13, 1) schedule.all_occurrences.should == [t0, t1] end it 'should return an error if there is nothing to stop it' do schedule = IceCube::Schedule.new schedule.rrule IceCube::Rule.daily lambda do schedule.all_occurrences end.should raise_error ArgumentError end it 'should consider count limits separately for multiple rules' do schedule = IceCube::Schedule.new schedule.rrule IceCube::Rule.minutely.count(3) schedule.rrule IceCube::Rule.daily.count(3) schedule.all_occurrences.size.should == 5 end end describe :next_occurrences do let(:nonsense) { IceCube::Rule.monthly.day_of_week(:monday => [1]).day_of_month(31) } it 'should be able to calculate next occurrences ignoring excluded times' do start_time = Time.now schedule = IceCube::Schedule.new start_time schedule.rrule IceCube::Rule.daily(1) schedule.extime start_time + IceCube::ONE_DAY occurrences = schedule.next_occurrences(2, start_time) # 3 occurrences in the next year occurrences.should == [ start_time + IceCube::ONE_DAY * 2, start_time + IceCube::ONE_DAY * 3 ] end it 'should be empty if nothing is found before closing time' do schedule = IceCube::Schedule.new(t0 = Time.utc(2013, 1, 1)) do |s| next_year = Date.new(t0.year + 1, t0.month, t0.day) s.add_recurrence_rule nonsense.until(next_year) end trap_infinite_loop_beyond(24) schedule.next_occurrences(1).should be_empty end end describe :next_occurrence do it 'should be able to calculate the next occurrence past an exception time' do start_time = Time.now schedule = IceCube::Schedule.new start_time schedule.rrule IceCube::Rule.daily(1) schedule.extime start_time + IceCube::ONE_DAY occurrence = schedule.next_occurrence(start_time) # 3 occurrences in the next year occurrence.should == start_time + IceCube::ONE_DAY * 2 end it 'should respect time zone info for a local future time [#115]' do start_time = Time.local(Time.now.year + 1, 7, 1, 0, 0, 0) compare_time_zone_info(start_time) end it 'should respect time zone info for a local past time [#115]' do start_time = Time.local(Time.now.year - 1, 7, 1, 0, 0, 0) compare_time_zone_info(start_time) end it 'should respect time zone info for a utc past time [#115]' do start_time = Time.utc(Time.now.year - 1, 7, 1, 0, 0, 0) compare_time_zone_info(start_time) end it 'should respect time zone info for a utc future time [#115]' do start_time = Time.utc(Time.now.year + 1, 7, 1, 0, 0, 0) compare_time_zone_info(start_time) end it 'should respect time zone info for a offset past time [#115]' do start_time = Time.utc(Time.now.year - 1, 7, 1, 0, 0, 0).localtime("-05:00") compare_time_zone_info(start_time) end it 'should respect time zone info for a offset future time [#115]' do start_time = Time.utc(Time.now.year + 1, 7, 1, 0, 0, 0).localtime("-05:00") compare_time_zone_info(start_time) end end describe :previous_occurrence do it 'returns the previous occurrence for a time in the schedule' do t0 = Time.utc(2013, 5, 18, 12, 34) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.daily previous = schedule.previous_occurrence(t0 + 2 * ONE_DAY) previous.should == t0 + ONE_DAY end it 'returns nil given the start time' do t0 = Time.utc(2013, 5, 18, 12, 34) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.daily previous = schedule.previous_occurrence(t0) previous.should be_nil end end describe :previous_occurrences do it 'returns an array of previous occurrences from a given time' do t0 = Time.utc(2013, 5, 18, 12, 34) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.daily previous = schedule.previous_occurrences(2, t0 + 3 * ONE_DAY) previous.should == [t0 + ONE_DAY, t0 + 2 * ONE_DAY] end it 'limits the returned occurrences to a given count' do t0 = Time.utc(2013, 5, 18, 12, 34) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.daily previous = schedule.previous_occurrences(999, t0 + 2 * ONE_DAY) previous.should == [t0, t0 + ONE_DAY] end it 'returns empty array given the start time' do t0 = Time.utc(2013, 5, 18, 12, 34) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.daily previous = schedule.previous_occurrences(2, t0) previous.should == [] end end describe :last do it 'returns the last occurrence for a terminating schedule' do t0 = Time.utc(2013, 5, 18, 12, 34) t1 = Time.utc(2013, 5, 31, 12, 34) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.daily.until(t1 + 1) schedule.last.should == t1 end it 'returns an array of occurrences given a number' do t0 = Time.utc(2013, 5, 18, 12, 34) t1 = Time.utc(2013, 5, 31, 12, 34) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.daily.until(t1 + 1) schedule.last(2).should == [t1 - ONE_DAY, t1] end it 'raises an error for a non-terminating schedule' do schedule = IceCube::Schedule.new schedule.add_recurrence_rule IceCube::Rule.daily expect { schedule.last }.to raise_error end end describe :start_date= do it 'should modify start date in rrule_occurrence_heads when changed' do schedule = IceCube::Schedule.new(Time.now - 1000) schedule.rrule IceCube::Rule.daily schedule.start_time = (start_date = Time.now) (Time.now - schedule.first.start_time).should be < 100 end end describe :recurrence_rules do it 'should not include rules for single occurrences' do schedule = IceCube::Schedule.new Time.now schedule.add_recurrence_time Time.now schedule.rrules.should be_empty end end describe :remove_recurrence_rule do it 'should be able to one rule based on the comparator' do schedule = IceCube::Schedule.new Time.now schedule.rrule IceCube::Rule.daily schedule.rrule IceCube::Rule.daily(2) schedule.remove_recurrence_rule schedule.rrules.first schedule.rrules.count.should == 1 end it 'should be able to remove multiple rules based on the comparator' do schedule = IceCube::Schedule.new Time.now schedule.rrule IceCube::Rule.daily schedule.rrule IceCube::Rule.daily schedule.remove_recurrence_rule schedule.rrules.first schedule.rrules.should be_empty end it 'should return the rule that was removed' do schedule = IceCube::Schedule.new Time.now rule = IceCube::Rule.daily schedule.rrule rule rule2 = schedule.remove_recurrence_rule rule [rule].should == rule2 end it 'should return [] if nothing was removed' do schedule = IceCube::Schedule.new Time.now rule = IceCube::Rule.daily schedule.remove_recurrence_rule(rule).should == [] end end describe :remove_exception_rule do it 'should be able to one rule based on the comparator' do schedule = IceCube::Schedule.new Time.now schedule.exrule IceCube::Rule.daily schedule.exrule IceCube::Rule.daily(2) schedule.remove_exception_rule schedule.exrules.first schedule.exrules.count.should == 1 end it 'should be able to remove multiple rules based on the comparator' do schedule = IceCube::Schedule.new Time.now schedule.exrule IceCube::Rule.daily schedule.exrule IceCube::Rule.daily schedule.remove_exception_rule schedule.exrules.first schedule.exrules.should be_empty end it 'should return the rule that was removed' do schedule = IceCube::Schedule.new Time.now rule = IceCube::Rule.daily schedule.exrule rule rule2 = schedule.remove_exception_rule rule [rule].should == rule2 end it 'should return [] if nothing was removed' do schedule = IceCube::Schedule.new Time.now rule = IceCube::Rule.daily schedule.remove_exception_rule(rule).should == [] end end describe :remove_recurrence_time do it 'should be able to remove a recurrence date from a schedule' do time = Time.now schedule = IceCube::Schedule.new(time) schedule.add_recurrence_time time schedule.remove_recurrence_time time schedule.recurrence_times.should be_empty end it 'should return the time that was removed' do schedule = IceCube::Schedule.new Time.now time = Time.now schedule.rtime time schedule.remove_rtime(time).should == time end it 'should return nil if the date was not in the schedule' do schedule = IceCube::Schedule.new Time.now schedule.remove_recurrence_time(Time.now).should be_nil end end describe :remove_exception_time do it 'should be able to remove a exception date from a schedule' do time = Time.now schedule = IceCube::Schedule.new(time) schedule.extime time schedule.remove_exception_time time schedule.exception_times.should be_empty end it 'should return the date that was removed' do schedule = IceCube::Schedule.new Time.now time = Time.now schedule.extime time schedule.remove_extime(time).should == time end it 'should return nil if the date was not in the schedule' do schedule = IceCube::Schedule.new Time.now schedule.remove_exception_time(Time.now).should be_nil end end describe :occurs_on? do subject(:schedule) { IceCube::Schedule.new(start_time) } shared_examples "occurring on a given day" do WORLD_TIME_ZONES.each do |zone| context "in #{zone}", :system_time_zone => zone do specify 'should determine if it occurs on a given Date' do schedule.occurs_on?(Date.new(2010, 7, 1)).should be_false schedule.occurs_on?(Date.new(2010, 7, 2)).should be_true schedule.occurs_on?(Date.new(2010, 7, 3)).should be_false end specify 'should determine if it occurs on the day of a given UTC Time' do schedule.occurs_on?(Time.utc(2010, 7, 1, 23, 59, 59)).should be_false schedule.occurs_on?(Time.utc(2010, 7, 2, 0, 0, 1)).should be_true schedule.occurs_on?(Time.utc(2010, 7, 2, 23, 59, 59)).should be_true schedule.occurs_on?(Time.utc(2010, 7, 3, 0, 0, 1)).should be_false end specify 'should determine if it occurs on the day of a given local Time' do schedule.occurs_on?(Time.local(2010, 7, 1, 23, 59, 59)).should be_false schedule.occurs_on?(Time.local(2010, 7, 2, 0, 0, 1)).should be_true schedule.occurs_on?(Time.local(2010, 7, 2, 23, 59, 59)).should be_true schedule.occurs_on?(Time.local(2010, 7, 3, 0, 0, 1)).should be_false end specify 'should determine if it occurs on the day of a given non-local Time' do schedule.occurs_on?(Time.new(2010, 7, 1, 23, 59, 59, "+11:15")).should be_false schedule.occurs_on?(Time.new(2010, 7, 2, 0, 0, 1, "+11:15")).should be_true schedule.occurs_on?(Time.new(2010, 7, 2, 23, 59, 59, "+11:15")).should be_true schedule.occurs_on?(Time.new(2010, 7, 3, 0, 0, 1, "+11:15")).should be_false end specify 'should determine if it occurs on the day of a given ActiveSupport::Time', :if_active_support_time => true do Time.zone = "Pacific/Honolulu" schedule.occurs_on?(Time.zone.parse('2010-07-01 23:59:59')).should be_false schedule.occurs_on?(Time.zone.parse('2010-07-02 00:00:01')).should be_true schedule.occurs_on?(Time.zone.parse('2010-07-02 23:59:59')).should be_true schedule.occurs_on?(Time.zone.parse('2010-07-03 00:00:01')).should be_false end end end end shared_examples :occurs_on? do context 'starting from a UTC Time' do let(:start_time) { Time.utc(2010, 7, 2, 10, 0, 0) } include_examples "occurring on a given day" end context 'starting from a local Time' do let(:start_time) { Time.local(2010, 7, 2, 10, 0, 0) } include_examples "occurring on a given day" end context 'starting from a non-local Time' do let(:start_time) { Time.local(2010, 7, 2, 10, 0, 0, false, "-2:30") } include_examples 'occurring on a given day' end context 'starting from an ActiveSupport::Time', :if_active_support_time => true do let(:start_time) { Time.new(2010, 7, 2, 10, 0, 0, '-07:00').in_time_zone('America/Vancouver') } include_examples 'occurring on a given day' end end context 'with a recurrence rule limited by count' do before { schedule.add_recurrence_rule IceCube::Rule.daily.count(1) } include_examples :occurs_on? end context 'with a recurrence rule limited by until' do before { schedule.add_recurrence_rule IceCube::Rule.daily.until(start_time) } include_examples :occurs_on? end context 'with a single recurrence time' do before { schedule.add_recurrence_time(start_time) } include_examples :occurs_on? end it 'should be true for multiple rtimes' do schedule = IceCube::Schedule.new(Time.local(2010, 7, 10, 16)) schedule.add_recurrence_time(Time.local(2010, 7, 11, 16)) schedule.add_recurrence_time(Time.local(2010, 7, 12, 16)) schedule.add_recurrence_time(Time.local(2010, 7, 13, 16)) schedule.occurs_on?(Date.new(2010, 7, 11)).should be_true schedule.occurs_on?(Date.new(2010, 7, 12)).should be_true schedule.occurs_on?(Date.new(2010, 7, 13)).should be_true end end def compare_time_zone_info(start_time) schedule = IceCube::Schedule.new(start_time) schedule.rrule IceCube::Rule.yearly(1) occurrence = schedule.next_occurrence occurrence.dst?.should == start_time.dst? if start_time.respond_to? :dst? occurrence.utc?.should == start_time.utc? if start_time.respond_to? :utc? occurrence.zone.should == start_time.zone occurrence.utc_offset == start_time.utc_offset end def trap_infinite_loop_beyond(iterations) IceCube::ValidatedRule.any_instance.should_receive(:finds_acceptable_time?). at_most(iterations).times.and_call_original end end ice_cube-0.12.1/spec/examples/secondly_rule_spec.rb000066400000000000000000000020021235562124600223070ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe SecondlyRule, 'interval validation' do it 'converts a string integer to an actual int when using the interval method' do rule = Rule.secondly.interval("2") rule.validations_for(:interval).first.interval.should == 2 end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.secondly("3") rule.validations_for(:interval).first.interval.should == 3 end it 'raises an argument error when a bad value is passed' do expect { rule = Rule.secondly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { rule = Rule.secondly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end end end ice_cube-0.12.1/spec/examples/serialization_spec.rb000066400000000000000000000020021235562124600223150ustar00rootroot00000000000000require 'active_support/time' require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule do let(:schedule) { IceCube::Schedule.new Time.now } let(:yaml) { described_class.dump(schedule) } describe "::dump(schedule)" do it "serializes a Schedule object as YAML string" do yaml.should start_with "---\n" end [nil, ""].each do |blank| context "when schedule is #{blank.inspect}" do let(:schedule) { blank } it "returns #{blank.inspect}" do yaml.should be blank end end end end describe "::load(yaml)" do let(:new_schedule) { described_class.load yaml } it "creates a new object from a YAML string" do new_schedule.start_time.to_s.should eq schedule.start_time.to_s end [nil, ""].each do |blank| context "when yaml is #{blank.inspect}" do let(:yaml) { blank } it "returns #{blank.inspect}" do new_schedule.should be blank end end end end end ice_cube-0.12.1/spec/examples/string_builder_spec.rb000066400000000000000000000011311235562124600224560ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::StringBuilder do describe :sentence do it 'should return empty string when none' do IceCube::StringBuilder.sentence([]).should == '' end it 'should return sole when one' do IceCube::StringBuilder.sentence(['1']).should == '1' end it 'should split on and when two' do IceCube::StringBuilder.sentence(['1', '2']).should == '1 and 2' end it 'should comma and when more than two' do IceCube::StringBuilder.sentence(['1', '2', '3']).should == '1, 2, and 3' end end end ice_cube-0.12.1/spec/examples/time_util_spec.rb000066400000000000000000000031311235562124600214370ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe TimeUtil do describe :wday_to_sym do it 'converts 0..6 to weekday symbols' do TimeUtil.wday_to_sym(1).should == :monday end it 'returns weekday symbols as is' do TimeUtil.wday_to_sym(:monday).should == :monday end it 'raises an error for bad input' do expect { TimeUtil.wday_to_sym(:anyday) }.to raise_error expect { TimeUtil.wday_to_sym(17) }.to raise_error end end describe :sym_to_wday do it 'converts weekday symbols to 0..6 wday numbers' do TimeUtil.sym_to_wday(:monday).should == 1 end it 'returns wday numbers as is' do TimeUtil.sym_to_wday(1).should == 1 end it 'raises an error for bad input' do expect { TimeUtil.sym_to_wday(:anyday) }.to raise_error expect { TimeUtil.sym_to_wday(17) }.to raise_error end end describe :sym_to_month do it 'converts month symbols to 1..12 month numbers' do TimeUtil.sym_to_month(:january).should == 1 end it 'returns month numbers as is' do TimeUtil.sym_to_month(12).should == 12 end it 'raises an error for bad input' do expect { TimeUtil.sym_to_month(13) }.to raise_error expect { TimeUtil.sym_to_month(:neveruary) }.to raise_error end end describe :deserialize_time do it 'supports ISO8601 time strings' do expect(TimeUtil.deserialize_time('2014-04-04T18:30:00+08:00')).to eq(Time.utc(2014, 4, 4, 10, 30, 0)) end end end end ice_cube-0.12.1/spec/examples/to_ical_spec.rb000066400000000000000000000230431235562124600210620ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' require 'active_support/time' describe IceCube, 'to_ical' do it 'should return a proper ical representation for a basic daily rule' do rule = IceCube::Rule.daily rule.to_ical.should == "FREQ=DAILY" end it 'should return a proper ical representation for a basic monthly rule' do rule = IceCube::Rule.weekly rule.to_ical.should == "FREQ=WEEKLY" end it 'should return a proper ical representation for a basic monthly rule' do rule = IceCube::Rule.monthly rule.to_ical.should == "FREQ=MONTHLY" end it 'should return a proper ical representation for a basic yearly rule' do rule = IceCube::Rule.yearly rule.to_ical.should == "FREQ=YEARLY" end it 'should return a proper ical representation for a basic hourly rule' do rule = IceCube::Rule.hourly rule.to_ical.should == "FREQ=HOURLY" end it 'should return a proper ical representation for a basic minutely rule' do rule = IceCube::Rule.minutely rule.to_ical.should == "FREQ=MINUTELY" end it 'should return a proper ical representation for a basic secondly rule' do rule = IceCube::Rule.secondly rule.to_ical.should == "FREQ=SECONDLY" end it 'should be able to serialize a .day rule to_ical' do rule = IceCube::Rule.daily.day(:monday, :tuesday) rule.to_ical.should == "FREQ=DAILY;BYDAY=MO,TU" end it 'should be able to serialize a .day_of_week rule to_ical' do rule = IceCube::Rule.daily.day_of_week(:tuesday => [-1, -2]) rule.to_ical.should == "FREQ=DAILY;BYDAY=-1TU,-2TU" end it 'should be able to serialize a .day_of_month rule to_ical' do rule = IceCube::Rule.daily.day_of_month(23) rule.to_ical.should == "FREQ=DAILY;BYMONTHDAY=23" end it 'should be able to serialize a .day_of_year rule to_ical' do rule = IceCube::Rule.daily.day_of_year(100,200) rule.to_ical.should == "FREQ=DAILY;BYYEARDAY=100,200" end it 'should be able to serialize a .month_of_year rule to_ical' do rule = IceCube::Rule.daily.month_of_year(:january, :april) rule.to_ical.should == "FREQ=DAILY;BYMONTH=1,4" end it 'should be able to serialize a .hour_of_day rule to_ical' do rule = IceCube::Rule.daily.hour_of_day(10, 20) rule.to_ical.should == "FREQ=DAILY;BYHOUR=10,20" end it 'should be able to serialize a .minute_of_hour rule to_ical' do rule = IceCube::Rule.daily.minute_of_hour(5, 55) rule.to_ical.should == "FREQ=DAILY;BYMINUTE=5,55" end it 'should be able to serialize a .second_of_minute rule to_ical' do rule = IceCube::Rule.daily.second_of_minute(0, 15, 30, 45) rule.to_ical.should == "FREQ=DAILY;BYSECOND=0,15,30,45" end it 'should be able to collapse a combination day_of_week and day' do rule = IceCube::Rule.daily.day(:monday, :tuesday).day_of_week(:monday => [1, -1]) ['FREQ=DAILY;BYDAY=TU,1MO,-1MO', 'FREQ=DAILY;BYDAY=1MO,-1MO,TU'].include?(rule.to_ical).should be_true end it 'should be able to serialize of .day_of_week rule to_ical with multiple days' do rule = IceCube::Rule.daily.day_of_week(:monday => [1, -1], :tuesday => [2]).day(:wednesday) [ 'FREQ=DAILY;BYDAY=WE,1MO,-1MO,2TU', 'FREQ=DAILY;BYDAY=1MO,-1MO,2TU,WE', 'FREQ=DAILY;BYDAY=2TU,1MO,-1MO,WE', 'FREQ=DAILY;BYDAY=WE,2TU,1MO,-1MO', 'FREQ=DAILY;BYDAY=2TU,WE,1MO,-1MO' ].include?(rule.to_ical).should be_true end it 'should be able to serialize a base schedule to ical in local time' do Time.zone = "Eastern Time (US & Canada)" schedule = IceCube::Schedule.new(Time.zone.local(2010, 5, 10, 9, 0, 0)) schedule.to_ical.should == "DTSTART;TZID=EDT:20100510T090000" end it 'should be able to serialize a base schedule to ical in UTC time' do schedule = IceCube::Schedule.new(Time.utc(2010, 5, 10, 9, 0, 0)) schedule.to_ical.should == "DTSTART:20100510T090000Z" end it 'should be able to serialize a schedule with one rrule' do Time.zone = 'Pacific Time (US & Canada)' schedule = IceCube::Schedule.new(Time.zone.local(2010, 5, 10, 9, 0, 0)) schedule.add_recurrence_rule IceCube::Rule.weekly # test equality expectation = "DTSTART;TZID=PDT:20100510T090000\n" expectation << 'RRULE:FREQ=WEEKLY' schedule.to_ical.should == expectation end it 'should be able to serialize a schedule with multiple rrules' do Time.zone = 'Eastern Time (US & Canada)' schedule = IceCube::Schedule.new(Time.zone.local(2010, 10, 20, 4, 30, 0)) schedule.add_recurrence_rule IceCube::Rule.weekly.day_of_week(:monday => [2, -1]) schedule.add_recurrence_rule IceCube::Rule.hourly expectation = "DTSTART;TZID=EDT:20101020T043000\n" expectation << "RRULE:FREQ=WEEKLY;BYDAY=2MO,-1MO\n" expectation << "RRULE:FREQ=HOURLY" schedule.to_ical.should == expectation end it 'should be able to serialize a schedule with one exrule' do Time.zone ='Pacific Time (US & Canada)' schedule = IceCube::Schedule.new(Time.zone.local(2010, 5, 10, 9, 0, 0)) schedule.add_exception_rule IceCube::Rule.weekly # test equality expectation= "DTSTART;TZID=PDT:20100510T090000\n" expectation<< 'EXRULE:FREQ=WEEKLY' schedule.to_ical.should == expectation end it 'should be able to serialize a schedule with multiple exrules' do Time.zone ='Eastern Time (US & Canada)' schedule = IceCube::Schedule.new(Time.zone.local(2010, 10, 20, 4, 30, 0)) schedule.add_exception_rule IceCube::Rule.weekly.day_of_week(:monday => [2, -1]) schedule.add_exception_rule IceCube::Rule.hourly expectation = "DTSTART;TZID=EDT:20101020T043000\n" expectation<< "EXRULE:FREQ=WEEKLY;BYDAY=2MO,-1MO\n" expectation<< "EXRULE:FREQ=HOURLY" schedule.to_ical.should == expectation end it 'should be able to serialize a schedule with an rtime' do schedule = IceCube::Schedule.new(Time.utc(2010, 5, 10, 10, 0, 0)) schedule.add_recurrence_time Time.utc(2010, 6, 20, 5, 0, 0) # test equality expectation = "DTSTART:20100510T100000Z\n" expectation << "RDATE:20100620T050000Z" schedule.to_ical.should == expectation end it 'should be able to serialize a schedule with an exception time' do schedule = IceCube::Schedule.new(Time.utc(2010, 5, 10, 10, 0, 0)) schedule.add_exception_time Time.utc(2010, 6, 20, 5, 0, 0) # test equality expectation = "DTSTART:20100510T100000Z\n" expectation << "EXDATE:20100620T050000Z" schedule.to_ical.should == expectation end it 'should be able to serialize a schedule with a duration' do schedule = IceCube::Schedule.new(Time.utc(2010, 5, 10, 10), :duration => 3600) expectation = "DTSTART:20100510T100000Z\n" expectation << 'DTEND:20100510T110000Z' schedule.to_ical.should == expectation end it 'should be able to serialize a schedule with a duration - more odd duration' do schedule = IceCube::Schedule.new(Time.utc(2010, 5, 10, 10), :duration => 3665) expectation = "DTSTART:20100510T100000Z\n" expectation << 'DTEND:20100510T110105Z' schedule.to_ical.should == expectation end it 'should be able to serialize a schedule with an end time' do schedule = IceCube::Schedule.new(Time.utc(2010, 5, 10, 10), :end_time => Time.utc(2010, 5, 10, 20)) expectation = "DTSTART:20100510T100000Z\n" expectation << "DTEND:20100510T200000Z" schedule.to_ical.should == expectation end it 'should not modify the duration when running to_ical' do schedule = IceCube::Schedule.new(Time.now, :duration => 3600) schedule.to_ical schedule.duration.should == 3600 end it 'should default to to_ical using local time' do time = Time.now schedule = IceCube::Schedule.new(Time.now) schedule.to_ical.should == "DTSTART;TZID=#{time.zone}:#{time.strftime('%Y%m%dT%H%M%S')}" # default false end it 'should not have an rtime that duplicates start time' do start = Time.utc(2012, 12, 12, 12, 0, 0) schedule = IceCube::Schedule.new(start) schedule.add_recurrence_time start schedule.to_ical.should == "DTSTART:20121212T120000Z" end it 'should be able to receive a to_ical in utc time' do time = Time.now schedule = IceCube::Schedule.new(Time.now) schedule.to_ical.should == "DTSTART;TZID=#{time.zone}:#{time.strftime('%Y%m%dT%H%M%S')}" # default false schedule.to_ical(false).should == "DTSTART;TZID=#{time.zone}:#{time.strftime('%Y%m%dT%H%M%S')}" schedule.to_ical(true).should == "DTSTART:#{time.utc.strftime('%Y%m%dT%H%M%S')}Z" end it 'should be able to serialize to ical with an until date' do rule = IceCube::Rule.weekly.until Time.now rule.to_ical.should match /^FREQ=WEEKLY;UNTIL=\d{8}T\d{6}Z$/ end it 'should be able to serialize to ical with a count date' do rule = IceCube::Rule.weekly.count(5) rule.to_ical.should match /^FREQ=WEEKLY;COUNT=5$/ end %w{secondly minutely hourly daily monthly yearly}.each do |mthd| it "should include intervals for #{mthd} rule" do interval = 2 rule = IceCube::Rule.send(mthd.to_sym, interval) rule.to_ical.should == "FREQ=#{mthd.upcase};INTERVAL=#{interval}" end end it 'should include intervals for weekly rule, including weekstart' do interval = 2 rule = IceCube::Rule.send(:weekly, interval) rule.to_ical.should == "FREQ=WEEKLY;INTERVAL=#{interval};WKST=SU" end it 'should include intervals for weekly rule, including custom weekstart' do interval = 2 rule = IceCube::Rule.send(:weekly, interval, :monday) rule.to_ical.should == "FREQ=WEEKLY;INTERVAL=#{interval};WKST=MO" end it 'should not repeat interval when updating rule' do rule = IceCube::Rule.weekly rule.interval(2) rule.to_ical.should =~ /^FREQ=WEEKLY;INTERVAL=2/ end end ice_cube-0.12.1/spec/examples/to_s_spec.rb000066400000000000000000000174611235562124600204230ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule, 'to_s' do it 'should represent its start time by default' do t0 = Time.local(2013, 2, 14) IceCube::Schedule.new(t0).to_s.should == 'February 14, 2013' end it 'should have a useful base to_s representation for a secondly rule' do IceCube::Rule.secondly.to_s.should == 'Secondly' IceCube::Rule.secondly(2).to_s.should == 'Every 2 seconds' end it 'should have a useful base to_s representation for a minutely rule' do IceCube::Rule.minutely.to_s.should == 'Minutely' IceCube::Rule.minutely(2).to_s.should == 'Every 2 minutes' end it 'should have a useful base to_s representation for a hourly rule' do IceCube::Rule.hourly.to_s.should == 'Hourly' IceCube::Rule.hourly(2).to_s.should == 'Every 2 hours' end it 'should have a useful base to_s representation for a daily rule' do IceCube::Rule.daily.to_s.should == 'Daily' IceCube::Rule.daily(2).to_s.should == 'Every 2 days' end it 'should have a useful base to_s representation for a weekly rule' do IceCube::Rule.weekly.to_s.should == 'Weekly' IceCube::Rule.weekly(2).to_s.should == 'Every 2 weeks' end it 'should have a useful base to_s representation for a monthly rule' do IceCube::Rule.monthly.to_s.should == 'Monthly' IceCube::Rule.monthly(2).to_s.should == 'Every 2 months' end it 'should have a useful base to_s representation for a yearly rule' do IceCube::Rule.yearly.to_s.should == 'Yearly' IceCube::Rule.yearly(2).to_s.should == 'Every 2 years' end it 'should work with various sentence types properly' do IceCube::Rule.weekly.to_s.should == 'Weekly' IceCube::Rule.weekly.day(:monday).to_s.should == 'Weekly on Mondays' IceCube::Rule.weekly.day(:monday, :tuesday).to_s.should == 'Weekly on Mondays and Tuesdays' IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday).to_s.should == 'Weekly on Mondays, Tuesdays, and Wednesdays' end it 'should show saturday and sunday as weekends' do IceCube::Rule.weekly.day(:saturday, :sunday).to_s.should == 'Weekly on Weekends' end it 'should not show saturday and sunday as weekends when other days are present also' do IceCube::Rule.weekly.day(:sunday, :monday, :saturday).to_s.should == 'Weekly on Sundays, Mondays, and Saturdays' end it 'should reorganize days to be in order' do IceCube::Rule.weekly.day(:tuesday, :monday).to_s.should == 'Weekly on Mondays and Tuesdays' end it 'should show weekdays as such' do IceCube::Rule.weekly.day( :monday, :tuesday, :wednesday, :thursday, :friday ).to_s.should == 'Weekly on Weekdays' end it 'should not show weekdays as such when a weekend day is present' do IceCube::Rule.weekly.day( :sunday, :monday, :tuesday, :wednesday, :thursday, :friday ).to_s.should == 'Weekly on Sundays, Mondays, Tuesdays, Wednesdays, Thursdays, and Fridays' end it 'should show start time for an empty schedule' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.to_s.should == "March 20, 2010" end it 'should work with a single date' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.to_s.should == "March 20, 2010" end it 'should work with additional dates' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 21) schedule.to_s.should == 'March 20, 2010 / March 21, 2010' end it 'should order dates that are out of order' do schedule = IceCube::Schedule.new(t0 = Time.local(2010, 3, 20)) schedule.add_recurrence_time t1 = Time.local(2010, 3, 19) schedule.to_s.should == 'March 19, 2010 / March 20, 2010' end it 'should remove duplicated start time' do schedule = IceCube::Schedule.new t0 = Time.local(2010, 3, 20) schedule.add_recurrence_time t0 schedule.to_s.should == 'March 20, 2010' end it 'should remove duplicate rtimes' do schedule = IceCube::Schedule.new t0 = Time.local(2010, 3, 19) schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.to_s.should == 'March 19, 2010 / March 20, 2010' end it 'should work with rules and dates' do schedule = IceCube::Schedule.new Time.local(2010, 3, 19) schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.add_recurrence_rule IceCube::Rule.weekly schedule.to_s.should == 'March 20, 2010 / Weekly' end it 'should work with rules and times and exception times' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.add_recurrence_rule IceCube::Rule.weekly schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.add_exception_time Time.local(2010, 3, 20) # ignored schedule.add_exception_time Time.local(2010, 3, 21) schedule.to_s.should == 'Weekly / not on March 20, 2010 / not on March 21, 2010' end it 'should work with a single rrule' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.add_recurrence_rule IceCube::Rule.weekly.day_of_week(:monday => [1]) schedule.to_s.should == schedule.rrules[0].to_s end it 'should be able to say the last Thursday of the month' do rule_str = IceCube::Rule.monthly.day_of_week(:thursday => [-1]).to_s rule_str.should == 'Monthly on the last Thursday' end it 'should be able to say what months of the year something happens' do rule_str = IceCube::Rule.yearly.month_of_year(:june, :july).to_s rule_str.should == 'Yearly in June and July' end it 'should be able to say the second to last monday of the month' do rule_str = IceCube::Rule.monthly.day_of_week(:thursday => [-2]).to_s rule_str.should == 'Monthly on the 2nd to last Thursday' end it 'should join the first and last weekdays of the month' do rule_str = IceCube::Rule.monthly.day_of_week(:thursday => [1, -1]).to_s rule_str.should == 'Monthly on the 1st Thursday and last Thursday' end it 'should be able to say the days of the month something happens' do rule_str = IceCube::Rule.monthly.day_of_month(1, 15, 30).to_s rule_str.should == 'Monthly on the 1st, 15th, and 30th days of the month' end it 'should be able to say what day of the year something happens' do rule_str = IceCube::Rule.yearly.day_of_year(30).to_s rule_str.should == 'Yearly on the 30th day of the year' end it 'should be able to say what hour of the day something happens' do rule_str = IceCube::Rule.daily.hour_of_day(6, 12).to_s rule_str.should == 'Daily on the 6th and 12th hours of the day' end it 'should be able to say what minute of an hour something happens - with special suffix minutes' do rule_str = IceCube::Rule.hourly.minute_of_hour(10, 11, 12, 13, 14, 15).to_s rule_str.should == 'Hourly on the 10th, 11th, 12th, 13th, 14th, and 15th minutes of the hour' end it 'should be able to say what seconds of the minute something happens' do rule_str = IceCube::Rule.minutely.second_of_minute(10, 11).to_s rule_str.should == 'Minutely on the 10th and 11th seconds of the minute' end it 'should be able to reflect until dates' do schedule = IceCube::Schedule.new(Time.now) schedule.rrule IceCube::Rule.weekly.until(Time.local(2012, 2, 3)) schedule.to_s.should == 'Weekly until February 3, 2012' end it 'should be able to reflect count' do schedule = IceCube::Schedule.new(Time.now) schedule.add_recurrence_rule IceCube::Rule.weekly.count(1) schedule.to_s.should == 'Weekly 1 time' end it 'should be able to reflect count (proper pluralization)' do schedule = IceCube::Schedule.new(Time.now) schedule.add_recurrence_rule IceCube::Rule.weekly.count(2) schedule.to_s.should == 'Weekly 2 times' end end ice_cube-0.12.1/spec/examples/to_yaml_spec.rb000066400000000000000000000266771235562124600211340ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' require 'active_support/time' module IceCube describe Schedule, 'to_yaml' do before(:all) { Time.zone = 'Eastern Time (US & Canada)' } [:yearly, :monthly, :weekly, :daily, :hourly, :minutely, :secondly].each do |type| it "should make a #{type} round trip with to_yaml [#47]" do schedule = Schedule.new(t0 = Time.now) schedule.add_recurrence_rule Rule.send(type, 3) Schedule.from_yaml(schedule.to_yaml).first(3).inspect.should == schedule.first(3).inspect end end it 'should be able to let rules take round trips to yaml' do schedule = Schedule.new schedule.add_recurrence_rule Rule.monthly schedule = Schedule.from_yaml schedule.to_yaml rule = schedule.rrules.first rule.is_a?(MonthlyRule) end it 'should respond to .to_yaml' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.daily.until(Time.now) #check assumption schedule.should respond_to('to_yaml') end it 'should be able to make a round-trip to YAML' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.daily.until(Time.now + 10) result1 = schedule.all_occurrences yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) result2 = schedule2.all_occurrences # compare without usecs result1.map { |r| r.to_s }.should == result2.map { |r| r.to_s } end it 'should be able to make a round-trip to YAML with .day' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.daily.day(:monday, :wednesday) yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs schedule.first(10).map { |r| r.to_s }.should == schedule2.first(10).map { |r| r.to_s } end it 'should be able to make a round-trip to YAML with .day_of_month' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.monthly.day_of_month(10, 20) yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs schedule.first(10).map { |r| r.to_s }.should == schedule2.first(10).map { |r| r.to_s } end it 'should be able to make a round-trip to YAML with .day_of_week' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.weekly.day_of_week(:monday => [1, -2]) yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs schedule.first(10).map { |r| r.to_s }.should == schedule2.first(10).map { |r| r.to_s } end it 'should be able to make a round-trip to YAML with .day_of_year' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.yearly.day_of_year(100, 200) yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs schedule.first(10).map { |r| r.to_s }.should == schedule2.first(10).map { |r| r.to_s } end it 'should be able to make a round-trip to YAML with .hour_of_day' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.daily.hour_of_day(1, 2) yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs schedule.first(10).map { |r| r.to_s }.should == schedule2.first(10).map { |r| r.to_s } end it 'should be able to make a round-trip to YAML with .minute_of_hour' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.daily.minute_of_hour(0, 30) yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs schedule.first(10).map { |r| r.to_s }.should == schedule2.first(10).map { |r| r.to_s } end it 'should be able to make a round-trip to YAML with .month_of_year' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.yearly.month_of_year(:april, :may) yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs schedule.first(10).map { |r| r.to_s }.should == schedule2.first(10).map { |r| r.to_s } end it 'should be able to make a round-trip to YAML with .second_of_minute' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.daily.second_of_minute(1, 2) yaml_string = schedule.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs schedule.first(10).map { |r| r.to_s }.should == schedule2.first(10).map { |r| r.to_s } end it 'should have a to_yaml representation of a rule that does not contain ruby objects' do rule = Rule.daily.day_of_week(:monday => [1, -1]).month_of_year(:april) rule.to_yaml.include?('object').should be_false end it 'should have a to_yaml representation of a schedule that does not contain ruby objects' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.daily.day_of_week(:monday => [1, -1]).month_of_year(:april) schedule.to_yaml.include?('object').should be_false end # This test will fail when not run in Eastern Time # This is a bug because to_datetime will always convert to system local time it 'should be able to roll forward times and get back times in an array - TimeWithZone', :if_active_support_time => true do Time.zone = "Eastern Time (US & Canada)" start_date = Time.zone.local(2011, 11, 5, 12, 0, 0) schedule = Schedule.new(start_date) schedule = Schedule.from_yaml(schedule.to_yaml) # round trip ice_cube_start_date = schedule.start_time ice_cube_start_date.should == start_date ice_cube_start_date.utc_offset.should == start_date.utc_offset end it 'should be able to roll forward times and get back times in an array - Time' do start_date = Time.now schedule = Schedule.new(start_date) schedule = Schedule.from_yaml(schedule.to_yaml) # round trip ice_cube_start_date = schedule.start_time ice_cube_start_date.to_s.should == start_date.to_s ice_cube_start_date.class.should == Time ice_cube_start_date.utc_offset.should == start_date.utc_offset end it 'should be able to go back and forth to yaml and then call occurrences' do start_date = Time.local(2011, 5, 10, 12, 0, 0) schedule1 = Schedule.new(start_date) schedule1.add_recurrence_time start_date schedule2 = Schedule.from_yaml(schedule1.to_yaml) # round trip end_time = Time.now + ONE_DAY schedule1.occurrences(end_time).should == schedule2.occurrences(end_time) end it 'should be able to make a round trip with an exception time' do schedule = Schedule.new schedule.add_exception_time(time = Time.now) schedule = Schedule.from_yaml schedule.to_yaml schedule.extimes.map(&:to_s).should == [time.to_s] end it 'crazy shit' do start_date = Time.zone.now schedule = Schedule.new(start_date) schedule.add_recurrence_rule Rule.weekly.day(:wednesday) schedule.add_recurrence_time start_date schedule = Schedule.from_hash(schedule.to_hash) schedule = Schedule.from_yaml(schedule.to_yaml) schedule.occurrences(start_date + ONE_DAY * 14) end it 'should be able to make a round trip to hash with a duration' do schedule = Schedule.new Time.now, :duration => 3600 Schedule.from_hash(schedule.to_hash).duration.should == 3600 end it 'should be able to be serialized to yaml as part of a hash' do schedule = Schedule.new Time.now hash = { :schedule => schedule } lambda do hash.to_yaml end.should_not raise_error end it 'should be able to roll forward and back in time' do schedule = Schedule.new(Time.now) rt_schedule = Schedule.from_yaml(schedule.to_yaml) rt_schedule.start_time.utc_offset.should == schedule.start_time.utc_offset end it 'should be backward compatible with old yaml Time format' do pacific_time = 'Pacific Time (US & Canada)' yaml = "---\n:end_time:\n:rdates: []\n:rrules: []\n:duration:\n:exdates: []\n:exrules: []\n:start_date: 2010-10-18T14:35:47-07:00" schedule = Schedule.from_yaml(yaml) schedule.start_time.should be_a(Time) end it 'should work to_yaml with non-TimeWithZone' do schedule = Schedule.new(Time.now) schedule.to_yaml.length.should be < 200 end it 'should work with occurs_on and TimeWithZone' do pacific_time = 'Pacific Time (US & Canada)' Time.zone = pacific_time schedule = Schedule.new(Time.zone.now) schedule.add_recurrence_rule Rule.weekly schedule.occurs_on?(schedule.start_time.to_date + 6).should be_false schedule.occurs_on?(schedule.start_time.to_date + 7).should be_true schedule.occurs_on?(schedule.start_time.to_date + 8).should be_false end it 'should work with occurs_on and TimeWithZone' do start_time = Time.zone.local(2012, 7, 15, 12, 0, 0) pacific_time = 'Pacific Time (US & Canada)' Time.zone = pacific_time schedule = Schedule.new(start_time) schedule.add_recurrence_time start_time + 7 * ONE_DAY schedule.occurs_on?(schedule.start_time.to_date + 6).should be_false schedule.occurs_on?(schedule.start_time.to_date + 7).should be_true schedule.occurs_on?(schedule.start_time.to_date + 8).should be_false end it 'should crazy patch' do Time.zone = 'Pacific Time (US & Canada)' day = Time.zone.parse('21 Oct 2010 02:00:00') schedule = Schedule.new(day) schedule.add_recurrence_time(day) schedule.occurs_on?(Date.new(2010, 10, 20)).should be_false schedule.occurs_on?(Date.new(2010, 10, 21)).should be_true schedule.occurs_on?(Date.new(2010, 10, 22)).should be_false end it 'should be able to bring a Rule to_yaml and back with a timezone' do Time.zone = 'Pacific Time (US & Canada)' time = Time.now offset = time.utc_offset rule = Rule.daily.until(time) rule = Rule.from_yaml(rule.to_yaml) rule.until_date.utc_offset.should == offset end it 'should be able to bring a Rule to_yaml and back with a count' do rule = Rule.daily.count(5) rule = Rule.from_yaml rule.to_yaml rule.occurrence_count.should == 5 end it 'should be able to bring a Rule to_yaml and back with an undefined week start' do rule = Rule.weekly(2) rule = Rule.from_yaml rule.to_yaml rule.week_start.should == :sunday end it 'should be able to bring a Rule to_yaml and back with a week start defined' do rule = Rule.weekly.interval(2, :monday) rule = Rule.from_yaml rule.to_yaml rule.week_start.should == :monday end it 'should be able to bring in a schedule with a rule from hash with symbols or strings' do time = Time.zone.now symbol_data = { :start_date => time, :rrules => [ { :validations => { :day => [1] }, :rule_type => "IceCube::DailyRule", :interval => 1 } ], :exrules => [], :rtimes => [], :extimes => [] } string_data = { 'start_date' => time, 'rrules' => [ { 'validations' => { 'day' => [1] }, 'rule_type' => "IceCube::DailyRule", 'interval' => 1 } ], 'exrules' => [], 'rtimes' => [], 'extimes' => [] } symbol_yaml = Schedule.from_hash(symbol_data).to_yaml string_yaml = Schedule.from_hash(string_data).to_yaml YAML.load(symbol_yaml).should == YAML.load(string_yaml) end end end ice_cube-0.12.1/spec/examples/validated_rule_spec.rb000066400000000000000000000041001235562124600224250ustar00rootroot00000000000000require 'active_support/time' require File.dirname(__FILE__) + '/../spec_helper' describe IceCube, "::ValidatedRule" do describe "#next_time" do context "monthly" do let(:rule) { IceCube::Rule.monthly } it "Should return current day when starting on same day" do first = Time.new(2013, 2, 25, 0, 0, 0) schedule = IceCube::Schedule.new(first) schedule.add_recurrence_rule rule rule.next_time(first, schedule, nil).should == first end it "Should return the next month when starting one second in the future" do first = Time.new(2013, 2, 25, 0, 0, 0) schedule = IceCube::Schedule.new(first) schedule.add_recurrence_rule rule rule.next_time(first + 1, schedule, nil).should == Time.new(2013, 3, 25, 0, 0, 0) end it 'should return the next month near end of longer month [#171]' do schedule = IceCube::Schedule.new(Date.new 2013, 1, 1) [27, 28, 29, 30, 31].each do |day| rule.next_time(Time.new(2013, 1, day), schedule, nil).should == Time.new(2013, 2, 1) end end context "DST edge" do before { Time.zone = "Europe/London" } let(:first) { Time.zone.parse("Sun, 31 Mar 2013 00:00:00 GMT +00:00") } let(:schedule) { sc = IceCube::Schedule.new(first) sc.add_recurrence_rule rule sc } it "should not return the same time on a DST edge when starting one second in the future (results in infinite loop [#98])" do rule.next_time(first + 1, schedule, nil).to_s.should_not == first.to_s end it "previous failing test with DST edge taken into account" do rule.next_time(first + 1.hour + 1.second, schedule, nil).to_s.should_not == first.to_s end end end it 'should match times with usec' do first_time = Time.new(2012, 12, 21, 12, 21, 12.12121212) schedule = double(:start_time => first_time) rule = IceCube::Rule.secondly rule.next_time(first_time + 1, schedule, nil).should == first_time + 1 end end end ice_cube-0.12.1/spec/examples/weekly_rule_spec.rb000066400000000000000000000140111235562124600217720ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe WeeklyRule, 'interval validation' do it 'converts a string integer to an actual int when using the interval method' do rule = Rule.weekly.interval("2") rule.validations_for(:interval).first.interval.should == 2 end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.weekly("3") rule.validations_for(:interval).first.interval.should == 3 end it 'raises an argument error when a bad value is passed' do expect { rule = Rule.weekly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { rule = Rule.weekly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end end describe WeeklyRule do context :system_time_zone => 'America/Vancouver' do it 'should include nearest time in DST start hour' do schedule = Schedule.new(t0 = Time.local(2013, 3, 3, 2, 30, 0)) schedule.add_recurrence_rule Rule.weekly schedule.first(3).should == [ Time.local(2013, 3, 3, 2, 30, 0), # -0800 Time.local(2013, 3, 10, 3, 30, 0), # -0700 Time.local(2013, 3, 17, 2, 30, 0) # -0700 ] end it 'should not skip times in DST end hour' do schedule = Schedule.new(t0 = Time.local(2013, 10, 27, 2, 30, 0)) schedule.add_recurrence_rule Rule.weekly schedule.first(3).should == [ Time.local(2013, 10, 27, 2, 30, 0), # -0700 Time.local(2013, 11, 3, 2, 30, 0), # -0700 Time.local(2013, 11, 10, 2, 30, 0) # -0800 ] end end it 'should update previous interval' do schedule = double(start_time: t0 = Time.new(2013, 1, 1)) rule = Rule.weekly(7) rule.interval(2) rule.next_time(t0 + 1, schedule, nil).should == Time.new(2013, 1, 15) end it 'should produce the correct number of days for @interval = 1 with no weekdays specified' do schedule = Schedule.new(t0 = Time.now) schedule.add_recurrence_rule Rule.weekly #check assumption (2 weeks in the future) (1) (2) (3) (4) (5) times = schedule.occurrences(t0 + (7 * 3 + 1) * ONE_DAY) times.size.should == 4 end it 'should produce the correct number of days for @interval = 1 with only weekends' do schedule = Schedule.new(t0 = WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day(:saturday, :sunday) #check assumption schedule.occurrences(t0 + 4 * ONE_WEEK).size.should == 8 end it 'should set days from symbol args' do schedule = Schedule.new(t0 = WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day(:monday, :wednesday) schedule.rrules.first.validations_for(:day).map(&:day).should == [1, 3] end it 'should set days from array of symbols' do schedule = Schedule.new(t0 = WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day([:monday, :wednesday]) schedule.rrules.first.validations_for(:day).map(&:day).should == [1, 3] end it 'should set days from integer args' do schedule = Schedule.new(t0 = WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day(1, 3) schedule.rrules.first.validations_for(:day).map(&:day).should == [1, 3] end it 'should set days from array of integers' do schedule = Schedule.new(t0 = WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day([1, 3]) schedule.rrules.first.validations_for(:day).map(&:day).should == [1, 3] end it 'should raise an error on invalid input' do schedule = Schedule.new(t0 = WEDNESDAY) expect { schedule.add_recurrence_rule Rule.weekly.day(["1", "3"]) }.to raise_error end it 'should produce the correct number of days for @interval = 2 with only one day per week' do schedule = Schedule.new(t0 = WEDNESDAY) schedule.add_recurrence_rule Rule.weekly(2).day(:wednesday) #check assumption times = schedule.occurrences(t0 + 3 * ONE_WEEK) times.should == [t0, t0 + 2 * ONE_WEEK] end it 'should produce the correct days for @interval = 2, regardless of the start week' do schedule = Schedule.new(t0 = WEDNESDAY + ONE_WEEK) schedule.add_recurrence_rule Rule.weekly(2).day(:wednesday) #check assumption times = schedule.occurrences(t0 + 3 * ONE_WEEK) times.should == [t0, t0 + 2 * ONE_WEEK] end it 'should occur every 2nd tuesday of a month' do schedule = Schedule.new(t0 = Time.now) schedule.add_recurrence_rule Rule.monthly.hour_of_day(11).day_of_week(:tuesday => [2]) schedule.first(48).each do |d| d.hour.should == 11 d.wday.should == 2 end end it 'should be able to start on sunday but repeat on wednesdays' do schedule = Schedule.new(t0 = Time.local(2010, 8, 1)) schedule.add_recurrence_rule Rule.weekly.day(:monday) schedule.first(3).should == [ Time.local(2010, 8, 2), Time.local(2010, 8, 9), Time.local(2010, 8, 16) ] end it 'should start weekly rules on monday when monday is the week start' do schedule = Schedule.new(t0 = Time.local(2012, 2, 7)) schedule.add_recurrence_rule Rule.weekly(2, :monday).day(:tuesday, :sunday) schedule.first(3).should == [ Time.local(2012, 2, 7), Time.local(2012, 2, 12), Time.local(2012, 2, 21) ] end it 'should start weekly rules on sunday by default' do schedule = Schedule.new(t0 = Time.local(2012,2,7)) schedule.add_recurrence_rule Rule.weekly(2).day(:tuesday, :sunday) schedule.first(3).should == [ Time.local(2012, 2, 7), Time.local(2012, 2, 19), Time.local(2012, 2, 21) ] end it 'should validate week_start input' do expect { Rule.weekly(2, :someday) }.to raise_error end end end ice_cube-0.12.1/spec/examples/yearly_rule_spec.rb000066400000000000000000000064331235562124600220100ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::YearlyRule, 'interval validation' do it 'converts a string integer to an actual int when using the interval method' do rule = Rule.yearly.interval("2") rule.validations_for(:interval).first.interval.should == 2 end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.yearly("3") rule.validations_for(:interval).first.interval.should == 3 end it 'raises an argument error when a bad value is passed' do expect { rule = Rule.yearly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { rule = Rule.yearly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass an integer.") end end describe IceCube::YearlyRule, 'occurs_on?' do it 'should update previous interval' do schedule = double(start_time: t0 = Time.utc(2013, 5, 1)) rule = Rule.yearly(3) rule.interval(1) rule.next_time(t0 + 1, schedule, nil).should == t0 + 365.days end it 'should be able to specify complex yearly rules' do start_date = Time.local(2010, 7, 12, 5, 0, 0) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:april).day_of_week(:monday => [1, -1]) #check assumption - over 1 year should be 2 schedule.occurrences(start_date + IceCube::TimeUtil.days_in_year(start_date) * IceCube::ONE_DAY).size.should == 2 end it 'should produce the correct number of days for @interval = 1' do start_date = Time.now schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly #check assumption schedule.occurrences(start_date + 370 * IceCube::ONE_DAY).size.should == 2 end it 'should produce the correct number of days for @interval = 2' do start_date = Time.now schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly(2) #check assumption schedule.occurrences(start_date + 370 * IceCube::ONE_DAY).should == [start_date] end it 'should produce the correct number of days for @interval = 1 when you specify months' do start_date = Time.utc(2010, 1, 1) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:january, :april, :november) #check assumption schedule.occurrences(Time.utc(2010, 12, 31)).size.should == 3 end it 'should produce the correct number of days for @interval = 1 when you specify days' do start_date = Time.utc(2010, 1, 1) schedule = IceCube::Schedule.new(start_date) schedule.add_recurrence_rule IceCube::Rule.yearly.day_of_year(155, 200) #check assumption schedule.occurrences(Time.utc(2010, 12, 31)).size.should == 2 end it 'should produce the correct number of days for @interval = 1 when you specify negative days' do schedule = IceCube::Schedule.new(Time.utc(2010, 1, 1)) schedule.add_recurrence_rule IceCube::Rule.yearly.day_of_year(100, -1) #check assumption schedule.occurrences(Time.utc(2010, 12, 31)).size.should == 2 end end ice_cube-0.12.1/spec/spec_helper.rb000066400000000000000000000023041235562124600171060ustar00rootroot00000000000000begin require 'simplecov' SimpleCov.start rescue LoadError # okay end require File.dirname(__FILE__) + '/../lib/ice_cube' DAY = Time.utc(2010, 3, 1) WEDNESDAY = Time.utc(2010, 6, 23, 5, 0, 0) WORLD_TIME_ZONES = [ 'America/Anchorage', # -1000 / -0900 'Europe/London', # +0000 / +0100 'Pacific/Auckland', # +1200 / +1300 ] RSpec.configure do |config| config.around :each, :if_active_support_time => true do |example| example.run if defined? ActiveSupport end config.around :each, :if_active_support_time => false do |example| unless defined? ActiveSupport stubbed_active_support = ::ActiveSupport = Module.new example.run Object.send :remove_const, :ActiveSupport end end config.around :each do |example| if zone = example.metadata[:system_time_zone] @orig_zone = ENV['TZ'] ENV['TZ'] = zone example.run ENV['TZ'] = @orig_zone else example.run end end config.before :each do if time_args = @example.metadata[:system_time] case time_args when Array then Time.stub!(:now).and_return Time.local(*time_args) when Time then Time.stub!(:now).and_return time_args end end end end