pax_global_header00006660000000000000000000000064141343415070014514gustar00rootroot0000000000000052 comment=36f6d2414d3d06ee3f5e0b9484b03a2a754d4945 ice_cube-0.16.4/000077500000000000000000000000001413434150700133425ustar00rootroot00000000000000ice_cube-0.16.4/.gitignore000066400000000000000000000001771413434150700153370ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ # rspec failure tracking .rspec_status ice_cube-0.16.4/.rspec000066400000000000000000000000261413434150700144550ustar00rootroot00000000000000--require spec_helper ice_cube-0.16.4/.travis.yml000066400000000000000000000013661413434150700154610ustar00rootroot00000000000000sudo: false language: ruby before_install: - gem install bundler notifications: email: - john.crepezzi@gmail.com - andrew@avit.ca branches: only: - master rvm: - 1.9.3 - 2.0 - 2.1.10 - 2.2.6 - 2.3.3 - 2.4.0 - ruby-head env: - RAILS_VERSION='~> 3.2' - RAILS_VERSION='~> 4.2' - RAILS_VERSION='~> 5.0' matrix: exclude: # Rails 5 only runs on ruby 2.3 and up - rvm: 1.9.3 env: RAILS_VERSION='~> 5.0' - rvm: 2.0 env: RAILS_VERSION='~> 5.0' - rvm: 2.1.10 env: RAILS_VERSION='~> 5.0' - rvm: 2.2.6 env: RAILS_VERSION='~> 5.0' # Rails 3 no longer runs on ruby 2.4 and up - rvm: 2.4.0 env: RAILS_VERSION='~> 3.2' - rvm: ruby-head env: RAILS_VERSION='~> 3.2' ice_cube-0.16.4/CHANGELOG.md000066400000000000000000000203521413434150700151550ustar00rootroot00000000000000# Changelog ## 0.16.4 / 2021-10-21 * [FEATURE] Italian translations ## 0.16.3 / 2018-07-23 * [FEATURE] Added support for parsing RDATE from iCal format ## 0.16.2 / 2017-07-10 * [BUGFIX] Fix serialization of Date values (for `until`) (#399) * [BUGFIX] Fix double DST occurrences (#398) * [BUGFIX] Realign first wday for monday-based weekly rules (#402) * [BUGFIX] Fix weekly realignment for `spans: true` option (#402) ## 0.16.1 / 2017-05-03 * [FEATURE] Add pt-BR i18n locale (#388) * [BUGFIX] Fix misaligned first weekly occurrence (#387) ## 0.16.0 / 2017-04-12 * [FEATURE] Support ruby 2.4 * [ENHANCEMENT] Raise ArgumentError on empty values for remaining rules (#373) * [BUGFIX] Fix biweekly realign with spans option (#377) * [BUGFIX] Fix `day_of_year` with negative offsets (#326) * [BUGFIX] Fix weekly rule alignment with non-Sunday week start (#383) ## 0.15.0 / 2017-01-27 * [FEATURE] Added i18n translations for Russian, Swedish, German, and French * [ENHANCEMENT] Support testing with different `RAILS_VERSION` * [ENHANCEMENT] Support "until" Date with local Time conversion (#327) * [ENHANCEMENT] Validate rules (and raise ArgumentError) on empty `from_hash` * [BUGFIX] Fix validations on `Rule.from_hash` with empty array (#281) ## 0.14.0 / 2016-02-23 * [FEATURE] Option to include prior occurrences with overlapping duration (#302) ## 0.13.3 / 2016-01-30 * [ENHANCEMENT] Performance optimizations * [ENHANCEMENT] Default deprecation compatibility to track the current version ## 0.13.2 / 2015-12-09 No changes. ## 0.13.1 / 2015-12-07 * [FEATURE] Added i18n support! * [FEATURE] Option to include prior occurrences with overlapping duration (#154) ## 0.13.0 / 2015-05-26 NOTE: the commit for the _v0.13.0_ release tag incorrectly says _Release 0.13.1_ * [FEATURE] Add `from_ical`! (#258) * [BUGFIX] Method arity for `ActiveSupport::TimeZone.to_s` (#255) * [BUGFIX] Fix whole-day skip with date inputs * [BUGFIX] Missed times selected from gap week with weekly interval > 1 (#241) * [BUGFIX] Fix `occurs_on?` miss near midnight for DST (#245) ## 0.12.1 / 2014-07-04 * [FEATURE] Added support for deserialization of times via Time.parse * [ENHANCEMENT] Added interval validations * [ENHANCEMENT] Deprecation message improvements * [BUGFIX] Coerce validation intervals to Fixnum * [BUGFIX] Fix YAML serialization on blank values in ActiveRecord (#231) * [BUGFIX] Yearly interval should return self like others ## 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.16.4/Gemfile000066400000000000000000000005431413434150700146370ustar00rootroot00000000000000source 'https://rubygems.org' gemspec compatible_rails_versions = [ '>= 3.0.0', ('<5' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2.2')) ].compact gem 'activesupport', (ENV['RAILS_VERSION'] || compatible_rails_versions), require: false gem 'i18n', require: false gem 'tzinfo', require: false # only needed explicitly for RAILS_VERSION=3 ice_cube-0.16.4/LICENSE000066400000000000000000000020551413434150700143510ustar00rootroot00000000000000Copyright © 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.16.4/README.md000066400000000000000000000232251413434150700146250ustar00rootroot00000000000000# ice_cube - Easy schedule expansion [![Build Status][travis-ice_cube-badge_image]][travis-ice_cube] [![Gem Version](https://badge.fury.io/rb/ice_cube.svg)](http://badge.fury.io/rb/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 = IceCube::Schedule.new schedule.add_recurrence_rule( IceCube::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. Requires "rails/activesupport" (`gem install 'activesupport'`). ```ruby require 'ice_cube' require 'active_support/time' schedule = IceCube::Schedule.new(now = Time.now) do |s| s.add_recurrence_rule(IceCube::Rule.daily.count(4)) 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, now + 3.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 + 4.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 + 2.days, now + 3.days] schedule.last # now + 3.days # or the next occurrence schedule.next_occurrence(from_time) # defaults to Time.now schedule.next_occurrences(4, from_time) # defaults to Time.now schedule.remaining_occurrences # for terminating schedules # or the previous occurrence schedule.previous_occurrence(from_time) schedule.previous_occurrences(4, from_time) # or include prior occurrences with a duration overlapping from_time schedule.next_occurrences(4, from_time, :spans => true) schedule.occurrences_between(from_time, to_time, :spans => true) # or give the schedule a duration and ask if occurring_at? schedule = IceCube::Schedule.new(now, :duration => 3600) schedule.add_recurrence_rule IceCube::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 IceCube::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 compatibility 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 It also supports partial serialization to/from `ICAL`. Parsing datetimes with time zone information is not currently supported. ``` ruby yaml = schedule.to_yaml IceCube::Schedule.from_yaml(yaml) hash = schedule.to_hash IceCube::Schedule.from_hash(hash) ical = schedule.to_ical IceCube::Schedule.from_ical(ical) ``` --- ## 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 IceCube::Rule.daily # every third day schedule.add_recurrence_rule IceCube::Rule.daily(3) ``` ### Weekly ```ruby # every week schedule.add_recurrence_rule IceCube::Rule.weekly # every other week on monday and tuesday schedule.add_recurrence_rule IceCube::Rule.weekly(2).day(:monday, :tuesday) # for programmatic convenience (same as above) schedule.add_recurrence_rule IceCube::Rule.weekly(2).day(1, 2) # specifying a weekly interval with a different first weekday (defaults to Sunday) schedule.add_recurrence_rule IceCube::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 IceCube::Rule.monthly.day_of_month(1, -1) # every other month on the 15th of the month schedule.add_recurrence_rule IceCube::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 IceCube::Rule.monthly.day_of_week(:tuesday => [1, -1]) # every other month on the first monday and last tuesday schedule.add_recurrence_rule IceCube::Rule.monthly(2).day_of_week( :monday => [1], :tuesday => [-1] ) # for programmatic convenience (same as above) schedule.add_recurrence_rule IceCube::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 IceCube::Rule.yearly.day_of_year(100, -100) # every fourth year on new year's eve schedule.add_recurrence_rule IceCube::Rule.yearly(4).day_of_year(-1) ``` ### Yearly (by month of year) ```ruby # every year on the same day as start_time but in january and february schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:january, :february) # every third year in march schedule.add_recurrence_rule IceCube::Rule.yearly(3).month_of_year(:march) # for programmatic convenience (same as above) schedule.add_recurrence_rule IceCube::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 IceCube::Rule.hourly # every other hour, on mondays schedule.add_recurrence_rule IceCube::Rule.hourly(2).day(:monday) ``` ### Minutely (every N minutes) ```ruby # every 10 minutes schedule.add_recurrence_rule IceCube::Rule.minutely(10) # every hour and a half, on the last tuesday of the month schedule.add_recurrence_rule IceCube::Rule.minutely(90).day_of_week(:tuesday => [-1]) ``` ### Secondly (every N seconds) ```ruby # every second schedule.add_recurrence_rule IceCube::Rule.secondly # every 15 seconds between 12:00 - 12:59 schedule.add_recurrence_rule IceCube::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-badge_image]: https://secure.travis-ci.org/seejohnrun/ice_cube.svg [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.16.4/Rakefile000066400000000000000000000002111413434150700150010ustar00rootroot00000000000000require "rspec/core/rake_task" require "bundler/gem_tasks" RSpec::Core::RakeTask.new(:spec) task :build => :spec task :default => :spec ice_cube-0.16.4/UPGRADING.md000066400000000000000000000011361413434150700152050ustar00rootroot00000000000000# 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.16.4/config/000077500000000000000000000000001413434150700146075ustar00rootroot00000000000000ice_cube-0.16.4/config/locales/000077500000000000000000000000001413434150700162315ustar00rootroot00000000000000ice_cube-0.16.4/config/locales/de.yml000066400000000000000000000071341413434150700173510ustar00rootroot00000000000000de: ice_cube: pieces_connector: ' / ' not: 'außer %{target}' not_on: 'außer am %{target}' date: formats: default: '%-d. %B %Y' month_names: - - Januar - Februar - März - April - Mai - Juni - Juli - August - September - Oktober - November - Dezember day_names: - Sonntag - Montag - Dienstag - Mittwoch - Donnerstag - Freitag - Samstag times: other: '%{count} mal' one: '%{count} mal' until: 'bis zum %{date}' days_of_week: '%{segments} %{day}' days_of_month: one: '%{segments} Tag des Monats' other: '%{segments} Tag des Monats' days_of_year: one: '%{segments} Tag des Jahres' other: '%{segments} Tag des Jahres' at_hours_of_the_day: one: in der %{segments} Stunde des Tages other: in der %{segments} Stunde des Tages on_minutes_of_hour: one: in der %{segments} Minute der Stunde other: in der %{segments} Minute der Stunde at_seconds_of_minute: one: in der %{segments} Sekunde other: in der %{segments} Sekunde on_seconds_of_minute: one: in der %{segments} Sekunde der Minute other: in der %{segments} Sekunde der Minute each_second: one: Jede Sekunde other: Alle %{count} Sekunden each_minute: one: Jede Minute other: Alle %{count} Minuten each_hour: one: Stündlich other: Alle %{count} Stunden each_day: one: Täglich other: Alle %{count} Tage each_week: one: Wöchentlich other: Alle %{count} Wochen each_month: one: Monatlich other: Alle %{count} Monate each_year: one: Jährlich other: Alle %{count} Jahre 'on': am %{sentence} in: 'im %{target}' integer: negative: '%{ordinal}. letzter' literal_ordinals: -1: letzten -2: vorletzten ordinal: '%{number}%{ordinal}' ordinals: default: '.' # 1: st # 2: nd # 3: rd # 11: th # 12: th # 13: th on_weekends: am Wochenende on_weekdays: an Wochentagen days_on: - Sonntags - Montags - Dienstags - Mittwochs - Donnerstags - Freitags - Samstags on_days: '%{days}' array: last_word_connector: ' und ' two_words_connector: ' und ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: abbr_day_names: - So - Mo - Di - Mi - Do - Fr - Sa abbr_month_names: - - Jan - Feb - Mär - Apr - Mai - Jun - Jul - Aug - Sep - Okt - Nov - Dez day_names: - Sonntag - Montag - Dienstag - Mittwoch - Donnerstag - Freitag - Samstag formats: default: "%Y-%m-%d" long: "%B %d, %Y" short: "%b %d" month_names: - - Januar - Februar - März - April - Mai - Juni - Juli - August - September - Oktober - November - Dezember order: - :year - :month - :day time: am: am formats: default: "%a, %d %b %Y %H:%M:%S %z" long: "%B %d, %Y %H:%M" short: "%d %b %H:%M" pm: pm ice_cube-0.16.4/config/locales/en.yml000066400000000000000000000057151413434150700173660ustar00rootroot00000000000000en: ice_cube: pieces_connector: ' / ' not: 'not %{target}' not_on: 'not on %{target}' date: formats: default: '%B %-d, %Y' times: other: '%{count} times' one: '%{count} time' until: 'until %{date}' days_of_week: '%{segments} %{day}' days_of_month: other: '%{segments} days of the month' one: '%{segments} day of the month' days_of_year: other: '%{segments} days of the year' one: '%{segments} day of the year' at_hours_of_the_day: other: on the %{segments} hours of the day one: on the %{segments} hour of the day on_minutes_of_hour: other: on the %{segments} minutes of the hour one: on the %{segments} minute of the hour at_seconds_of_minute: other: at the %{segments} seconds one: at the %{segments} second on_seconds_of_minute: other: on the %{segments} seconds of the minute one: on the %{segments} second of the minute each_second: one: Secondly other: Every %{count} seconds each_minute: one: Minutely other: Every %{count} minutes each_hour: one: Hourly other: Every %{count} hours each_day: one: Daily other: Every %{count} days each_week: one: Weekly other: Every %{count} weeks each_month: one: Monthly other: Every %{count} months each_year: one: Yearly other: Every %{count} years 'on': on the %{sentence} in: 'in %{target}' integer: negative: '%{ordinal} to last' literal_ordinals: -1: last -2: 2nd to last ordinal: '%{number}%{ordinal}' ordinals: default: th 1: st 2: nd 3: rd 11: th 12: th 13: th on_weekends: on Weekends on_weekdays: on Weekdays days_on: - Sundays - Mondays - Tuesdays - Wednesdays - Thursdays - Fridays - Saturdays on_days: on %{days} array: last_word_connector: ', and ' two_words_connector: ' and ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: abbr_day_names: - Sun - Mon - Tue - Wed - Thu - Fri - Sat abbr_month_names: - - Jan - Feb - Mar - Apr - May - Jun - Jul - Aug - Sep - Oct - Nov - Dec day_names: - Sunday - Monday - Tuesday - Wednesday - Thursday - Friday - Saturday month_names: - - January - February - March - April - May - June - July - August - September - October - November - December ice_cube-0.16.4/config/locales/es.yml000066400000000000000000000060161413434150700173660ustar00rootroot00000000000000es: ice_cube: pieces_connector: ', ' not: 'excepto %{target}' not_on: 'excepto el %{target}' date: formats: default: '%-d de %B de %Y' times: other: '%{count} veces' one: '%{count} vez' until: 'hasta el %{date}' days_of_week: '%{segments} %{day}' days_of_month: other: 'los días %{segments} del mes' one: 'el día %{segments} del mes' days_of_year: one: 'el día %{segments}' other: 'los días %{segments}' at_hours_of_the_day: one: 'en la hora %{segments}' other: 'en las horas %{segments}' on_minutes_of_hour: one: 'en el minuto %{segments}' other: 'en los minutos %{segments}' at_seconds_of_minute: one: 'en el segundo %{segments}' other: 'en los segundos %{segments}' on_seconds_of_minute: one: 'en el segundo %{segments} del minuto' other: 'en los segundos %{segments} del minuto' on_days: '%{days}' each_second: one: Cada segundo other: 'Cada %{count} segundos' each_minute: one: Cada minuto other: 'Cada %{count} minutos' each_hour: one: Cada hora other: 'Cada %{count} horas' each_day: one: Diariamente other: 'Cada %{count} días' each_week: one: Semanalmente other: 'Cada %{count} semanas' each_month: one: Mensualmente other: 'Cada %{count} meses' each_year: one: Anualmente other: 'Cada %{count} años' 'on': 'en %{sentence}' in: 'en %{target}' integer: negative: '%{ordinal} por la cola' literal_ordinals: -1: el último -2: el penúltimo ordinal: '%{number}%{ordinal}' ordinals: default: º 1: º 2: º 3: º 11: º 12: º 13: º on_weekends: en fin de semana on_weekdays: en días laborables days_on: - los domingos - los lunes - los martes - los miércoles - los jueves - los viernes - los sábados on_days: '%{days}' array: last_word_connector: ' y ' two_words_connector: ' y ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: day_names: - Domingo - Lunes - Martes - Miércoles - Jueves - Viernes - Sábado abbr_day_names: - Dom - Lun - Mar - Mie - Jue - Vie - Sab month_names: - - Enero - Febrero - Marzo - Abril - Mayo - Junio - Julio - Agosto - Septiembre - Octubre - Noviembre - Diciembre abbr_month_names: - - Ene - Feb - Mar - Abr - May - Jun - Jul - Ago - Sep - Oct - Nov - Dic ice_cube-0.16.4/config/locales/fr.yml000066400000000000000000000070731413434150700173720ustar00rootroot00000000000000fr: ice_cube: pieces_connector: ' / ' not: 'pas %{target}' not_on: 'pas durant %{target}' date: formats: default: '%d %B %Y' month_names: - - janvier - février - mars - avril - mai - juin - juillet - août - septembre - octobre - novembre - décembre day_names: - dimanche - lundi - mardi - mercredi - jeudi - vendredi - samedi times: other: '%{count} fois' one: '%{count} fois' until: "jusqu'au %{date}" days_of_week: '%{segments} %{day}' days_of_month: other: '%{segments} jours du mois' one: '%{segments} jours du mois' days_of_year: other: "%{segments} jours de l'année" one: "%{segments} jours de l'année" at_hours_of_the_day: other: aux %{segments} heures de la journée one: à %{segments}h on_minutes_of_hour: other: aux %{segments} minutes de l'heure one: à la %{segments} minute de l'heure at_seconds_of_minute: other: aux %{segments} secondes one: à la %{segments} seconde on_seconds_of_minute: other: aux %{segments} secondes de la minute one: à la %{segments} seconde de la minute each_second: one: Toutes les secondes other: Toutes les %{count} secondes each_minute: one: Toutes les minutes other: Toutes les %{count} minutes each_hour: one: Toutes les heures other: Toutes les %{count} heures each_day: one: Quotidien other: Tous les %{count} jours each_week: one: Hebdomadaire other: Toutes les %{count} semaines each_month: one: Mensuel other: Tous les %{count} mois each_year: one: Annuel other: Tous les %{count} ans 'on': les %{sentence} in: 'en %{target}' integer: negative: '%{ordinal} depuis la fin' literal_ordinals: -1: derniers -2: avant-derniers ordinal: '%{number}%{ordinal}' ordinals: default: '°' 1: ° on_weekends: pendant les weekends on_weekdays: pendant les jours ouvrés days_on: - dimanches - lundis - mardis - mercredis - jeudis - vendredis - samedis on_days: les %{days} array: last_word_connector: ', et ' two_words_connector: ' et ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: abbr_day_names: - Dim - Lun - Mar - Mer - Jeu - Ven - Sam abbr_month_names: - - Jan - Fév - Mar - Avr - Mai - Jun - Jul - Aou - Sep - Oct - Nov - Déc day_names: - dimanche - lundi - mardi - mercredi - jeudi - vendredi - samedi formats: default: "%d-%m-%Y" long: "%d %B %Y" short: "%d %b" month_names: - - janvier - février - mars - avril - mai - juin - juillet - août - septembre - octobre - novembre - décembre order: - :year - :month - :day time: am: am formats: default: "%a, %d %b %Y %H:%M:%S %z" long: "%d %B %Y %H:%M" short: "%d %b %H:%M" pm: pm ice_cube-0.16.4/config/locales/it.yml000066400000000000000000000071651413434150700174010ustar00rootroot00000000000000it: ice_cube: pieces_connector: ' / ' not: 'tranne %{target}' not_on: 'tranne %{target}' date: formats: default: '%-d. %B %Y' month_names: - - gennaio - febbraio - marzo - aprile - maggio - giugno - luglio - agosto - settembre - ottobre - novembre - dicembre day_names: - domenica - lunedì - martedì - mercoledì - giovedì - venerdì - sabato times: other: '%{count} volte' one: '%{count} volta' until: 'fino al %{date}' days_of_week: '%{segments} %{day}' days_of_month: one: '%{segments} giorno del mese' other: '%{segments} giorni del mese' days_of_year: one: "%{segments} giorno dell'anno" other: "%{segments} giorni dell'anno" at_hours_of_the_day: one: alla %{segments} ora del giorno other: alla %{segments} ora del giorno on_minutes_of_hour: one: al %{segments} minuto dell'ora other: al %{segments} minuto dell'ora at_seconds_of_minute: one: al %{segments} secondo other: al %{segments} secondo on_seconds_of_minute: one: al %{segments} secondo del minuto other: al %{segments} secondo del minuto each_second: one: ogni secondo other: Ogni %{count} secondi each_minute: one: Ogni minuto other: Ogni %{count} minuti each_hour: one: Ogni ora other: Ogni %{count} ore each_day: one: Ogni giorno other: Ogni %{count} giorni each_week: one: Ogni settimana other: Ogni %{count} settimane each_month: one: Ogni mese other: Ogni %{count} mesi each_year: one: Ogni anno other: Ogni %{count} anni 'on': il %{sentence} in: 'a %{target}' integer: negative: '%{ordinal}. e ultimo' literal_ordinals: -1: ultimo -2: penultimo ordinal: '%{number}%{ordinal}' ordinals: default: '.' # 1: st # 2: nd # 3: rd # 11: th # 12: th # 13: th on_weekends: nel fine settimana on_weekdays: nei giorni feriali days_on: - di domenica - di lunedì - di martedì - di mercoledì - di giovedì - di venerdì - di sabato on_days: '%{days}' array: last_word_connector: ' e ' two_words_connector: ' e ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: abbr_day_names: - dom - lun - mar - mer - gio - ven - sab abbr_month_names: - - gen - feb - mar - apr - mag - giu - lug - ago - set - ott - nov - dic day_names: - domenica - lunedì - martedì - mercoledì - giovedì - venerdì - sabato formats: default: "%d/%m/%Y" long: "%d %B %Y" short: "%d %b" month_names: - - gennaio - febbraio - marzo - aprile - maggio - giugno - luglio - agosto - settembre - ottobre - novembre - dicembre order: - :day - :month - :year time: am: alle formats: default: "%a, %d %b %Y %H:%M:%S %z" long: "%B %d, %Y %H:%M" short: "%d %b %H:%M" pm: pm ice_cube-0.16.4/config/locales/ja.yml000066400000000000000000000050411413434150700173460ustar00rootroot00000000000000ja: ice_cube: pieces_connector: ' / ' not: '%{target}以外' not_on: '%{target}以外' date: formats: default: '%Y年%m月%d日' times: other: '%{count}回' one: '%{count}回' until: '%{date}まで' days_of_week: '%{segments}%{day}' days_of_month: other: '%{segments}日' one: '%{segments}日' days_of_year: other: '%{segments}日' one: '%{segments}日' at_hours_of_the_day: other: '%{segments}時' one: '%{segments}時' on_minutes_of_hour: other: '%{segments}分' one: '%{segments}分' on_seconds_of_minute: other: '%{segments}秒' one: '%{segments}秒' each_second: one: 毎秒 other: '%{count}秒ごと' each_minute: one: 毎分 other: '%{count}分ごと' each_hour: one: 毎時間 other: '%{count}時間ごと' each_day: one: 毎日 other: '%{count}日ごと' each_week: one: 毎週 other: '%{count}週間ごと' each_month: one: 毎月 other: '%{count}ヶ月ごと' each_year: one: 毎年 other: '%{count}年ごと' 'on': '%{sentence}' in: '%{target}' integer: negative: '最終%{ordinal}' literal_ordinals: -1: 最終 -2: 最後から2番目の ordinal: '%{ordinal}%{number}' ordinals: default: '' on_weekends: 週末 on_weekdays: 平日 days_on: - 日曜 - 月曜 - 火曜 - 水曜 - 木曜 - 金曜 - 土曜 on_days: '%{days}' array: last_word_connector: '、' two_words_connector: '、' words_connector: '、' string: format: day: '%{rest}%{current}' day_of_week: '%{rest}%{current}' day_of_month: '%{rest}%{current}' day_of_year: '%{rest}%{current}' hour_of_day: '%{rest}%{current}' minute_of_hour: '%{rest}%{current}' until: '%{current}%{rest}' count: '%{rest}%{current}' default: '%{rest}%{current}' date: abbr_day_names: - 日 - 月 - 火 - 水 - 木 - 金 - 土 abbr_month_names: - - 1月 - 2月 - 3月 - 4月 - 5月 - 6月 - 7月 - 8月 - 9月 - 10月 - 11月 - 12月 day_names: - 日曜 - 月曜 - 火曜 - 水曜 - 木曜 - 金曜 - 土曜 month_names: - - 1月 - 2月 - 3月 - 4月 - 5月 - 6月 - 7月 - 8月 - 9月 - 10月 - 11月 - 12月 ice_cube-0.16.4/config/locales/nl.yml000066400000000000000000000056021413434150700173700ustar00rootroot00000000000000nl: ice_cube: pieces_connector: ' / ' not: 'niet %{target}' not_on: 'niet op %{target}' date: formats: default: '%-d %B %Y' times: other: '%{count} keer' one: '%{count} keer' until: 'tot %{date}' days_of_week: '%{segments} %{day}' days_of_month: other: '%{segments} dagen van de maand' one: '%{segments} dag van de maand' days_of_year: other: '%{segments} dagen van het jaar' one: '%{segments} dag van het jaar' at_hours_of_the_day: other: op de %{segments} uren van de dag one: op het %{segments} uur on_minutes_of_hour: other: op de %{segments} minuten van het uur one: om de %{segments} minuut van het uur at_seconds_of_minute: other: op de %{segments} seconden one: op de %{segments} seconde on_seconds_of_minute: other: op de %{segments} seconden van de minuut one: op de %{segments} seconde van de minuut each_second: one: Elke seconde other: Elke %{count} seconden each_minute: one: Elke minuut other: Elke %{count} minuten each_hour: one: Elk uur other: Elke %{count} uren each_day: one: Dagelijks other: Elke %{count} dagen each_week: one: Wekelijks other: Elke %{count} weken each_month: one: Maandelijks other: Elke %{count} maanden each_year: one: Jaarlijks other: Elke %{count} jaren 'on': op de %{sentence} in: 'in %{target}' integer: negative: '%{ordinal} laatste' literal_ordinals: -1: laatste -2: voorlaatste ordinal: '%{number}%{ordinal}' ordinals: default: e on_weekends: in het weekend on_weekdays: op weekdagen days_on: - zondagen - maandagen - dinsdagen - woensdagen - donderdagen - vrijdagen - zaterdagen on_days: op %{days} array: last_word_connector: ' en ' two_words_connector: ' en ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: abbr_day_names: - zo - ma - di - wo - do - vr - za abbr_month_names: - - jan - feb - mrt - apr - mei - jun - jul - aug - sep - okt - nov - dec day_names: - zondag - maandag - dinsdag - woensdag - donderdag - vrijdag - zaterdag month_names: - - januari - februari - maart - april - mei - juni - juli - augustus - september - oktober - november - december ice_cube-0.16.4/config/locales/pt-BR.yml000066400000000000000000000071261413434150700177060ustar00rootroot00000000000000pt-BR: ice_cube: pieces_connector: ' / ' not: 'exceto %{target}' not_on: 'exceto o dia %{target}' date: formats: default: '%-d %B %Y' month_names: - - Janeiro - Fevereiro - Março - Abril - Maio - Junho - Julho - Agosto - Setembro - Outubro - Novembro - Dezembro day_names: - Domingo - Segunda - Terça - Quarta - Quinta - Sexta - Sábado times: other: '%{count} vezes' one: '%{count} vez' until: 'até %{date}' days_of_week: '%{segments} %{day}' days_of_month: other: '%{segments} dias do mês' one: '%{segments} dia' days_of_year: other: '%{segments} dias do ano' one: '%{segments} dia' at_hours_of_the_day: other: às %{segments} horas one: à %{segments} hora on_minutes_of_hour: other: aos %{segments} minutos one: ao %{segments} minuto at_seconds_of_minute: other: aos %{segments} segundos one: ao %{segments} segundo on_seconds_of_minute: other: aos %{segments} segundos one: ao %{segments} segundo each_second: one: A cada segundo other: A cada %{count} segundos each_minute: one: A cada minuto other: A cada %{count} minutos each_hour: one: A cada hora other: A cada %{count} horas each_day: one: Diariamente other: A cada %{count} dias each_week: one: Semanalmente other: A cada %{count} semanas each_month: one: Mensalmente other: A cada %{count} meses each_year: one: Anualmente other: A cada %{count} anos 'on': no %{sentence} in: 'em %{target}' integer: negative: '%{ordinal} depois que acabar' literal_ordinals: -1: último -2: penúltimo ordinal: '%{number}%{ordinal}' ordinals: default: º 1: º 2: º 3: º 11: º 12: º 13: º on_weekends: nos finais de semana on_weekdays: nos dias úteis days_on: - Domingos - Segundas-feiras - Terças-feiras - Quartas-feiras - Quintas-feiras - Sextas-feiras - Sábados on_days: no dia %{days} array: last_word_connector: ' e ' two_words_connector: ' e ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: abbr_day_names: - Dom - Seg - Ter - Qua - Qui - Sex - Sáb abbr_month_names: - - Jan - Fev - Mar - Abr - Mai - Jun - Jul - Ago - Set - Out - Nov - Dez day_names: - Domingo - Segunda-feira - Terça-feira - Quarta-feira - Quinta-feira - Sexta-feira - Sábado formats: default: "%d/%m/%Y" long: "%d de %B de %Y" short: "%d de %B" month_names: - - Janeiro - Fevereiro - Março - Abril - Maio - Junho - Julho - Agosto - Setembro - Outubro - Novembro - Dezembro order: - :day - :month - :year time: am: '' formats: default: "%a, %d de %B de %Y, %H:%M:%S %z" long: "%d de %B de %Y, %H:%M" short: "%d de %B, %H:%M" pm: '' ice_cube-0.16.4/config/locales/ru.yml000066400000000000000000000122221413434150700174010ustar00rootroot00000000000000ru: ice_cube: pieces_connector: ' / ' not: 'не %{target}' not_on: 'не в %{target}' date: formats: default: '%-d %B %Y' month_names: - - январь - февраль - март - апрель - май - июнь - июль - август - сентябрь - октябрь - ноябрь - декабрь day_names: - воскресенье - понедельник - вторник - среда - четверг - пятница - суббота times: other: '%{count} раза' many: '%{count} раз' few: '%{count} раза' one: '%{count} раз' until: 'до %{date}' days_of_week: '%{segments} %{day}' days_of_month: other: '%{segments} день месяца' many: '%{segments} день месяца' few: '%{segments} день месяца' one: '%{segments} день месяца' days_of_year: other: '%{segments} день года' many: '%{segments} день года' few: '%{segments} день года' one: '%{segments} день года' at_hours_of_the_day: other: в %{segments} часы дня many: в %{segments} часы дня few: в %{segments} часы дня one: в %{segments} час дня on_minutes_of_hour: other: в %{segments} минуты часа many: в %{segments} минуты часа few: в %{segments} минуты часа one: в %{segments} минуту часа on_seconds_of_minute: other: на %{segments} секунде минуты many: на %{segments} секунде минуты few: на %{segments} секунде минуты one: на %{segments} секунде минуты each_second: one: Ежесекундно few: Каждые %{count} секунды many: Каждые %{count} секунд other: Каждые %{count} секунды each_minute: one: Ежеминутно few: Каждые %{count} минуты many: Каждые %{count} минут other: Каждые %{count} минуты each_hour: one: Каждый час few: Каждые %{count} часа many: Каждые %{count} часов other: Каждые %{count} часа each_day: one: Ежедневно few: Каждые %{count} дня many: Каждые %{count} дней other: Каждые %{count} дня each_week: one: Еженедельно few: Каждые %{count} недели many: Каждые %{count} недель other: Каждые %{count} недели each_month: one: Ежемесячно few: Каждые %{count} месяца many: Каждые %{count} месяцев other: Каждые %{count} месяца each_year: one: Ежегодно few: Каждые %{count} года many: Каждые %{count} лет other: Каждые %{count} года 'on': в %{sentence} in: 'через %{target}' integer: negative: '%{ordinal} с конца' literal_ordinals: -1: последний -2: предпоследний ordinal: '%{number}%{ordinal}' ordinals: default: '' on_weekends: на выходных on_weekdays: по будням days_on: - воскресеньям - понедельникам - вторникам - средам - четвергам - пятницам - субботам on_days: по %{days} array: last_word_connector: ' и ' two_words_connector: ' и ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: abbr_day_names: - Вс - Пн - Вт - Ср - Чт - Пт - Сб abbr_month_names: - - Янв. - Февр. - Марта - Апр. - Мая - Июня - Июля - Авг. - Сент. - Окт. - Нояб. - Дек. day_names: - Воскресенье - Понедельник - Вторник - Среда - Четверг - Пятница - Суббота formats: default: "%d.%m.%Y" short: "%d %b" long: "%d %B %Y" month_names: - - Января - Февраля - Марта - Апреля - Мая - Июня - Июля - Августа - Сентября - Октября - Ноября - Декабря order: - :day - :month - :year time: am: утра formats: default: "%a, %d %b %Y, %H:%M:%S %z" short: "%d %b, %H:%M" long: "%d %B %Y, %H:%M" pm: вечера ice_cube-0.16.4/config/locales/sv.yml000066400000000000000000000065731413434150700174170ustar00rootroot00000000000000sv: ice_cube: pieces_connector: ' / ' not: 'inte %{target}' not_on: 'inte i %{target}' date: formats: default: '%-d %B %Y' month_names: - - januari - februari - mars - april - maj - juni - juli - augusti - september - oktober - november - december day_names: - söndag - måndag - tisdag - onsdag - torsdag - fredag - lördag times: other: '%{count} gånger' one: '%{count} gång' until: 'tills %{date}' days_of_week: '%{segments} %{day}' days_of_month: other: '%{segments} dagen i månaden' one: '%{segments} dagen i månaden' days_of_year: other: '%{segments} dagen på året' one: '%{segments} dagen på året' at_hours_of_the_day: other: i %{segments} timme på dygnet one: i %{segments} timme på dygnet on_minutes_of_hour: other: i %{segments} minuter av timmen one: i %{segments} minuten av timmen on_seconds_of_minute: other: på %{segments} sekunden av minuten one: på %{segments} sekunden av minuten each_second: one: Varje sekund other: Varje %{count} sekund each_minute: one: Varje minut other: Varje %{count} minut each_hour: one: Varje timme other: Varje %{count} timme each_day: one: Varje dag other: Varje %{count} dag each_week: one: Varje vecka other: Varje %{count} vecka each_month: one: Varje månad other: Varje %{count} månad each_year: one: Varje år other: Varje %{count} år 'on': i %{sentence} in: 'efter %{target}' integer: negative: '%{ordinal} från slutet' literal_ordinals: -1: sista -2: nästsista ordinal: '%{number}%{ordinal}' ordinals: default: '' on_weekends: på helger on_weekdays: på vardagar days_on: - söndagar - måndagar - tisdagar - onsdagar - torsdagar - fredagar - lördagar on_days: på %{days} array: last_word_connector: ' och ' two_words_connector: ' och ' words_connector: ', ' string: format: day: '%{rest} %{current}' day_of_week: '%{rest} %{current}' day_of_month: '%{rest} %{current}' day_of_year: '%{rest} %{current}' hour_of_day: '%{rest} %{current}' minute_of_hour: '%{rest} %{current}' until: '%{rest} %{current}' count: '%{rest} %{current}' default: '%{rest} %{current}' date: abbr_day_names: - Sön - Mån - Tis - Ons - Tor - Fre - Lör abbr_month_names: - - Jan - Feb - Mar - Apr - Maj - Jun - Jul - Aug - Sep - Okt - Nov - Dec day_names: - Söndag - Måndag - Tisdag - Onsdag - Torsdag - Fredag - Lördag formats: default: "%Y-%m-%d" long: "%e %B %Y" short: "%e %b" month_names: - - Januari - Februari - Mars - April - Maj - Juni - Juli - Augusti - September - Oktober - November - December order: - :day - :month - :year time: am: '' formats: default: "%a, %e %b %Y %H:%M:%S %z" long: "%e %B %Y %H:%M" short: "%e %b %H:%M" pm: '' ice_cube-0.16.4/ice_cube.gemspec000066400000000000000000000015271413434150700164520ustar00rootroot00000000000000# encoding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require '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', 'config/**/*.yml'] s.test_files = Dir.glob('spec/*.rb') s.require_paths = ['lib'] s.add_development_dependency('rake') s.add_development_dependency('rspec', '> 3') end ice_cube-0.16.4/lib/000077500000000000000000000000001413434150700141105ustar00rootroot00000000000000ice_cube-0.16.4/lib/ice_cube.rb000066400000000000000000000064701413434150700162020ustar00rootroot00000000000000require '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 :I18n, 'ice_cube/i18n' 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 :IcalParser, 'ice_cube/parsers/ical_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 :FixedValue, 'ice_cube/validations/fixed_value' 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 IceCube::I18n.t("ice_cube.date.formats.default") 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 ||= IceCube::VERSION.scan(/\d+/)[0..1].join.to_i end def self.compatibility=(version) @compatibility = version end end ice_cube-0.16.4/lib/ice_cube/000077500000000000000000000000001413434150700156465ustar00rootroot00000000000000ice_cube-0.16.4/lib/ice_cube/builders/000077500000000000000000000000001413434150700174575ustar00rootroot00000000000000ice_cube-0.16.4/lib/ice_cube/builders/hash_builder.rb000066400000000000000000000005721413434150700224410ustar00rootroot00000000000000module 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.16.4/lib/ice_cube/builders/ical_builder.rb000066400000000000000000000024521413434150700224250ustar00rootroot00000000000000module 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 IceCube::I18n.l(time, format: '%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? ":#{IceCube::I18n.l(time, format: '%Y%m%dT%H%M%SZ')}" # utc time else ";TZID=#{IceCube::I18n.l(time, format: '%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.16.4/lib/ice_cube/builders/string_builder.rb000066400000000000000000000037331413434150700230260ustar00rootroot00000000000000module IceCube class StringBuilder attr_writer :base def initialize @types = {} end def piece(type, prefix = nil, suffix = nil) @types[type] ||= [] end def to_s string = @base || '' @types.each do |type, segments| if f = self.class.formatter(type) current = f.call(segments) else next if segments.empty? current = self.class.sentence(segments) end f = IceCube::I18n.t('ice_cube.string.format')[type] ? type : 'default' string = IceCube::I18n.t("ice_cube.string.format.#{f}", rest: string, current: current) end string end def self.formatter(type) @formatters[type] end def self.register_formatter(type, &formatter) @formatters ||= {} @formatters[type] = formatter end module Helpers # influenced by ActiveSupport's to_sentence def sentence(array) case array.length when 0 ; '' when 1 ; array[0].to_s when 2 ; "#{array[0]}#{IceCube::I18n.t('ice_cube.array.two_words_connector')}#{array[1]}" else ; "#{array[0...-1].join(IceCube::I18n.t('ice_cube.array.words_connector'))}#{IceCube::I18n.t('ice_cube.array.last_word_connector')}#{array[-1]}" end end def nice_number(number) literal_ordinal(number) || ordinalize(number) end def ordinalize(number) IceCube::I18n.t('ice_cube.integer.ordinal', number: number, ordinal: ordinal(number)) end def literal_ordinal(number) IceCube::I18n.t("ice_cube.integer.literal_ordinals")[number] end def ordinal(number) ord = IceCube::I18n.t("ice_cube.integer.ordinals")[number] || IceCube::I18n.t("ice_cube.integer.ordinals")[number % 10] || IceCube::I18n.t('ice_cube.integer.ordinals')[:default] number >= 0 ? ord : IceCube::I18n.t("ice_cube.integer.negative", ordinal: ord) end end extend Helpers end end ice_cube-0.16.4/lib/ice_cube/deprecated.rb000066400000000000000000000024731413434150700203010ustar00rootroot00000000000000module 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 (use #{replacement})", 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 (use #{replacement})", 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 " \ "(use a block: `{|s| s.start_time = override }`)", caller[0] schedule.start_time = options[:start_date_override] end end end end ice_cube-0.16.4/lib/ice_cube/errors/000077500000000000000000000000001413434150700171625ustar00rootroot00000000000000ice_cube-0.16.4/lib/ice_cube/errors/count_exceeded.rb000066400000000000000000000001671413434150700224710ustar00rootroot00000000000000module IceCube # An exception for when a count on a Rule is passed class CountExceeded < StopIteration end end ice_cube-0.16.4/lib/ice_cube/errors/until_exceeded.rb000066400000000000000000000001751413434150700224730ustar00rootroot00000000000000module IceCube # An exception for when an until date on a Rule is passed class UntilExceeded < StopIteration end end ice_cube-0.16.4/lib/ice_cube/flexible_hash.rb000066400000000000000000000013701413434150700207710ustar00rootroot00000000000000require '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.16.4/lib/ice_cube/i18n.rb000066400000000000000000000006751413434150700167620ustar00rootroot00000000000000require 'ice_cube/null_i18n' module IceCube module I18n LOCALES_PATH = File.expand_path(File.join('..', '..', '..', 'config', 'locales'), __FILE__) class << self delegate :t, :l, to: :backend end def self.backend @backend ||= detect_backend! end def self.detect_backend! ::I18n.load_path += Dir[File.join(LOCALES_PATH, '*.yml')] ::I18n rescue NameError NullI18n end end end ice_cube-0.16.4/lib/ice_cube/input_alignment.rb000066400000000000000000000050711413434150700213730ustar00rootroot00000000000000module IceCube class InputAlignment def initialize(rule, value, rule_part) @rule = rule @value = value @rule_part = rule_part end attr_reader :rule, :value, :rule_part def verify(freq, options={}, &block) @rule.validations[:interval] or return case @rule when DailyRule verify_wday_alignment(freq, &block) when MonthlyRule verify_month_alignment(freq, &block) else verify_freq_alignment(freq, &block) end end private def interval_validation @interval_validation ||= @rule.validations[:interval].first end def interval_value @interval_value ||= (rule_part == :interval) ? value : interval_validation.interval end def fixed_validations @fixed_validations ||= @rule.validations.values.flatten.select { |v| interval_type = (v.type == :wday ? :day : v.type) v.class < Validations::FixedValue && interval_type == rule.base_interval_validation.type } end def verify_freq_alignment(freq) interval_validation.type == freq or return (last_validation = fixed_validations.min_by(&:value)) or return alignment = (value - last_validation.value) % interval_validation.interval return if alignment.zero? validation_values = fixed_validations.map(&:value).join(', ') if rule_part == :interval message = "interval(#{value}) " \ "must be a multiple of " \ "intervals in #{last_validation.key}(#{validation_values})" else message = "intervals in #{last_validation.key}(#{validation_values}, #{value}) " \ "must be multiples of " \ "interval(#{interval_validation.interval})" end yield ArgumentError.new(message) end def verify_month_alignment(_freq) return if interval_value == 1 || (interval_value % 12).zero? return if fixed_validations.empty? message = "month_of_year can only be used with interval(1) or multiples of interval(12)" yield ArgumentError.new(message) end def verify_wday_alignment(freq) return if interval_value == 1 if freq == :wday return if (interval_value % 7).zero? return if Array(@rule.validations[:day]).empty? message = "day can only be used with multiples of interval(7)" else (fixed_validation = fixed_validations.first) or return message = "#{fixed_validation.key} can only be used with interval(1)" end yield ArgumentError.new(message) end end end ice_cube-0.16.4/lib/ice_cube/null_i18n.rb000066400000000000000000000016461413434150700200130ustar00rootroot00000000000000require 'yaml' module IceCube module NullI18n def self.t(key, options = {}) base = key.to_s.split('.').reduce(config) { |hash, current_key| hash[current_key] } base = base[options[:count] == 1 ? "one" : "other"] if options[:count] case base when Hash base.each_with_object({}) do |(k, v), hash| hash[k.is_a?(String) ? k.to_sym : k] = v end when Array base.each_with_index.each_with_object({}) do |(v, k), hash| hash[k] = v end else return base unless base.include?('%{') base % options end end def self.l(date_or_time, options = {}) return date_or_time.strftime(options[:format]) if options[:format] date_or_time.strftime(t('ice_cube.date.formats.default')) end def self.config @config ||= YAML.load_file(File.join(IceCube::I18n::LOCALES_PATH, 'en.yml'))['en'] end end end ice_cube-0.16.4/lib/ice_cube/occurrence.rb000066400000000000000000000051351413434150700203270ustar00rootroot00000000000000require '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 include Comparable # Report class name as 'Time' to thwart type checking. def self.name 'Time' end attr_reader :start_time, :end_time alias first start_time alias last end_time def initialize(start_time, end_time=nil) @start_time = start_time @end_time = end_time || start_time __setobj__ @start_time end def to_i @start_time.to_i end def <=>(other) @start_time <=> other end def is_a?(klass) klass == ::Time || super end alias_method :kind_of?, :is_a? def intersects?(other) return cover?(other) unless other.is_a?(Occurrence) || other.is_a?(Range) this_start = first + 1 this_end = last # exclude end boundary other_start = other.first + 1 other_end = other.last + 1 !(this_end < other_start || this_start > other_end) end def cover?(other) to_range.cover?(other) end alias_method :include?, :cover? 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.16.4/lib/ice_cube/parsers/000077500000000000000000000000001413434150700173255ustar00rootroot00000000000000ice_cube-0.16.4/lib/ice_cube/parsers/hash_parser.rb000066400000000000000000000045361413434150700221610ustar00rootroot00000000000000module 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| rrule = h.is_a?(IceCube::Rule) ? h : IceCube::Rule.from_hash(h) schedule.rrule(rrule) 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| rrule = h.is_a?(IceCube::Rule) ? h : IceCube::Rule.from_hash(h) schedule.exrule(rrule) 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.16.4/lib/ice_cube/parsers/ical_parser.rb000066400000000000000000000057141413434150700221450ustar00rootroot00000000000000module IceCube class IcalParser def self.schedule_from_ical(ical_string, options = {}) data = {} ical_string.each_line do |line| (property, value) = line.split(':') (property, _tzid) = property.split(';') case property when 'DTSTART' data[:start_time] = TimeUtil.deserialize_time(value) when 'DTEND' data[:end_time] = TimeUtil.deserialize_time(value) when 'RDATE' data[:rtimes] ||= [] data[:rtimes] += value.split(',').map { |v| TimeUtil.deserialize_time(v) } when 'EXDATE' data[:extimes] ||= [] data[:extimes] += value.split(',').map { |v| TimeUtil.deserialize_time(v) } when 'DURATION' data[:duration] # FIXME when 'RRULE' data[:rrules] ||= [] data[:rrules] += [rule_from_ical(value)] end end Schedule.from_hash data end def self.rule_from_ical(ical) raise ArgumentError, 'empty ical rule' if ical.nil? validations = {} params = {validations: validations, interval: 1} ical.split(';').each do |rule| (name, value) = rule.split('=') raise ArgumentError, "Invalid iCal rule component" if value.nil? value.strip! case name when 'FREQ' params[:rule_type] = "IceCube::#{value[0]}#{value.downcase[1..-1]}Rule" when 'INTERVAL' params[:interval] = value.to_i when 'COUNT' params[:count] = value.to_i when 'UNTIL' params[:until] = TimeUtil.deserialize_time(value).utc when 'WKST' params[:week_start] = TimeUtil.ical_day_to_symbol(value) when 'BYSECOND' validations[:second_of_minute] = value.split(',').map(&:to_i) when 'BYMINUTE' validations[:minute_of_hour] = value.split(',').map(&:to_i) when 'BYHOUR' validations[:hour_of_day] = value.split(',').map(&:to_i) when 'BYDAY' dows = {} days = [] value.split(',').each do |expr| day = TimeUtil.ical_day_to_symbol(expr.strip[-2..-1]) if expr.strip.length > 2 # day with occurence occ = expr[0..-3].to_i dows[day].nil? ? dows[day] = [occ] : dows[day].push(occ) days.delete(TimeUtil.sym_to_wday(day)) else days.push TimeUtil.sym_to_wday(day) if dows[day].nil? end end validations[:day_of_week] = dows unless dows.empty? validations[:day] = days unless days.empty? when 'BYMONTHDAY' validations[:day_of_month] = value.split(',').map(&:to_i) when 'BYMONTH' validations[:month_of_year] = value.split(',').map(&:to_i) when 'BYYEARDAY' validations[:day_of_year] = value.split(',').map(&:to_i) when 'BYSETPOS' else validations[name] = nil # invalid type end end Rule.from_hash(params) end end end ice_cube-0.16.4/lib/ice_cube/parsers/yaml_parser.rb000066400000000000000000000006461413434150700221760ustar00rootroot00000000000000require '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.16.4/lib/ice_cube/rule.rb000066400000000000000000000062121413434150700171430ustar00rootroot00000000000000require 'yaml' module IceCube class Rule INTERVAL_TYPES = [ :secondly, :minutely, :hourly, :daily, :weekly, :monthly, :yearly ] attr_reader :uses def reset end # Is this a terminating schedule? def terminating? until_time || occurrence_count end def ==(other) return false unless other.is_a? Rule hash == other.hash end def hash to_hash.hash end def to_ical raise MethodNotImplemented, "Expected to be overridden by subclasses" end # Convert from ical string and create a rule def self.from_ical(ical) IceCube::IcalParser.rule_from_ical(ical) 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 def next_time(time, schedule, closing_time) end def on?(time, schedule) next_time(time, schedule, time).to_i == time.to_i end class << self # Convert from a hash and create a rule def from_hash(original_hash) hash = IceCube::FlexibleHash.new original_hash unless hash[:rule_type] && match = hash[:rule_type].match(/\:\:(.+?)Rule/) raise ArgumentError, 'Invalid rule type' end interval_type = match[1].downcase.to_sym unless INTERVAL_TYPES.include?(interval_type) raise ArgumentError, "Invalid rule frequency type: #{match[1]}" end rule = IceCube::Rule.send(interval_type, hash[:interval] || 1) if match[1] == "Weekly" rule.interval(hash[:interval] || 1, TimeUtil.wday_to_sym(hash[:week_start] || 0)) end rule.until(TimeUtil.deserialize_time(hash[:until])) if hash[:until] rule.count(hash[:count]) if hash[:count] hash[:validations] && hash[:validations].each do |name, args| apply_validation(rule, name, args) end rule end private def apply_validation(rule, name, args) name = name.to_sym unless ValidatedRule::VALIDATION_ORDER.include?(name) raise ArgumentError, "Invalid rule validation type: #{name}" end args.is_a?(Array) ? rule.send(name, *args) : rule.send(name, args) end 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.16.4/lib/ice_cube/rules/000077500000000000000000000000001413434150700170005ustar00rootroot00000000000000ice_cube-0.16.4/lib/ice_cube/rules/daily_rule.rb000066400000000000000000000010211413434150700214500ustar00rootroot00000000000000module IceCube class DailyRule < ValidatedRule include Validations::HourOfDay include Validations::MinuteOfHour include Validations::SecondOfMinute include Validations::DayOfMonth include Validations::DayOfWeek include Validations::Day include Validations::MonthOfYear # include Validations::DayOfYear # n/a include Validations::DailyInterval def initialize(interval = 1) super interval(interval) schedule_lock(:hour, :min, :sec) reset end end end ice_cube-0.16.4/lib/ice_cube/rules/hourly_rule.rb000066400000000000000000000010011413434150700216660ustar00rootroot00000000000000module IceCube class HourlyRule < ValidatedRule 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::HourlyInterval def initialize(interval = 1) super interval(interval) schedule_lock(:min, :sec) reset end end end ice_cube-0.16.4/lib/ice_cube/rules/minutely_rule.rb000066400000000000000000000007771413434150700222350ustar00rootroot00000000000000module IceCube class MinutelyRule < ValidatedRule 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::MinutelyInterval def initialize(interval = 1) super interval(interval) schedule_lock(:sec) reset end end end ice_cube-0.16.4/lib/ice_cube/rules/monthly_rule.rb000066400000000000000000000010331413434150700220430ustar00rootroot00000000000000module IceCube class MonthlyRule < ValidatedRule include Validations::HourOfDay include Validations::MinuteOfHour include Validations::SecondOfMinute include Validations::DayOfMonth include Validations::DayOfWeek include Validations::Day include Validations::MonthOfYear # include Validations::DayOfYear # n/a include Validations::MonthlyInterval def initialize(interval = 1) super interval(interval) schedule_lock(:day, :hour, :min, :sec) reset end end end ice_cube-0.16.4/lib/ice_cube/rules/secondly_rule.rb000066400000000000000000000007451413434150700222020ustar00rootroot00000000000000module IceCube class SecondlyRule < ValidatedRule 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::SecondlyInterval def initialize(interval = 1) super interval(interval) reset end end end ice_cube-0.16.4/lib/ice_cube/rules/weekly_rule.rb000066400000000000000000000042471413434150700216630ustar00rootroot00000000000000module IceCube class WeeklyRule < ValidatedRule include Validations::HourOfDay include Validations::MinuteOfHour include Validations::SecondOfMinute # include Validations::DayOfMonth # n/a include Validations::DayOfWeek include Validations::Day include Validations::MonthOfYear # include Validations::DayOfYear # n/a include Validations::WeeklyInterval attr_reader :week_start def initialize(interval = 1, week_start = :sunday) super(interval) interval(interval, week_start) schedule_lock(:wday, :hour, :min, :sec) reset end # Move the effective start time to correct for when the schedule has # validations earlier in the week than the selected start time, e.g. # # Schedule.new(wednesday).weekly(2).day(:monday) # # The effective start time gets realigned to the second next Monday, jumping # over the gap week for the interval (2). Without realignment, the correct # Monday occurrence would be missed when the schedule performs a 7-day jump # into the next interval week, arriving on the Wednesday. This corrects any # selections from dates that are misaligned to the schedule interval. # def realign(step_time, start_time) time = TimeUtil::TimeWrapper.new(start_time) offset = wday_offset(step_time, start_time) time.add(:day, offset) super step_time, time.to_time end # Calculate how many days to the first wday validation in the correct # interval week. This may move backwards within the week if starting in an # interval week with earlier validations. # def wday_offset(step_time, start_time) return 0 if step_time == start_time wday_validations = other_interval_validations.select { |v| v.type == :wday } return 0 if wday_validations.none? days = step_time.to_date - start_time.to_date interval = base_interval_validation.validate(step_time, start_time).to_i min_wday = wday_validations.map { |v| TimeUtil.normalize_wday(v.day, week_start) }.min step_wday = TimeUtil.normalize_wday(step_time.wday, week_start) days + interval - step_wday + min_wday end end end ice_cube-0.16.4/lib/ice_cube/rules/yearly_rule.rb000066400000000000000000000010261413434150700216600ustar00rootroot00000000000000module IceCube class YearlyRule < ValidatedRule 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::YearlyInterval def initialize(interval = 1) super interval(interval) schedule_lock(:month, :day, :hour, :min, :sec) reset end end end ice_cube-0.16.4/lib/ice_cube/schedule.rb000066400000000000000000000412131413434150700177700ustar00rootroot00000000000000require '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 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 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) return if rule.nil? @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) return if rule.nil? @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, options = {}) from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time) enumerate_occurrences(from + 1, nil, options).take(num) end # The next occurrence after now (overridable) def next_occurrence(from = nil, options = {}) from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time) enumerate_occurrences(from + 1, nil, options).next rescue StopIteration nil end # The previous occurrence from a given time def previous_occurrence(from) from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{from.inspect}" 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) from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{from.inspect}" 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, options = {}) require_terminating_rules from ||= TimeUtil.now(@start_time) enumerate_occurrences(from, nil, options).to_a end # Returns an enumerator for all remaining occurrences def remaining_occurrences_enumerator(from = nil, options = {}) from ||= TimeUtil.now(@start_time) enumerate_occurrences(from, nil, options) end # Occurrences between two times def occurrences_between(begin_time, closing_time, options = {}) enumerate_occurrences(begin_time, closing_time, options).to_a end # Return a boolean indicating if an occurrence falls between two times def occurs_between?(begin_time, closing_time, options = {}) enumerate_occurrences(begin_time, closing_time, options).next true rescue StopIteration false 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) occurs_between?(opening_time, closing_time, :spans => true) 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) time = TimeUtil.match_zone(time, start_time) or raise ArgumentError, "Time required, got #{time.inspect}" 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| IceCube::I18n.l(t, format: IceCube.to_s_time_format) } pieces.concat rrules.map { |t| t.to_s } pieces.concat exrules.map { |t| IceCube::I18n.t('ice_cube.not', target: t.to_s) } pieces.concat extimes.sort.map { |t| target = IceCube::I18n.l(t, format: IceCube.to_s_time_format) IceCube::I18n.t('ice_cube.not_on', target: target) } pieces.join(IceCube::I18n.t('ice_cube.pieces_connector')) 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 # Load the schedule from ical def self.from_ical(ical, options = {}) IcalParser.schedule_from_ical(ical, options) end # Hook for YAML.dump, enables to_yaml def encode_with(coder) coder.represent_object nil, to_hash 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 alias_method :to_h, :to_hash # 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? @all_recurrence_rules.all?(&:terminating?) end def hash [ TimeUtil.hash(start_time), duration, *@all_recurrence_rules.map(&:hash).sort!, *@all_exception_rules.map(&:hash).sort! ].hash end def eql?(other) self.hash == other.hash end alias == eql? 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, options = {}) opening_time = TimeUtil.match_zone(opening_time, start_time) closing_time = TimeUtil.match_zone(closing_time, start_time) opening_time += TimeUtil.subsec(start_time) - TimeUtil.subsec(opening_time) opening_time = start_time if opening_time < start_time spans = options[:spans] == true && duration != 0 Enumerator.new do |yielder| reset t1 = full_required? ? start_time : opening_time t1 -= duration if spans t1 = start_time if t1 < start_time loop do break unless (t0 = next_time(t1, closing_time)) break if closing_time && t0 > closing_time if (spans ? (t0.end_time > opening_time) : (t0 >= opening_time)) yielder << (block_given? ? yield(t0) : t0) end t1 = t0 + 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 |best_time, rule| begin new_time = rule.next_time(time, start_time, best_time || closing_time) [best_time, new_time].compact.min rescue StopIteration best_time end end break 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, start_time) 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 end end ice_cube-0.16.4/lib/ice_cube/single_occurrence_rule.rb000066400000000000000000000007131413434150700227140ustar00rootroot00000000000000module 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, _, closing_time) unless closing_time && closing_time < t time if time.to_i >= t.to_i end end def to_hash { :time => time } end def full_required? false end end end ice_cube-0.16.4/lib/ice_cube/time_util.rb000066400000000000000000000260171413434150700201740ustar00rootroot00000000000000require 'date' require 'time' module IceCube module TimeUtil extend Deprecated DAYS = { :sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6 } ICAL_DAYS = { 'SU' => :sunday, 'MO' => :monday, 'TU' => :tuesday, 'WE' => :wednesday, 'TH' => :thursday, 'FR' => :friday, 'SA' => :saturday } 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.build_in_zone(args, reference) if reference.respond_to?(:time_zone) reference.time_zone.local(*args) elsif reference.utc? Time.utc(*args) elsif reference.zone Time.local(*args) else Time.new(*args << reference.utc_offset) end end def self.match_zone(input_time, reference) return unless time = ensure_time(input_time, reference) time = if reference.respond_to? :time_zone time.in_time_zone(reference.time_zone) else if reference.utc? time.getgm elsif reference.zone time.getlocal else time.getlocal(reference.utc_offset) end end (Date === input_time) ? beginning_of_date(time, reference) : time end # Ensure that this is either nil, or a time def self.ensure_time(time, reference = nil, 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 if date_eod end_of_date(time, reference) else if reference build_in_zone([time.year, time.month, time.day], reference) else time.to_time end end 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) case time when Time, Date if time.respond_to?(:time_zone) {:time => time.utc, :zone => time.time_zone.name} else time end when DateTime Time.local(time.year, time.month, time.day, time.hour, time.min, time.sec) else raise ArgumentError, "cannot serialize #{time.inspect}, expected a 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, Date time_or_hash when DateTime Time.local(time.year, time.month, time.day, time.hour, time.min, time.sec) when Hash hash = FlexibleHash.new(time_or_hash) hash[:time].in_time_zone(hash[:zone]) when String Time.parse(time_or_hash) end end # Get a more precise equality for time objects # Ruby provides a Time#hash method, but it fails to account for UTC # offset (so the current date may be different) or DST rules (so the # hour may be wrong for different schedule occurrences) def self.hash(time) [time, time.utc_offset, time.zone].hash 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=Time.now) build_in_zone([date.year, date.month, date.day, 0, 0, 0], reference) end # Get the end of a date def self.end_of_date(date, reference=Time.now) build_in_zone([date.year, date.month, date.day, 23, 59, 59], reference) end # Convert a symbol to a numeric month def self.sym_to_month(sym) MONTHS.fetch(sym) do |k| MONTHS.values.detect { |i| i.to_s == k.to_s } or raise ArgumentError, "Expecting Integer 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| DAYS.values.detect { |i| i.to_s == k.to_s } or raise ArgumentError, "Expecting Integer 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 wday if DAYS.keys.include? wday DAYS.invert.fetch(wday) do |i| raise ArgumentError, "Expecting Integer 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 def self.ical_day_to_symbol(str) day = ICAL_DAYS[str] raise ArgumentError, "Invalid day: #{str}" if day.nil? day end # 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 # Handle discrepancies between various time types # - Time has subsec # - DateTime does not # - ActiveSupport::TimeWithZone can wrap either type, depending on version # or if `parse` or `now`/`local` was used to build it. def self.subsec(time) if time.respond_to?(:subsec) time.subsec elsif time.respond_to?(:sec_fraction) time.sec_fraction else 0.0 end end # A utility class for safely moving time around class TimeWrapper def initialize(time, dst_adjust = true) @dst_adjust = dst_adjust @base = time if dst_adjust @time = Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + TimeUtil.subsec(time)) else @time = time end end # Get the wrapped time back in its original zone & format def to_time return @time unless @dst_adjust parts = @time.year, @time.month, @time.day, @time.hour, @time.min, @time.sec + @time.subsec TimeUtil.build_in_zone(parts, @base) end # DST-safely add an interval of time to the wrapped time def add(type, val) type = :day if type == :wday @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 # 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 send :"clear_#{ptype}" end end def hour=(value) @time += (value * ONE_HOUR) - (@time.hour * ONE_HOUR) end def min=(value) @time += (value * ONE_MINUTE) - (@time.min * ONE_MINUTE) end def sec=(value) @time += (value) - (@time.sec) 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 end end end ice_cube-0.16.4/lib/ice_cube/validated_rule.rb000066400000000000000000000114151413434150700211610ustar00rootroot00000000000000require 'ice_cube/input_alignment' module IceCube class ValidatedRule < Rule include Validations::ScheduleLock 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 ] attr_reader :validations def initialize(interval = 1) @validations = Hash.new end # Reset the uses on the rule to 0 def reset @time = nil @start_time = nil @uses = 0 end def base_interval_validation @validations[:interval].first end def other_interval_validations Array(@validations[base_interval_validation.type]) end # Compute the next time after (or including) the specified time in respect # to the given start time def next_time(time, start_time, closing_time) @time = time unless @start_time @start_time = realign(time, start_time) @time = @start_time if @time < @start_time end return nil unless find_acceptable_time_before(closing_time) @uses += 1 if @time @time end def realign(opening_time, start_time) start_time end def full_required? !occurrence_count.nil? end def to_s builder = StringBuilder.new @validations.each_value do |validations| validations.each do |validation| validation.build_s(builder) end end builder.to_s end def to_hash builder = HashBuilder.new(self) @validations.each_value do |validations| validations.each do |validation| validation.build_hash(builder) end end builder.to_hash end def to_ical builder = IcalBuilder.new @validations.each_value do |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 a postive 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 # Returns true if all validations for the current rule match # otherwise false and shifts to the first (largest) unmatched offset # def validation_accepts_or_updates_time?(validations_for_type) res = validations_for_type.each_with_object([]) do |validation, offsets| r = validation.validate(@time, @start_time) return true if r.nil? || r == 0 offsets << r end shift_time_by_validation(res, validations_for_type.first) false end def shift_time_by_validation(res, validation) return unless (interval = res.min) 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 def verify_alignment(value, freq, rule_part) InputAlignment.new(self, value, rule_part).verify(freq) do |error| yield error end end end end ice_cube-0.16.4/lib/ice_cube/validations/000077500000000000000000000000001413434150700201635ustar00rootroot00000000000000ice_cube-0.16.4/lib/ice_cube/validations/count.rb000066400000000000000000000022341413434150700216410ustar00rootroot00000000000000module IceCube module Validations::Count # Value reader for limit def occurrence_count (arr = @validations[:count]) && (val = arr[0]) && val.count end def count(max) unless max.nil? || max.is_a?(Integer) raise ArgumentError, "Expecting Integer or nil value for count, got #{max.inspect}" end 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, start_time) 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 IceCube::I18n.t('ice_cube.times', count: count) end end end end ice_cube-0.16.4/lib/ice_cube/validations/daily_interval.rb000066400000000000000000000024351413434150700235220ustar00rootroot00000000000000module IceCube module Validations::DailyInterval # Add a new interval validation def interval(interval) interval = normalized_interval(interval) verify_alignment(interval, :wday, :interval) { |error| raise error } verify_alignment(interval, :day, :interval) { |error| raise error } @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, start_time) t0, t1 = 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 = IceCube::I18n.t('ice_cube.each_day', count: interval) 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.16.4/lib/ice_cube/validations/day.rb000066400000000000000000000035471413434150700212760ustar00rootroot00000000000000module IceCube module Validations::Day def day(*days) days = days.flatten return self if days.empty? days.flatten.each do |day| unless day.is_a?(Integer) || day.is_a?(Symbol) raise ArgumentError, "expecting Integer or Symbol value for day, got #{day.inspect}" end day = TimeUtil.sym_to_wday(day) verify_alignment(day, :wday, :day) { |error| raise error } validations_for(:day) << Validation.new(day) end clobber_base_validations(:wday, :day) self end class Validation < Validations::FixedValue attr_reader :day alias :value :day def initialize(day) @day = day end def key :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] IceCube::I18n.t('ice_cube.on_weekends') elsif validation_days == (1..5).to_a IceCube::I18n.t('ice_cube.on_weekdays') else day_names = ->(d){ "#{IceCube::I18n.t("ice_cube.days_on")[d]}" } segments = validation_days.map(&day_names) IceCube::I18n.t('ice_cube.on_days', days: StringBuilder.sentence(segments)) end end end end end ice_cube-0.16.4/lib/ice_cube/validations/day_of_month.rb000066400000000000000000000024511413434150700231600ustar00rootroot00000000000000module IceCube module Validations::DayOfMonth def day_of_month(*days) days.flatten.each do |day| unless day.is_a?(Integer) raise ArgumentError, "expecting Integer value for day, got #{day.inspect}" end verify_alignment(day, :day, :day_of_month) { |error| raise error } validations_for(:day_of_month) << Validation.new(day) end clobber_base_validations(:day, :wday) self end class Validation < Validations::FixedValue attr_reader :day alias :value :day def initialize(day) @day = day end def key :day_of_month 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| sentence = StringBuilder.sentence(entries) str = IceCube::I18n.t('ice_cube.days_of_month', count: entries.size, segments: sentence) IceCube::I18n.t('ice_cube.on', sentence: str) end end end end ice_cube-0.16.4/lib/ice_cube/validations/day_of_week.rb000066400000000000000000000036631413434150700227740ustar00rootroot00000000000000module 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, start_time) 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) << IceCube::I18n.t( 'ice_cube.days_of_week', segments: StringBuilder.nice_number(occ), day: IceCube::I18n.t('date.day_names')[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| sentence = segments.join(IceCube::I18n.t('ice_cube.array.two_words_connector')) IceCube::I18n.t('ice_cube.on', sentence: sentence) end end end end ice_cube-0.16.4/lib/ice_cube/validations/day_of_year.rb000066400000000000000000000025751413434150700230020ustar00rootroot00000000000000module IceCube module Validations::DayOfYear def day_of_year(*days) days.flatten.each do |day| unless day.is_a?(Integer) raise ArgumentError, "expecting Integer 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, start_time) days_in_year = TimeUtil.days_in_year(step_time) yday = day < 0 ? day + days_in_year + 1 : 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 = StringBuilder.sentence(entries) sentence = IceCube::I18n.t('ice_cube.days_of_year', count: entries.size, segments: str) IceCube::I18n.t('ice_cube.on', sentence: sentence) end end end end ice_cube-0.16.4/lib/ice_cube/validations/fixed_value.rb000066400000000000000000000055571413434150700230170ustar00rootroot00000000000000module IceCube # This abstract validation class 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 module 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 # class Validations::FixedValue INTERVALS = {:min => 60, :sec => 60, :hour => 24, :month => 12, :wday => 7} def validate(time, start_time) case type when :day then validate_day_lock(time, start_time) when :hour then validate_hour_lock(time, start_time) else validate_interval_lock(time, start_time) 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, start_time) t0 = starting_unit(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, start_time) h0 = starting_unit(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, start_time) 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(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 = start % INTERVALS[type] if start < 0 start end end end ice_cube-0.16.4/lib/ice_cube/validations/hour_of_day.rb000066400000000000000000000034511413434150700230110ustar00rootroot00000000000000module IceCube module Validations::HourOfDay # Add hour of day validations def hour_of_day(*hours) hours.flatten.each do |hour| unless hour.is_a?(Integer) raise ArgumentError, "expecting Integer value for hour, got #{hour.inspect}" end verify_alignment(hour, :hour, :hour_of_day) { |error| raise error } validations_for(:hour_of_day) << Validation.new(hour) end clobber_base_validations(:hour) self end def realign(opening_time, start_time) return super unless validations[:hour_of_day] freq = base_interval_validation.interval first_hour = Array(validations[:hour_of_day]).min_by(&:value) time = TimeUtil::TimeWrapper.new(start_time, false) if freq > 1 && base_interval_validation.type == :hour offset = first_hour.validate(opening_time, start_time) time.add(:hour, offset - freq) else time.hour = first_hour.value end super opening_time, time.to_time end class Validation < Validations::FixedValue attr_reader :hour alias :value :hour def initialize(hour) @hour = hour end def key :hour_of_day 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 = StringBuilder.sentence(segments) IceCube::I18n.t('ice_cube.at_hours_of_the_day', count: segments.size, segments: str) end end end end ice_cube-0.16.4/lib/ice_cube/validations/hourly_interval.rb000066400000000000000000000022351413434150700237400ustar00rootroot00000000000000module IceCube module Validations::HourlyInterval def interval(interval) verify_alignment(interval, :hour, :interval) { |error| raise error } @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, start_time) t0, t1 = 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 = IceCube::I18n.t("ice_cube.each_hour", count: interval) 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.16.4/lib/ice_cube/validations/lock.rb000066400000000000000000000055261413434150700214500ustar00rootroot00000000000000module 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, start_time) case type when :day then validate_day_lock(time, start_time) when :hour then validate_hour_lock(time, start_time) else validate_interval_lock(time, start_time) 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, start_time) t0 = starting_unit(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, start_time) h0 = starting_unit(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, start_time) 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(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.16.4/lib/ice_cube/validations/minute_of_hour.rb000066400000000000000000000031271413434150700235350ustar00rootroot00000000000000module IceCube module Validations::MinuteOfHour def minute_of_hour(*minutes) minutes.flatten.each do |minute| unless minute.is_a?(Integer) raise ArgumentError, "expecting Integer value for minute, got #{minute.inspect}" end verify_alignment(minute, :min, :minute_of_hour) { |error| raise error } validations_for(:minute_of_hour) << Validation.new(minute) end clobber_base_validations(:min) self end def realign(opening_time, start_time) return super unless validations[:minute_of_hour] first_minute = validations[:minute_of_hour].min_by(&:value) time = TimeUtil::TimeWrapper.new(start_time, false) time.min = first_minute.value super opening_time, time.to_time end class Validation < Validations::FixedValue attr_reader :minute alias :value :minute def initialize(minute) @minute = minute end def key :minute_of_hour 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 = StringBuilder.sentence(segments) IceCube::I18n.t('ice_cube.on_minutes_of_hour', count: segments.size, segments: str) end end end end ice_cube-0.16.4/lib/ice_cube/validations/minutely_interval.rb000066400000000000000000000022521413434150700242630ustar00rootroot00000000000000module IceCube module Validations::MinutelyInterval def interval(interval) verify_alignment(interval, :min, :interval) { |error| raise error } @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, start_time) t0, t1 = 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 = IceCube::I18n.t('ice_cube.each_minute', count: interval) 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.16.4/lib/ice_cube/validations/month_of_year.rb000066400000000000000000000024541413434150700233460ustar00rootroot00000000000000module IceCube module Validations::MonthOfYear def month_of_year(*months) months.flatten.each do |month| unless month.is_a?(Integer) || month.is_a?(Symbol) raise ArgumentError, "expecting Integer or Symbol value for month, got #{month.inspect}" end month = TimeUtil.sym_to_month(month) verify_alignment(month, :month, :month_of_year) { |error| raise error } validations_for(:month_of_year) << Validation.new(month) end clobber_base_validations :month self end class Validation < Validations::FixedValue attr_reader :month alias :value :month def initialize(month) @month = month end def key :month_of_year end def type :month end def dst_adjust? true end def build_s(builder) builder.piece(:month_of_year) << IceCube::I18n.t("date.month_names")[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| IceCube::I18n.t("ice_cube.in", target: StringBuilder.sentence(segments)) end end end end ice_cube-0.16.4/lib/ice_cube/validations/monthly_interval.rb000066400000000000000000000022371413434150700241120ustar00rootroot00000000000000module IceCube module Validations::MonthlyInterval def interval(interval) interval = normalized_interval(interval) verify_alignment(interval, :month, :interval) { |error| raise error } @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, start_time) t0, t1 = 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 = IceCube::I18n.t('ice_cube.each_month', count: interval) 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.16.4/lib/ice_cube/validations/schedule_lock.rb000066400000000000000000000015371413434150700233220ustar00rootroot00000000000000module 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 < Validations::FixedValue attr_reader :type, :value def initialize(type) @type = type end def key :base 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.16.4/lib/ice_cube/validations/second_of_minute.rb000066400000000000000000000031631413434150700240330ustar00rootroot00000000000000module IceCube module Validations::SecondOfMinute def second_of_minute(*seconds) seconds.flatten.each do |second| unless second.is_a?(Integer) raise ArgumentError, "Expecting Integer value for second, got #{second.inspect}" end verify_alignment(second, :sec, :second_of_minute) { |error| raise error } validations_for(:second_of_minute) << Validation.new(second) end clobber_base_validations :sec self end def realign(opening_time, start_time) return super unless validations[:second_of_minute] first_second = Array(validations[:second_of_minute]).min_by(&:value) time = TimeUtil::TimeWrapper.new(start_time, false) time.sec = first_second.value super opening_time, time.to_time end class Validation < Validations::FixedValue attr_reader :second alias :value :second def initialize(second) @second = second end def key :second_of_minute 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 = StringBuilder.sentence(segments) IceCube::I18n.t('ice_cube.on_seconds_of_minute', count: segments.size, segments: str) end end end end ice_cube-0.16.4/lib/ice_cube/validations/secondly_interval.rb000066400000000000000000000020751413434150700242400ustar00rootroot00000000000000module IceCube module Validations::SecondlyInterval def interval(interval) verify_alignment(interval, :sec, :interval) { |error| raise error } @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, start_time) seconds = step_time.to_i - start_time.to_i offset = (seconds % interval).nonzero? interval - offset if offset end def build_s(builder) builder.base = IceCube::I18n.t("ice_cube.each_second", count: interval) 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.16.4/lib/ice_cube/validations/until.rb000066400000000000000000000021471413434150700216470ustar00rootroot00000000000000module IceCube module Validations::Until extend Deprecated # Value reader for limit def until_time (arr = @validations[:until]) && (val = arr[0]) && val.time end deprecated_alias :until_date, :until_time def 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, start_time) end_time = TimeUtil.ensure_time(time, start_time, true) raise UntilExceeded if step_time > end_time end def build_s(builder) date = IceCube::I18n.l(time, format: IceCube.to_s_time_format) builder.piece(:until) << IceCube::I18n.t('ice_cube.until', date: date) 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.16.4/lib/ice_cube/validations/weekly_interval.rb000066400000000000000000000030411413434150700237120ustar00rootroot00000000000000module 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 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, start_time) return if step_time < start_time t0, t1 = 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.to_i / 7) % interval).nonzero? (interval - offset) * 7 if offset end def build_s(builder) builder.base = IceCube::I18n.t('ice_cube.each_week', count: interval) 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.16.4/lib/ice_cube/validations/yearly_interval.rb000066400000000000000000000017771413434150700237350ustar00rootroot00000000000000module 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, start_time) years = step_time.year - start_time.year offset = (years % interval).nonzero? interval - offset if offset end def build_s(builder) builder.base = IceCube::I18n.t('ice_cube.each_year', count: interval) 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.16.4/lib/ice_cube/version.rb000066400000000000000000000000521413434150700176550ustar00rootroot00000000000000module IceCube VERSION = '0.16.4' end ice_cube-0.16.4/spec/000077500000000000000000000000001413434150700142745ustar00rootroot00000000000000ice_cube-0.16.4/spec/data/000077500000000000000000000000001413434150700152055ustar00rootroot00000000000000ice_cube-0.16.4/spec/data/issue40.yml000066400000000000000000000004741413434150700172310ustar00rootroot00000000000000--- :start_time: 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 :rdates: [] :exdates: [] :duration: 3600 :end_time: !!null ice_cube-0.16.4/spec/examples/000077500000000000000000000000001413434150700161125ustar00rootroot00000000000000ice_cube-0.16.4/spec/examples/_no_active_support_spec.rb000066400000000000000000000025301413434150700233530ustar00rootroot00000000000000require 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 do before do raise 'ActiveSupport should not be loaded' if defined?(ActiveSuppport) 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) expect(TimeUtil.end_of_date(date).to_s).to eq(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) midnight = TimeUtil.beginning_of_date(date) expect(midnight).to eq(Time.local(2011, 1, 1, 0, 0, 0)) expect(midnight).to be_a Time 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.16.4/spec/examples/active_support_spec.rb000066400000000000000000000164341413434150700225300ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' require 'active_support/time' require 'active_support/version' require 'tzinfo' if ActiveSupport::VERSION::MAJOR == 3 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 expect(schedule.all_occurrences).to eq([t0]) end it 'works with a monthly recurrence rule starting from a TimeWithZone' do schedule = Schedule.new(Time.zone.parse("2010-02-05 05:00:00")) schedule.add_recurrence_rule Rule.monthly expect(schedule.first(10)).to eq([ 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(Time.zone.parse("2009-10-28 19:30:00")) schedule.add_recurrence_rule Rule.monthly expect(schedule.first(7).map { |d| d.getutc }).to eq([ 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) expect(schedule.all_occurrences).to eq(schedule2.all_occurrences) end it 'uses local zone from start time to determine occurs_on? from the beginning of day' do schedule = Schedule.new(Time.local(2009, 2, 7, 23, 59, 59)) schedule.add_recurrence_rule Rule.daily expect(schedule.occurs_on?(Date.new(2009, 2, 7))).to be_truthy end it 'uses local zone from start time to determine occurs_on? to the end of day' do schedule = Schedule.new(Time.local(2009, 2, 7, 0, 0, 0)) schedule.add_recurrence_rule Rule.daily expect(schedule.occurs_on?(Date.new(2009, 2, 7))).to be_truthy end it 'should use the correct zone for next_occurrences before start_time' do future_time = Time.zone.now.beginning_of_day + IceCube::ONE_DAY schedule = Schedule.new(future_time) schedule.add_recurrence_rule Rule.daily expect(schedule.next_occurrence.time_zone).to eq(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 expect(schedule.next_occurrence.time_zone).to eq(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) expect(next_occurrence).to eq(Time.utc(2013, 1, 2)) expect(next_occurrence.time_zone).to eq(schedule.start_time.time_zone) end it 'uses schedule zone for next_occurrences' do next_occurrences = schedule.next_occurrences(2, reference_time) expect(next_occurrences).to eq([Time.utc(2013, 1, 2), Time.utc(2013, 1, 3)]) next_occurrences.each do |t| expect(t.time_zone).to eq(schedule.start_time.time_zone) end end it 'uses schedule zone for remaining_occurrences' do remaining_occurrences = schedule.remaining_occurrences(reference_time + IceCube::ONE_DAY) expect(remaining_occurrences).to eq([Time.utc(2013, 1, 2), Time.utc(2013, 1, 3)]) remaining_occurrences.each do |t| expect(t.time_zone).to eq(schedule.start_time.time_zone) end end it 'uses schedule zone for occurrences' do occurrences = schedule.occurrences(reference_time + IceCube::ONE_DAY) expect(occurrences).to eq([Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)]) occurrences.each do |t| expect(t.time_zone).to eq(schedule.start_time.time_zone) end end it 'uses schedule zone for occurrences_between' do occurrences_between = schedule.occurrences_between(reference_time, reference_time + IceCube::ONE_DAY) expect(occurrences_between).to eq([Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)]) occurrences_between.each do |t| expect(t.time_zone).to eq(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') schedule = Schedule.new(utc) { |s| s.add_recurrence_rule Rule.daily.count(3) } occurrences_between = schedule.occurrences_between(reference_time, reference_time + IceCube::ONE_DAY) expect(occurrences_between).to eq([Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)]) occurrences_between.each do |t| expect(t.time_zone).to eq(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') schedule = Schedule.new(utc) { |s| s.add_recurrence_rule Rule.daily.until(utc.advance(:days => 3)) } occurrences_between = schedule.occurrences_between(reference_time, reference_time + IceCube::ONE_DAY) expect(occurrences_between).to eq([Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)]) occurrences_between.each do |t| expect(t.time_zone).to eq(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') schedule = Schedule.new(utc) { |s| s.add_recurrence_rule Rule.daily } occurrences_between = schedule.occurrences_between(reference_time, reference_time + IceCube::ONE_DAY) expect(occurrences_between).to eq([Time.utc(2013, 1, 1), Time.utc(2013, 1, 2)]) occurrences_between.each do |t| expect(t.time_zone).to eq(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 expect(difference).to eq(60) end end ice_cube-0.16.4/spec/examples/daily_rule_spec.rb000066400000000000000000000124031413434150700216020ustar00rootroot00000000000000require 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") expect(rule.validations_for(:interval).first.interval).to eq(2) end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.daily("3") expect(rule.validations_for(:interval).first.interval).to eq(3) end it 'raises an argument error when a bad value is passed using the interval method' do expect { Rule.daily.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end it 'raises an argument error when a bad value is passed' do expect { Rule.daily("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end end describe DailyRule do describe 'in Vancouver time', :system_time_zone => 'America/Vancouver' do it 'should include nearest time in DST start hour' do schedule = Schedule.new(Time.local(2013, 3, 9, 2, 30, 0)) schedule.add_recurrence_rule Rule.daily expect(schedule.first(3)).to eq([ 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(Time.local(2013, 11, 2, 2, 30, 0)) schedule.add_recurrence_rule Rule.daily expect(schedule.first(3)).to eq([ 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(Time.local(2013, 3, 9, 2, 0, 0)) schedule.add_recurrence_rule Rule.daily.hour_of_day(2) expect(schedule.first(3)).to eq([ 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 t0 = Time.now rule = Rule.daily(7) rule.interval(5) expect(rule.next_time(t0 + 1, t0, nil)).to eq(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) expect(times.size).to eq(3) expect(times).to eq([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) expect(times.size).to eq(3) expect(times).to eq([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) expect(times.size).to eq(3) expect(times).to eq([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) expect(times).to eq([ t0 + 5 * ONE_HOUR + 45 * ONE_MINUTE, t0 + 4 * ONE_DAY + 5 * ONE_HOUR + 45 * ONE_MINUTE ]) end describe "day validation" do it "allows multiples of 7" do expect { IceCube::Rule.daily(21).day(2, 4) }.to_not raise_error end it "raises errors for misaligned interval and day (wday) values" do expect { IceCube::Rule.daily(2).day(2, 4) }.to raise_error(ArgumentError, "day can only be used with multiples of interval(7)") end it "raises errors for misaligned hour_of_day values when changing interval" do expect { IceCube::Rule.daily.day(3, 6).interval(5) }.to raise_error(ArgumentError, "day can only be used with multiples of interval(7)") end end describe "day_of_month validation" do it "raises errors for misaligned interval and day_of_month values" do expect { IceCube::Rule.daily(2).day_of_month(2, 4) }.to raise_error(ArgumentError, "day_of_month can only be used with interval(1)") end it "raises errors for misaligned day_of_month values when changing interval" do expect { IceCube::Rule.daily.day_of_month(3, 6).interval(5) }.to raise_error(ArgumentError, "day_of_month can only be used with interval(1)") end end end end ice_cube-0.16.4/spec/examples/dst_spec.rb000066400000000000000000000325061413434150700202510ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe Schedule 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_time = Time.local(2010, 3, 13, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily.count(20) dates = schedule.first(20) expect(dates.size).to eq(20) # check assumptions dates.each do |date| expect(date.utc?).not_to eq(true) expect(date.hour).to eq(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_time = Time.local(2010, 11, 6, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily.count(20) dates = schedule.first(20) expect(dates.size).to eq(20) # check assumptions dates.each do |date| expect(date.utc?).not_to eq(true) expect(date.hour).to eq(5) end end it 'cross a daylight savings time boundary with a recurrence rule in local time' do start_time = Time.local(2010, 3, 14, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily # each occurrence MUST occur at 5pm, then we win dates = schedule.occurrences(start_time + 20 * ONE_DAY) dates.each do |date| expect(date.hour).to eq(5) end end it 'every two hours over a daylight savings time boundary, checking interval' do start_time = Time.local(2010, 11, 6, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.hourly(2) dates = schedule.first(100) # check assumption distance_in_hours = 0 dates.each do |d| expect(d).to eq(start_time + 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_time = Time.local(2010, 11, 6, 23, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.minutely(30) dates = schedule.first(100) # check assumption distance_in_minutes = 0 dates.each do |d| expect(d).to eq(start_time + 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_time = Time.local(2010, 11, 6, 23, 50, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.secondly(120) dates = schedule.first(10) # check assumption distance_in_seconds = 0 dates.each do |d| expect(d).to eq(start_time + 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_time = Time.local(2010, 11, 6, 20, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily(2) dates = schedule.first(10) # check assumption dates.each do |d| expect(d.hour).to eq(start_time.hour) expect(d.min).to eq(start_time.min) expect(d.sec).to eq(start_time.sec) end end it 'every other month over a daylight savings time boundary, checking day/hour/min/sec' do start_time = Time.local(2010, 11, 6, 20, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.monthly(2) dates = schedule.first(10) # check assumption dates.each do |d| expect(d.day).to eq(start_time.day) expect(d.hour).to eq(start_time.hour) expect(d.min).to eq(start_time.min) expect(d.sec).to eq(start_time.sec) end end it 'every other year over a daylight savings time boundary, checking day/hour/min/sec' do start_time = Time.local(2010, 11, 6, 20, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.yearly(2) dates = schedule.first(10) # check assumption dates.each do |d| expect(d.month).to eq(start_time.month) expect(d.day).to eq(start_time.day) expect(d.hour).to eq(start_time.hour) expect(d.min).to eq(start_time.min) expect(d.sec).to eq(start_time.sec) end end it 'LOCAL - has an until date on a rule that is over a DST from the start date' do start_time = Time.local(2010, 3, 13, 5, 0, 0) end_date = Time.local(2010, 3, 15, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily.until(end_date) # make sure we end on the proper time expect(schedule.all_occurrences.last).to eq(end_date) end it 'UTC - has an until date on a rule that is over a DST from the start date' do start_time = Time.utc(2010, 3, 13, 5, 0, 0) end_date = Time.utc(2010, 3, 15, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily.until(end_date) # make sure we end on the proper time expect(schedule.all_occurrences.last).to eq(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_time = Time.local(2010, 11, 5, 5, 0, 0) end_date = Time.local(2010, 11, 10, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily.until(end_date) # make sure we end on the proper time expect(schedule.all_occurrences.last).to eq(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_time = Time.utc(2010, 11, 5, 5, 0, 0) end_date = Time.utc(2010, 11, 10, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily.until(end_date) # make sure we end on the proper time expect(schedule.all_occurrences.last).to eq(end_date) end it 'LOCAL - has an end date on a rule that is over a DST from the start date' do start_time = Time.local(2010, 3, 13, 5, 0, 0) end_date = Time.local(2010, 3, 15, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily # make sure we end on the proper time expect(schedule.occurrences(end_date).last).to eq(end_date) end it 'UTC - has an end date on a rule that is over a DST from the start date' do start_time = Time.utc(2010, 3, 13, 5, 0, 0) end_date = Time.utc(2010, 3, 15, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily # make sure we end on the proper time expect(schedule.occurrences(end_date).last).to eq(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_time = Time.local(2010, 11, 5, 5, 0, 0) end_date = Time.local(2010, 11, 10, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily # make sure we end on the proper time expect(schedule.occurrences(end_date).last).to eq(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_time = Time.utc(2010, 11, 5, 5, 0, 0) end_date = Time.utc(2010, 11, 10, 5, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily # make sure we end on the proper time expect(schedule.occurrences(end_date).last).to eq(end_date) end it 'local - should make dates on interval over dst - github issue 4' do start_time = Time.local(2010, 3, 12, 19, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.daily(3) expect(schedule.first(3)).to eq([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_time = Time.local(2010, 3, 12, 19, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.monthly(2) expect(schedule.first(6)).to eq([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_time = Time.local(2010, 3, 12, 19, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.monthly expect(schedule.first(10)).to eq([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_time = Time.local(2010, 3, 12, 19, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.yearly(2) expect(schedule.first(3)).to eq([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_time = Time.local(2010, 3, 7, 12, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.monthly.day_of_week(:sunday => [1]) expect(schedule.first(3)).to eq([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_time = Time.local(2010, 3, 1, 12, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.monthly.day_of_month(1) expect(schedule.first(3)).to eq([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_time = Time.local(2010, 3, 7, 12, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.weekly.day(:sunday) expect(schedule.first(3)).to eq([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 yearly (day of year) inverval over dst - github issue 5" do start_time = Time.local(2010, 3, 7, 12, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.yearly.day_of_year(1) expect(schedule.first(3)).to eq([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_time = Time.local(2010, 3, 7, 12, 0, 0) schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.yearly.month_of_year(:april).day_of_month(10) expect(schedule.first(3)).to eq([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 daily occurrences from end of DST", :system_time_zone => "America/Denver" do t0 = Time.local(2013, 11, 3, 1, 30, 0) schedule = Schedule.new(t0) { |s| s.rrule Rule.daily.count(3) } expect(schedule.all_occurrences).to eq([t0, Time.local(2013, 11, 4, 1, 30, 0), Time.local(2013, 11, 5, 1, 30, 0)]) end it "skips double monthly occurrences from end of DST", :system_time_zone => "America/Nome" do t0 = Time.local(2017, 10, 5, 1, 0, 0) schedule = Schedule.new(t0) { |s| s.rrule Rule.monthly.day_of_month(5).count(3) } expect(schedule.all_occurrences).to eq([t0, t0 + 31*ONE_DAY + ONE_HOUR, t0 + 61*ONE_DAY + ONE_HOUR]) end it "does not skip hourly rules over DST", :system_time_zone => "America/Denver" do t0 = Time.local(2013, 11, 3, 1, 30, 0) schedule = Schedule.new(t0) { |s| s.rrule Rule.hourly.count(3) } expect(schedule.all_occurrences).to eq([t0, t0 + ONE_HOUR, t0 + 2*ONE_HOUR]) end it "does not skip minutely rules with minute of hour over DST", :system_time_zone => "America/Denver" do t0 = Time.local(2013, 11, 3, 1, 30, 0) schedule = Schedule.new(t0) { |s| s.rrule Rule.hourly.count(3) } schedule.rrule Rule.minutely.minute_of_hour([0, 15, 30, 45]) expect(schedule.first(5)).to eq([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", :system_time_zone => "America/Denver" do t0 = Time.local(2013, 11, 3, 1, 30, 0) schedule = Schedule.new(t0) { |s| s.rrule Rule.hourly.count(3) } schedule.rrule Rule.minutely(15).second_of_minute(0) expect(schedule.first(5)).to eq([t0, t0 + 15*60, t0 + 30*60, t0 + 45*60, t0 + 60*60]) end end end ice_cube-0.16.4/spec/examples/flexible_hash_spec.rb000066400000000000000000000033311413434150700222460ustar00rootroot00000000000000require 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 expect(hash[:sym]).to be true end specify "'sym' => :sym is found" do expect(hash["sym"]).to be true end specify "'str' => 'str' is found" do expect(hash["str"]).to be true end specify ":str => 'str' is found" do expect(hash[:str]).to be true end specify "other types are found" do expect(hash[1]).to be true end specify "missing keys are nil" do expect(hash[-1]).to be nil end end describe "#fetch" do it "yields missing keys" do expect(hash.fetch(-1) { |k| k == -1 }).to be true end end describe "#delete" do specify ":sym => :sym is found and removed" do expect(hash.delete(:sym)).to be true expect(hash[:sym]).to be nil end specify "'sym' => :sym is found and removed" do expect(hash.delete("sym")).to be true expect(hash["sym"]).to be nil end specify "'str' => 'str' is found and removed" do expect(hash.delete("str")).to be true expect(hash["str"]).to be nil end specify ":str => 'str' is found and removed" do expect(hash.delete(:str)).to be true expect(hash[:str]).to be nil end specify "other types are found and removed" do expect(hash.delete(1)).to be true expect(hash[1]).to be nil end specify "missing keys are nil" do expect(hash.delete(-1)).to be nil end end end end ice_cube-0.16.4/spec/examples/from_ical_spec.rb000066400000000000000000000355161413434150700214160ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' require 'active_support/time' module IceCube describe Rule, 'from_ical' do it 'should return a IceCube DailyRule class for a basic daily rule' do rule = IceCube::Rule.from_ical "FREQ=DAILY" expect(rule.class).to eq(IceCube::DailyRule) end it 'should return a IceCube WeeklyRule class for a basic monthly rule' do rule = IceCube::Rule.from_ical "FREQ=WEEKLY" expect(rule.class).to eq(IceCube::WeeklyRule) end it 'should return a IceCube MonthlyRule class for a basic monthly rule' do rule = IceCube::Rule.from_ical "FREQ=MONTHLY" expect(rule.class).to eq(IceCube::MonthlyRule) end it 'should return a IceCube YearlyRule class for a basic yearly rule' do rule = IceCube::Rule.from_ical "FREQ=YEARLY" expect(rule.class).to eq(IceCube::YearlyRule) end it 'should be able to parse a .day rule' do rule = IceCube::Rule.from_ical("FREQ=DAILY;BYDAY=MO,TU") expect(rule).to eq(IceCube::Rule.daily.day(:monday, :tuesday)) end it 'should be able to parse a .day_of_week rule' do rule = IceCube::Rule.from_ical("FREQ=DAILY;BYDAY=-1TU,-2TU") expect(rule).to eq(IceCube::Rule.daily.day_of_week(:tuesday => [-1, -2])) end it 'should be able to parse both .day and .day_of_week rules' do rule = IceCube::Rule.from_ical("FREQ=DAILY;BYDAY=MO,-1TU,-2TU") expect(rule).to eq(IceCube::Rule.daily.day_of_week(:tuesday => [-1, -2]).day(:monday)) end it 'should be able to parse a .day_of_month rule' do rule = IceCube::Rule.from_ical("FREQ=DAILY;BYMONTHDAY=23") expect(rule).to eq(IceCube::Rule.daily.day_of_month(23)) end it 'should be able to parse a .day_of_year rule' do rule = IceCube::Rule.from_ical("FREQ=YEARLY;BYYEARDAY=100,200") expect(rule).to eq(IceCube::Rule.yearly.day_of_year(100,200)) end it 'should be able to serialize a .month_of_year rule' do rule = IceCube::Rule.from_ical("FREQ=DAILY;BYMONTH=1,4") expect(rule).to eq(IceCube::Rule.daily.month_of_year(:january, :april)) end it 'should be able to split to a combination of day_of_week and day (day_of_week has priority)' do rule = IceCube::Rule.from_ical("FREQ=DAILY;BYDAY=TU,MO,1MO,-1MO") expect(rule).to eq(IceCube::Rule.daily.day(:tuesday).day_of_week(:monday => [1, -1])) end it 'should be able to parse of .day_of_week rule with multiple days' do rule = IceCube::Rule.from_ical("FREQ=DAILY;BYDAY=WE,1MO,-1MO,2TU") expect(rule).to eq(IceCube::Rule.daily.day_of_week(:monday => [1, -1], :tuesday => [2]).day(:wednesday)) end it 'should be able to parse a rule with an until date' do t = Time.now.utc rule = IceCube::Rule.from_ical("FREQ=WEEKLY;UNTIL=#{t.strftime("%Y%m%dT%H%M%SZ")}") expect(rule.to_s).to eq(IceCube::Rule.weekly.until(t).to_s) end it 'should be able to parse a rule with a count date' do rule = IceCube::Rule.from_ical("FREQ=WEEKLY;COUNT=5") expect(rule).to eq(IceCube::Rule.weekly.count(5)) end it 'should be able to parse a rule with an interval' do rule = IceCube::Rule.from_ical("FREQ=DAILY;INTERVAL=2") expect(rule).to eq(IceCube::Rule.daily.interval(2)) end it 'should be able to parse week start (WKST)' do rule = IceCube::Rule.from_ical("FREQ=WEEKLY;INTERVAL=2;WKST=MO") expect(rule).to eq(IceCube::Rule.weekly(2, :monday)) end it 'should return no occurrences after daily interval with count is over' do schedule = IceCube::Schedule.new(Time.now) schedule.add_recurrence_rule(IceCube::Rule.from_ical("FREQ=DAILY;COUNT=5")) expect(schedule.occurrences_between(Time.now + (IceCube::ONE_DAY * 7), Time.now + (IceCube::ONE_DAY * 14)).count).to eq(0) end end describe Schedule, 'from_ical' do ical_string = <<-ICAL.gsub(/^\s*/, '') DTSTART:20130314T201500Z DTEND:20130314T201545Z RRULE:FREQ=WEEKLY;BYDAY=TH;UNTIL=20130531T100000Z ICAL ical_string_with_multiple_exdates_and_rdates = <<-ICAL.gsub(/^\s*/, '') DTSTART;TZID=America/Denver:20130731T143000 DTEND;TZID=America/Denver:20130731T153000 RRULE:FREQ=WEEKLY;UNTIL=20140730T203000Z;BYDAY=MO,WE,FR EXDATE;TZID=America/Denver:20130823T143000 EXDATE;TZID=America/Denver:20130812T143000 EXDATE;TZID=America/Denver:20130807T143000 RDATE;TZID=America/Denver:20150812T143000 RDATE;TZID=America/Denver:20150807T143000 ICAL ical_string_with_multiple_rules = <<-ICAL.gsub(/^\s*/, '' ) DTSTART;TZID=CDT:20151005T195541 RRULE:FREQ=WEEKLY;BYDAY=MO,TU RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU;BYDAY=FR ICAL def sorted_ical(ical) ical.split(/\n/).sort.map { |field| k, v = field.split(':') v = v.split(';').sort.join(';') if k == 'RRULE' "#{ k }:#{ v }" }.join("\n") end describe "instantiation" do it "loads an ICAL string" do expect(IceCube::Schedule.from_ical(ical_string)).to be_a(IceCube::Schedule) end end describe "daily frequency" do it 'matches simple daily' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.daily) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles counts' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.daily.count(4)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.daily(4)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals and counts' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.daily(4).count(10)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles until dates' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.daily.until(start_time + (IceCube::ONE_DAY * 15))) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end end describe 'weekly frequency' do it 'matches simple weekly' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.weekly) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles weekdays' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.weekly.day(:monday, :thursday)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.weekly(2)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals and counts' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.weekly(2).count(4)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals and counts on given weekdays' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.weekly(2).day(:monday, :wednesday).count(4)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end end describe 'monthly frequency' do it 'matches simple monthly' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.monthly) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.monthly(2)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals and counts' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.monthly(2).count(5)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals and counts on specific days' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.monthly(2).day_of_month(1, 15).count(5)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end end describe 'yearly frequency' do it 'matches simple yearly' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.yearly) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles intervals' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.yearly(2)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles a specific day' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.yearly.day_of_year(15)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles specific days' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.yearly.day_of_year(1, 15, -1)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles counts' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.yearly.count(5)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles specific months' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.yearly.month_of_year(:january, :december)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles specific months and counts' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.yearly.month_of_year(:january, :december).count(15)) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end end describe "exceptions" do it 'handles single EXDATE lines, single RDATE lines' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule(IceCube::Rule.daily) schedule.add_exception_time(Time.now + (IceCube::ONE_DAY * 2)) schedule.add_recurrence_time(Time.now + IceCube::ONE_DAY * 4) ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end it 'handles multiple EXDATE / RDATE lines' do schedule = IceCube::Schedule.from_ical ical_string_with_multiple_exdates_and_rdates expect(schedule.exception_times.count).to eq(3) expect(schedule.recurrence_times.count).to eq(2) end it 'should raise ArgumentError when parsing an invalid rule type' do str = 'FREQ=FAKE' expect { Rule.from_ical(str) }.to raise_error(ArgumentError, 'Invalid rule frequency type: Fake') end it 'should raise ArgumentError when parsing an invalid validation type' do str = 'FREQ=DAILY;FAKE=23' expect { Rule.from_ical(str) }.to raise_error(ArgumentError, 'Invalid rule validation type: FAKE') end end describe 'multiple rules' do it 'handles multiple recurrence rules' do schedule = IceCube::Schedule.from_ical ical_string_with_multiple_rules expect(schedule.recurrence_rules.count).to eq(2) end end describe 'invalid ical data' do shared_examples_for('an invalid ical string') do it do expect { IceCube::Schedule.from_ical(ical_str) }.to raise_error(ArgumentError) # TODO replace with real ad end end describe 'empty rules' do let(:ical_str) { 'RRULE::' } it_behaves_like 'an invalid ical string' end describe 'invalid rules' do let(:ical_str) { 'RRULE::A' } it_behaves_like 'an invalid ical string' end describe 'incomplete rule' do let(:ical_str) { 'RRULE:FREQ' } it_behaves_like 'an invalid ical string' end describe 'invalid rule with invalid sensitive key' do let(:ical_str) { 'RRULE:FREQ=WEKLY;WKST=SU' } it_behaves_like 'an invalid ical string' end describe 'invalid rule with invalid value' do let(:ical_str) { 'RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;WKST=SE' } it_behaves_like 'an invalid ical string' end describe 'invalid rule with invalid key' do let(:ical_str) { 'RRULE:FREQ=WEEKLY;BDAY=MO,WE,FR;WKST=SU' } it_behaves_like 'an invalid ical string' end describe 'invalid rule with attempt to execute code' do let(:ical_str) { 'RRULE:FREQ=to_yaml' } it_behaves_like 'an invalid ical string' end end end end ice_cube-0.16.4/spec/examples/hash_parser_spec.rb000066400000000000000000000014311413434150700217470ustar00rootroot00000000000000require 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} } describe '#start_time' do subject { super().start_time } it { is_expected.to eq(t) } end describe '#duration' do subject { super().duration } it { is_expected.to eq(3600) } end describe "end_time overrules duration" do let(:hash) { {start_time: t, end_time: t + 1800, duration: 3600} } describe '#duration' do subject { super().duration } it { is_expected.to eq(1800) } end end end end end ice_cube-0.16.4/spec/examples/hourly_rule_spec.rb000066400000000000000000000073251413434150700220310ustar00rootroot00000000000000require 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") expect(rule.validations_for(:interval).first.interval).to eq(2) end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.hourly("3") expect(rule.validations_for(:interval).first.interval).to eq(3) end it 'raises an argument error when a bad value is passed' do expect { Rule.hourly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { Rule.hourly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end end context 'in Vancouver time', :system_time_zone => 'America/Vancouver' do it 'should work across DST start hour' do schedule = Schedule.new(Time.local(2013, 3, 10, 1, 0, 0)) schedule.add_recurrence_rule Rule.hourly expect(schedule.first(3)).to eq([ 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(Time.local(2013, 11, 3, 0, 0, 0)) schedule.add_recurrence_rule Rule.hourly expect(schedule.first(4)).to eq([ 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 t0 = Time.now rule = Rule.hourly(7) rule.interval(5) expect(rule.next_time(t0 + 1, t0, nil)).to eq(t0 + 5 * ONE_HOUR) end it 'should produce the correct days for @interval = 3' do start_time = DAY schedule = Schedule.new(start_time) 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) expect(dates.size).to eq(3) expect(dates).to eq([DAY, DAY + 3 * ONE_HOUR, DAY + 6 * ONE_HOUR]) end it "should realign to the first hour_of_day with interval" do t0 = Time.utc(2017, 1, 1, 20, 30, 40) schedule = IceCube::Schedule.new(t0) schedule.rrule IceCube::Rule.hourly(5).hour_of_day(5, 10) expect(schedule.first(2)).to eq [t0 + 9*ONE_HOUR, t0 + 14*ONE_HOUR] end it "should realign to the first hour_of_day without interval" do t0 = Time.utc(2017, 1, 1, 20, 30, 40) schedule = IceCube::Schedule.new(t0) schedule.rrule IceCube::Rule.hourly.hour_of_day(5, 10) expect(schedule.first(2)).to eq [t0 + 9*ONE_HOUR, t0 + 14*ONE_HOUR] end it "raises errors for misaligned interval and hour_of_day values" do expect { IceCube::Rule.hourly(10).hour_of_day(3, 6) }.to raise_error(ArgumentError, "intervals in hour_of_day(3, 6) must be multiples of interval(10)") end it "raises errors for misaligned hour_of_day values when changing interval" do expect { IceCube::Rule.hourly(3).hour_of_day(3, 6).interval(5) }.to raise_error(ArgumentError, "interval(5) must be a multiple of intervals in hour_of_day(3, 6)") end end end ice_cube-0.16.4/spec/examples/ice_cube_spec.rb000066400000000000000000000674771413434150700212340ustar00rootroot00000000000000require '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 expect { schedule.first(3) }.not_to raise_error end it 'should respond to complex combinations (1)' do start_time = Time.utc(2010, 1, 1) schedule = IceCube::Schedule.new(start_time) 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 expect(dates.size).to eq(4) dates.each do |date| expect(date.wday).to eq(3) expect(date.month).to eq(4) expect(date.year).to eq(start_time.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) expect(schedule.occurrences(t0 + 50)).to eq([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) expect(schedule.occurrences(t0 + 50)).to eq([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 expect(schedule.occurrences(DAY + 13 * IceCube::ONE_DAY).size).to eq(8) end it 'should be able to exclude a certain date from a range' do start_time = Time.local 2012, 3, 1 schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily schedule.add_exception_time(start_time + 1 * IceCube::ONE_DAY) # all days except tomorrow # check assumption dates = schedule.occurrences(start_time + 13 * IceCube::ONE_DAY) # 2 weeks expect(dates.size).to eq(13) # 2 weeks minus 1 day expect(dates).not_to include(start_time + 1 * IceCube::ONE_DAY) end it 'make a schedule with a start_time not included in a rule, and make sure that count behaves properly' do start_time = WEDNESDAY schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:thursday).count(5) dates = schedule.all_occurrences expect(dates.uniq.size).to eq(5) dates.each { |d| expect(d.wday).to eq(4) } expect(dates).not_to include(WEDNESDAY) end it 'make a schedule with a start_time included in a rule, and make sure that count behaves properly' do start_time = WEDNESDAY + IceCube::ONE_DAY schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:thursday).count(5) dates = schedule.all_occurrences expect(dates.uniq.size).to eq(5) dates.each { |d| expect(d.wday).to eq(4) } expect(dates).to include(WEDNESDAY + IceCube::ONE_DAY) end it 'should work as expected with a second_of_minute rule specified' do start_time = DAY schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.weekly.second_of_minute(30) dates = schedule.occurrences(start_time + 30 * 60) dates.each { |date| expect(date.sec).to eq(30) } end it 'ensure that when count on a rule is set to 0, 0 occurrences come back' do start_time = DAY schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.count(0) expect(schedule.all_occurrences).to eq([]) end it 'should be able to schedule at hour 1,2 with start min/sec every day' do start_time = Time.utc(2007, 9, 2, 9, 15, 25) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.hour_of_day(1, 2).count(6) dates = schedule.all_occurrences expect(dates).to eq([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 schedule at hour 1,2 at min 0 with start sec every day' do start_time = Time.utc(2007, 9, 2, 9, 15, 25) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.hour_of_day(1, 2).minute_of_hour(0).count(6) dates = schedule.all_occurrences expect(dates).to eq([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_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.count(10) dates = schedule.first(200) expect(dates.size).to eq(10) end it 'occurs yearly' do start_time = DAY schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.yearly dates = schedule.first(10) dates.each do |date| expect(date.month).to eq(start_time.month) expect(date.day).to eq(start_time.day) expect(date.hour).to eq(start_time.hour) expect(date.min).to eq(start_time.min) expect(date.sec).to eq(start_time.sec) end end it 'occurs daily' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily dates = schedule.first(10) dates.each do |date| expect(date.hour).to eq(start_time.hour) expect(date.min).to eq(start_time.min) expect(date.sec).to eq(start_time.sec) end end it 'occurs hourly' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.hourly dates = schedule.first(10) dates.each do |date| expect(date.min).to eq(start_time.min) expect(date.sec).to eq(start_time.sec) end end it 'occurs minutely' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.minutely dates = schedule.first(10) dates.each do |date| expect(date.sec).to eq(start_time.sec) end end it 'occurs every second for an hour' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.secondly.count(60) # build the expectation list expectation = [] 0.upto(59) { |i| expectation << start_time + i } # compare with what we get dates = schedule.all_occurrences expect(dates.size).to eq(60) expect(schedule.all_occurrences).to eq(expectation) end it 'perform a every day LOCAL and make sure we get back LOCAL' do Time.zone = 'Eastern Time (US & Canada)' start_time = Time.zone.local(2010, 9, 2, 5, 0, 0) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily schedule.first(10).each do |d| expect(d.utc?).to eq(false) expect(d.hour).to eq(5) expect(d.utc_offset == -5 * IceCube::ONE_HOUR || d.utc_offset == -4 * IceCube::ONE_HOUR).to be_truthy end end it 'perform a every day LOCAL and make sure we get back LOCAL' do start_time = Time.utc(2010, 9, 2, 5, 0, 0) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily schedule.first(10).each do |d| expect(d.utc?).to eq(true) expect(d.utc_offset).to eq(0) expect(d.hour).to eq(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_time = Time.zone.local(2010, 11, 6, 5, 0, 0) schedule = IceCube::Schedule.new(start_time) 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| expect(d.utc?).to eq(false) } expect(dates).to eq([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_time = Time.utc(2010, 11, 6, 5, 0, 0) Time.zone = 'Eastern Time (US & Canada)' schedule = IceCube::Schedule.new(start_time) 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| expect(d.utc?).to eq(true) } expect(dates).to eq([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 WORLD_TIME_ZONES.each do |zone| context "in #{zone}", :system_time_zone => zone do it 'works with a until date that is a Date, but the start date is UTC' do start_time = Time.utc(2016, 1, 1, 0, 0, 0) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.daily.until(Date.new(2016, 1, 2)) times = schedule.all_occurrences expect(times).to eq [ Time.utc(2016, 1, 1, 0, 0, 0), Time.utc(2016, 1, 2, 0, 0, 0) ] end end end it 'works with a monthly rule iterating on UTC' do start_time = Time.utc(2010, 4, 24, 15, 45, 0) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.monthly dates = schedule.first(10) dates.each do |d| expect(d.day).to eq(24) expect(d.hour).to eq(15) expect(d.min).to eq(45) expect(d.sec).to eq(0) expect(d.utc?).to be_truthy 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 expect(schedule.rrules).to eq(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 expect(schedule.exrules).to eq(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 expect(schedule.rtimes).to eq(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 expect(schedule.extimes).to eq(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 expect(schedule.first(10)).not_to eq(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) expect(schedule.first(10)).to eq(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) expect(schedule.first(10)).to eq(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) expect(dates).to eq([start_time + IceCube::ONE_DAY * 2, start_time + IceCube::ONE_DAY * 3, start_time + IceCube::ONE_DAY * 4]) end it 'should be able to tell us when there is at least one occurrence between two dates' do start_time = WEDNESDAY schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:friday) expect(true).to eq(schedule.occurs_between?(start_time, start_time + IceCube::ONE_DAY * 3)) end it 'should be able to tell us when there is no occurrence between two dates' do start_time = WEDNESDAY schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:friday) expect(false).to eq(schedule.occurs_between?(start_time, start_time + IceCube::ONE_DAY)) end it 'should be able to get back rtimes from a schedule' do schedule = IceCube::Schedule.new DAY schedule.add_recurrence_time DAY schedule.add_recurrence_time(DAY + 2) expect(schedule.rtimes).to eq([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) expect(schedule.extimes).to eq([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 expect(schedule.first).to eq(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 expect(schedule.all_occurrences).to eq([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 expect do expect(schedule.all_occurrences).to eq([start_time]) end.not_to raise_error end it 'should use occurs_at? when calling occurring_at? with no duration' do schedule = IceCube::Schedule.new expect(schedule).to 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 expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 30, 0))).to be_truthy #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 expect(schedule.occurring_at?(Time.local(2010, 5, 6, 9, 59, 0))).to be_falsey expect(schedule.occurring_at?(Time.local(2010, 5, 6, 11, 0, 0))).to be_falsey 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 expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 0, 0))).to be_truthy expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 59, 59))).to be_truthy 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) expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 29))).to be_truthy expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 30))).to be_falsey expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 31))).to be_truthy 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) expect(schedule.all_occurrences).to eq([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 expect(schedule.first(5)).to eq([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) expect(schedule.first(5)).to eq([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 expect(schedule2.end_time).to eq(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] expect(schedule.occurrences_between(start_time - IceCube::ONE_DAY, start_time + 4 * IceCube::ONE_DAY)).to eq(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] expect(schedule.occurrences(start_time + 4 * IceCube::ONE_DAY)).to eq(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) expect(schedule.occurs_at?(DAY + 4*IceCube::ONE_DAY)).to be_falsey # 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) expect(schedule.occurring_at?((DAY + 2*IceCube::ONE_DAY + 10))).to be_truthy # in range expect(schedule.occurring_at?((DAY + 4*IceCube::ONE_DAY + 10))).to be_falsey # 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 expect(schedule.first(3)).to eq([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 expect(schedule.first(3)).to eq([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 expect(schedule.first(3)).to eq([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 expect(schedule.first(3)).to eq([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 expect(schedule.first(3)).to eq([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) expect(schedule.first(6)).to eq([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 expect(schedule.first(2)).to eq([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)) expect(schedule.first(10)).to eq([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)) expect(schedule.first(10)).to eq([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) expect(schedule.first(10)).to eq([Time.local(2010, 2, 29)]) end it 'should have some convenient aliases' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) expect(schedule.start_time).to eq(schedule.start_time) expect(schedule.end_time).to eq(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 expect(schedule.rrules).to eq([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 expect(schedule.exrules).to eq([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) expect(schedule.rtimes).to eq([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) expect(schedule.extimes).to eq([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) expect(schedule.occurs_on?(Date.new(2010, 8, 27))).to be_falsey expect(schedule.occurs_on?(Date.new(2010, 8, 28))).to be_truthy 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 expect(s.first(3).size).to eq(3) expect(s.first(4).size).to eq(4) expect(s.first(5).size).to eq(5) end it 'should use current date as start date when invoked with a nil parameter' do schedule = IceCube::Schedule.new nil expect(Time.now - schedule.start_time).to be < 100 end it 'should be able to get the occurrence count for a rule' do rule = IceCube::Rule.daily.count(5) expect(rule.occurrence_count).to eq(5) end it 'should be able to remove a count validation from a rule' do rule = IceCube::Rule.daily.count(5) expect(rule.occurrence_count).to eq(5) rule.count(nil) expect(rule.occurrence_count).to be_nil end it 'should be able to remove a count validation from a rule' do rule = IceCube::Rule.daily.count(5) expect(rule.to_hash[:count]).to eq(5) rule.count nil expect(rule.to_hash[:count]).to 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) expect(rule.to_hash[:until]).not_to be_nil rule.until nil expect(rule.to_hash).not_to 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 expect(total).to be < 0.1 end end ice_cube-0.16.4/spec/examples/minutely_rule_spec.rb000066400000000000000000000066211413434150700223530ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::MinutelyRule do describe 'interval validation' do it 'converts a string integer to an actual int when using the interval method' do rule = Rule.minutely.interval("2") expect(rule.validations_for(:interval).first.interval).to eq(2) end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.minutely("3") expect(rule.validations_for(:interval).first.interval).to eq(3) end it 'raises an argument error when a bad value is passed' do expect { Rule.minutely("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end it 'raises an argument error when a bad value is passed when using the interval method' do expect { Rule.minutely.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end end it 'should update previous interval' do t0 = Time.now rule = Rule.minutely(7) rule.interval(5) expect(rule.next_time(t0 + 1, t0, nil)).to eq(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 expect(schedule.first(3)).to eq([ 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 expect(schedule.first(3)).to eq([ std_start - ONE_MINUTE, std_start, std_start + ONE_MINUTE ]) end it 'should produce the correct days for @interval = 3' do start_time = DAY schedule = Schedule.new(start_time) 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) expect(dates.size).to eq(3) expect(dates).to eq([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) expect(schedule.next_occurrence(Time.new(2013, 11, 1, 1, 4, 0))).to eq(Time.new(2013, 11, 1, 1, 8, 0)) end it "should realign to the first minute_of_hour" do t0 = Time.utc(2017, 1, 1, 20, 30, 40) schedule = IceCube::Schedule.new(t0) schedule.rrule IceCube::Rule.minutely(10).minute_of_hour(5, 15) expect(schedule.first(2)).to eq [t0 + 35*ONE_MINUTE, t0 + 45*ONE_MINUTE] end it "raises errors for misaligned interval and minute_of_hour values" do expect { IceCube::Rule.minutely(10).minute_of_hour(3, 6) }.to raise_error(ArgumentError, "intervals in minute_of_hour(3, 6) must be multiples of interval(10)") end it "raises errors for misaligned minute_of_hour values when changing interval" do expect { IceCube::Rule.minutely(3).minute_of_hour(3, 6).interval(5) }.to raise_error(ArgumentError, "interval(5) must be a multiple of intervals in minute_of_hour(3, 6)") end end ice_cube-0.16.4/spec/examples/monthly_rule_spec.rb000066400000000000000000000155321413434150700222000ustar00rootroot00000000000000require 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") expect(rule.validations_for(:interval).first.interval).to eq(2) end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.monthly("3") expect(rule.validations_for(:interval).first.interval).to eq(3) end it 'converts a string integer to an actual int' do rule = Rule.monthly("1") expect(rule.instance_variable_get(:@interval)).to eq(1) end it 'raises an argument error when a bad value is passed' do expect { Rule.monthly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { Rule.monthly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end end describe MonthlyRule do it 'should update previous interval' do t0 = Time.utc(2013, 5, 17) rule = Rule.monthly(3) rule.interval(1) expect(rule.next_time(t0 + 1, t0, nil)).to eq(t0 + (IceCube::ONE_DAY * 31)) 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 expect(schedule.occurrences(t0 + 50 * ONE_DAY).size).to eq(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) expect(schedule.occurrences(t0 + 50 * ONE_DAY).size).to eq(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) expect(schedule.occurrences(t0 + 50 * ONE_DAY).map(&:day)).to eq([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) expect(schedule.occurrences(t0 + 60 * ONE_DAY).map(&:day)).to eq([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) expect(schedule.occurrences(t0 + 50 * ONE_DAY).size).to eq(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) expect(schedule.occurrences(t0 + 40 * ONE_DAY).size).to eq(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) expect(schedule.occurrences(t1).size).to eq(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.new(Time.local(2011, 8, 1)) do |s| s.add_recurrence_rule Rule.monthly.day_of_week(weekday => [1]) end } it "should not skip a month when DST ends" do schedule.first(48).each_cons(2) do |t0, t1| expect(month_interval(t1, t0)).to eq(1) end end it "should not change day when DST ends" do schedule.first(48).each do |date| expect(date.wday).to eq(wday) end end it "should not change hour when DST ends" do schedule.first(48).each do |time| expect(time.hour).to eq(0) end end end end it 'should produce dates on a monthly interval for the last day of the month' do schedule = Schedule.new(Time.utc(2010, 3, 31, 0, 0, 0)) schedule.add_recurrence_rule Rule.monthly expect(schedule.first(10)).to eq([ 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(Time.utc(2010, 1, 29, 0, 0, 0)) schedule.add_recurrence_rule Rule.monthly expect(schedule.first(3)).to eq([ 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(Time.utc(2013,1,31)) schedule.add_recurrence_rule Rule.monthly.day_of_month(31) expect(schedule.first(3)).to eq([ 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 describe "month_of_year validation" do it "allows multiples of 12" do expect { IceCube::Rule.monthly(24).month_of_year(3, 6) }.to_not raise_error end it "raises errors for misaligned interval and month_of_year values" do expect { IceCube::Rule.monthly(10).month_of_year(3, 6) }.to raise_error(ArgumentError, "month_of_year can only be used with interval(1) or multiples of interval(12)") end it "raises errors for misaligned month_of_year values when changing interval" do expect { IceCube::Rule.monthly.month_of_year(3, 6).interval(5) }.to raise_error(ArgumentError, "month_of_year can only be used with interval(1) or multiples of interval(12)") end end end end ice_cube-0.16.4/spec/examples/occurrence_spec.rb000066400000000000000000000133261413434150700216060ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe Occurrence do it "reports as a Time" do occurrence = Occurrence.new(t0 = Time.now, t0 + 3600) expect(occurrence.class.name).to eq('Time') expect(occurrence.is_a?(Time)).to be_truthy expect(occurrence.kind_of?(Time)).to be_truthy end describe :to_s do it "looks like a Time for a zero duration" do start_time = Time.now occurrence = Occurrence.new(start_time) expect(occurrence.to_s).to eq(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) expect(occurrence.to_s).to eq("#{start_time} - #{end_time}") end it "accepts a format option to comply with ActiveSupport" do time_now = Time.current occurrence = Occurrence.new(time_now) expect(occurrence.to_s(:short)).to eq time_now.to_s(:short) end end describe :to_i do it "represents the start time" do start_time = Time.now occurrence = Occurrence.new(start_time) expect(occurrence.to_i).to eq start_time.to_i end end describe :<=> do it "is comparable to another occurrence's start time" do o1 = Occurrence.new(Time.now) o2 = Occurrence.new(o1.start_time + 1) expect(o1).to be < o2 end it "is comparable to another time" do occurrence = Occurrence.new(Time.now) expect(occurrence).to be < occurrence.start_time + 1 end end describe :cover? do let(:start_time) { Time.now } let(:occurrence) { Occurrence.new(start_time, start_time + ONE_HOUR) } it "is true for the start time" do expect(occurrence.cover?(start_time)).to be true end it "is true for a time in the range" do expect(occurrence.cover?(start_time + 1)).to be true end it "is true for the end time" do expect(occurrence.cover?(start_time + ONE_HOUR)).to be true end it "is false after the end time" do expect(occurrence.cover?(start_time + ONE_HOUR + 1)).to be false end it "is false before the start time" do expect(occurrence.cover?(start_time - 1)).to be false end end describe :end_time do it 'defaults to start_time' do start_time = Time.now occurrence = Occurrence.new(start_time) expect(occurrence.end_time).to eq(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) expect(occurrence.end_time).to eq(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 expect(new_time).to eq(start_time + 60) end it 'can get difference from a time' do difference = occurrence - (start_time - 60) expect(difference).to eq(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 expect(inclusion).to be_truthy end it 'is false for a time outside the occurrence' do occurrence = Occurrence.new(start_time, end_time) inclusion = occurrence.intersects? start_time + 3601 expect(inclusion).to be_falsey 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 expect(inclusion).to be_truthy 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 expect(inclusion).to be_falsey end end describe :overnight? do it 'is false for a zero-length occurrence' do occurrence = Occurrence.new(Time.local(2013, 12, 24)) expect(occurrence.overnight?).to be_falsey 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)) expect(occurrence.overnight?).to be_falsey 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) expect(occurrence.overnight?).to be_falsey 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) expect(occurrence.overnight?).to be_falsey 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) expect(occurrence.overnight?).to be_falsey 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) expect(occurrence.overnight?).to be_falsey 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) expect(occurrence.overnight?).to be_truthy 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) expect(occurrence.overnight?).to be_truthy end end end ice_cube-0.16.4/spec/examples/recur_spec.rb000066400000000000000000000153421413434150700205760ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' 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)) expect(schedule.remaining_occurrences(start_time).size).to eq(24 - schedule.start_time.hour) end it 'should get the proper remaining 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)) expect(schedule.remaining_occurrences(start_time + 366 * ONE_DAY).size).to eq(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 expect(schedule.occurring_between?(start_time, end_time)).to be_truthy end it 'should affirm a zero-length occurrence at the start of the range' do schedule.duration = 0 expect(schedule.occurring_between?(start_time, start_time)).to be_truthy end it 'should deny a zero-length occurrence at the end of the range' do schedule.duration = 0 expect(schedule.occurring_between?(end_time, end_time)).to be_falsey end it 'should affirm an occurrence entirely contained within the range' do expect(schedule.occurring_between?(start_time + 1, end_time - 1)).to be_truthy end it 'should affirm an occurrence spanning across the start of the range' do expect(schedule.occurring_between?(start_time - 1, start_time + 1)).to be_truthy end it 'should affirm an occurrence spanning across the end of the range' do expect(schedule.occurring_between?(end_time - 1, end_time + 1)).to be_truthy end it 'should affirm an occurrence spanning across the range entirely' do expect(schedule.occurring_between?(start_time - 1, end_time + 1)).to be_truthy end it 'should deny an occurrence before the range' do expect(schedule.occurring_between?(end_time + 1, end_time + 2)).to be_falsey end it 'should deny an occurrence after the range' do expect(schedule.occurring_between?(start_time - 2, start_time - 1)).to be_falsey 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) expect(schedule.next_occurrence(schedule.start_time)).to eq(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) expect(schedule.next_occurrence(schedule.end_time + 366 * ONE_DAY)).to eq(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 expect(schedule.next_occurrence(schedule.start_time)).to eq(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 expect(schedule.next_occurrence(schedule.start_time)).to eq(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 expect(schedule.next_occurrence(schedule.start_time)).to eq(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) expect(schedule.next_occurrences(3, start_time)).to eq([ 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)) expect(schedule.next_occurrences(3, schedule.end_time + 366 * ONE_DAY)).to eq([]) 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 expect(schedule.next_occurrences(3, schedule.start_time)).to eq([ 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) expect(schedule.next_occurrences(3, schedule.start_time)).to eq([ 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 expect(schedule.next_occurrences(3, schedule.start_time)).to eq([ 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 expect(schedule.next_occurrences(5)).to eq(schedule.next_occurrences(5)) end end ice_cube-0.16.4/spec/examples/regression_spec.rb000066400000000000000000000232771413434150700216440ustar00rootroot00000000000000require 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(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) expect(schedule.occurs_at?(Time.local(2011, 12, 3, 15, 0, 0))).to be_truthy end it 'should work well with occurrences_between [#33]' do schedule = Schedule.new(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) expect(schedule.occurrences_between(Time.local(2012, 1, 1), Time.local(2012, 12, 1))).to 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) expect(schedule.occurrences(Date.today >> 12)).to be_an Array end it 'should not choke on parsing [#26]' do schedule = Schedule.new(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', expect_warnings: true do file = File.read(File.dirname(__FILE__) + '/../data/issue40.yml') schedule = Schedule.from_yaml(file) expect(schedule.start_time.year).to eq(2011) expect(schedule.start_time.month).to eq(11) expect(schedule.start_time.day).to eq(16) expect(schedule.start_time.utc_offset).to eq(-5 * 3600) expect(schedule.duration).to eq(3600) expect(schedule.rrules).to eq([ 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) expect(schedule.all_occurrences).to eq([ 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(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)) expect(times).to eq([ 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) expect(schedule.occurrences_between(t0, t0 + ONE_WEEK).count).to eq(5) expect(schedule.occurrences_between(t0 + ONE_WEEK, t0 + 2 * ONE_WEEK).count).to eq(0) end it 'should produce occurrences regardless of time being specified [#81]' do schedule = Schedule.new(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)) expect(times).to eq([ 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) expect(schedule.first(2)[0]).to eq(t0) expect(schedule.first(2)[1]).to eq(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) expect(schedule.occurring_between?(t0, t1)).to be_truthy end require 'active_support/time' it 'should not hang next_time on DST boundary [#98]', expect_warnings: true 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 expect(schedule.occurrences(Date.new(2013, 07, 13).to_time)).to be_a Array end it 'should still include date over DST boundary [#98]', expect_warnings: true 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) expect(times.detect { |o| Date.new(o.year, o.month, o.day) == Date.new(2013, 3, 31) }).to be_truthy 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 expect(schedule.next_occurrence(t0)).to eq(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 |s| s.add_recurrence_rule Rule.weekly.day(:tuesday, :thursday) s.add_exception_time t0 end expect(schedule.first).to eq(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(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) expect(schedule.occurrences_between(t1, t2).first).to eq(t1) end it 'should return next_occurrence in utc if start_time is utc [#115]' do schedule = Schedule.new(Time.utc(2012, 10, 10, 20, 15, 0)) schedule.rrule Rule.daily expect(schedule.next_occurrence).to 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 expect(schedule.next_occurrence).not_to be_utc end it 'should return next_occurrence in local by default [#115]' do schedule = Schedule.new schedule.rrule Rule.daily expect(schedule.next_occurrence).not_to 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)) expect(schedule.all_occurrences).to eq([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 expect(time.usec).to eq(0) end end end end end ice_cube-0.16.4/spec/examples/rfc_spec.rb000066400000000000000000000427141413434150700202330ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule 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) } expect(dates).to eq(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| expect(dates).to include(Time.utc(date.year, date.month, date.day)) if offset % 2 == 0 expect(dates).not_to 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)) expect(dates).to eq([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| expect(date.month).to eq(1) expect([1998, 1999, 2000]).to 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| expect(date.month).to eq(1) expect([1998, 1999, 2000]).to 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)) expect(dates).to eq([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)) 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_time = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_time) 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| expect(date.yday).to eq(previous_date.yday + 14) previous_date = date end end it 'should ~ weekly on tuesday and thursday for 5 weeks (a)' do start_time = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_time) 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) } expect(dates).to eq(expectation.flatten) end it 'should ~ weekly on tuesday and thursday for 5 weeks (b)' do start_time = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_time) 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) } expect(dates).to eq(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_time = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_time) 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) } expect(dates).to eq(expectation.flatten) end it 'should ~ every other week on tuesday and thursday for 8 occurrences' do start_time = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_time) 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) } expect(dates).to eq(expectation.flatten) end it 'should ~ monthly on the 1st friday for ten occurrences' do start_time = Time.utc(1997, 9, 5) schedule = IceCube::Schedule.new(start_time) 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)] expect(dates).to eq(expectation) end it 'should ~ monthly on the first friday until december 24, 1997' do start_time = Time.utc(1997, 9, 5) schedule = IceCube::Schedule.new(start_time) 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)] expect(dates).to eq(expectation) end it 'should ~ every other month on the 1st and last sunday of the month for 10 occurrences' do start_time = Time.utc(1997, 9, 7) schedule = IceCube::Schedule.new(start_time) 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)] expect(dates).to eq(expectation) end it 'should ~ monthly on the second to last monday of the month for 6 months' do start_time = Time.utc(1997, 9, 22) schedule = IceCube::Schedule.new(start_time) 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)] expect(dates).to eq(expectation) end it 'should ~ monthly on the third to last day of the month, 6 times' do start_time = Time.utc(1997, 9, 28) schedule = IceCube::Schedule.new(start_time) 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)] expect(dates).to eq(expectation) end it 'should ~ monthly on the 2nd and 15th of the month for 10 occurrences' do start_time = Time.utc(1997, 9, 2) schedule = IceCube::Schedule.new(start_time) 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)] expect(dates).to eq(expectation) end it 'should ~ monthly on the 1st and last days of the month for 10 occurrences' do start_time = Time.utc(1997, 9, 30) schedule = IceCube::Schedule.new(start_time) 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)] expect(dates).to eq(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) } expect(dates).to eq(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) } expect(dates).to eq(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 expect(dates).to eq(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)] expect(dates).to eq(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)] expect(dates).to eq(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) } expect(dates).to eq(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) } expect(dates).to eq(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)] expect(dates).to eq(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)] expect(dates).to eq(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)] expect(dates).to eq(expectation) end it 'should ~ every 3 hours from 9am to 5pm on a specific day' do start_time = Time.utc(1997, 9, 2, 9, 0, 0) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.hourly(3).until(Time.utc(1997, 9, 2, 17, 0, 0)) dates = schedule.all_occurrences expect(dates).to eq([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_time = Time.utc(1997, 9, 2, 9, 0, 0) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.minutely(15).count(6) dates = schedule.all_occurrences expect(dates).to eq([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_time = Time.utc(1997, 9, 2, 9, 0, 0) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.minutely(90).count(4) dates = schedule.all_occurrences expect(dates).to eq([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_time = Time.utc(1997, 9, 2, 8, 0, 0) end_date = Time.utc(1997, 9, 2, 10, 20, 0) schedule = IceCube::Schedule.new(start_time) 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)] expect(dates).to eq(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! expect(schedule.occurrences(expectation.last)).to eq(expectation) expectation.each do |date| expect(schedule).to be_occurs_at(date) end end ice_cube-0.16.4/spec/examples/schedule_equality_spec.rb000066400000000000000000000066231413434150700231710ustar00rootroot00000000000000require 'spec_helper' module IceCube describe Schedule do let(:t0) { Time.utc(2017, 1, 1, 12, 34, 56) } let(:s1) { IceCube::Schedule.new(t0) } let(:s2) { IceCube::Schedule.new(t0) } describe :eql? do subject(:equality) { s1 == s2 } it "should be true for same start time" do should be true end it "should be false for different start time" do s2.start_time = t0 + 1 should be false end it "should be false for UTC vs. British start time", system_time_zone: "Europe/London" do s2.start_time = t0.getlocal should be false end it "should be false with different offset" do s1.start_time = s1.start_time.getlocal("-06:00") s2.start_time = s1.start_time.getlocal("-05:00") should be false end it "should be true with same static offset" do s1.start_time = t0.getlocal("-08:00") s2.start_time = t0.getlocal("-08:00") should be true end it "should be false with local zone vs. static offset", system_time_zone: "America/Vancouver" do s1.start_time = t0.getlocal s2.start_time = t0.getlocal(s1.start_time.utc_offset) should be false end it "should be false with different duration" do s2.duration = ONE_HOUR should be false end it "should be false with different end time" do s2.end_time = s2.start_time + ONE_HOUR should be false end context "with ActiveSupport", requires_active_support: true do require 'active_support/time' let(:utc_tz) { ActiveSupport::TimeZone["Etc/UTC"] } let(:pst_tz) { ActiveSupport::TimeZone["America/Vancouver"] } let(:est_tz) { ActiveSupport::TimeZone["America/New_York"] } let(:activesupport_t0) { utc_tz.local(2017, 1, 1, 12, 34, 56) } it "should be true for ActiveSupport UTC vs. standard UTC" do s2.start_time = activesupport_t0 should be true end it "should be true for ActiveSupport TZ vs. standard TZ", system_time_zone: "America/Vancouver" do s1.start_time = t0.getlocal s2.start_time = activesupport_t0.in_time_zone(pst_tz) should be true end it "should be false for different ActiveSupport zones" do s2.start_time = activesupport_t0.in_time_zone(pst_tz) s1.start_time = activesupport_t0.in_time_zone(est_tz) should be false end end it "should be true with same rrules in different order" do s1.rrule Rule.weekly.day(:thursday) s1.rrule Rule.monthly.day_of_month(1) s2.rrule Rule.monthly.day_of_month(1) s2.rrule Rule.weekly.day(:thursday) should be true end it "should be false with different rrules" do s1.rrule Rule.weekly s2.rrule Rule.weekly(2) should be false end it "should be true with same extimes in different order" do s1.rrule Rule.hourly s1.extime t0 + ONE_HOUR s1.extime t0 + 3 * ONE_HOUR s2.rrule Rule.hourly s2.extime t0 + 3 * ONE_HOUR s2.extime t0 + ONE_HOUR should be true end it "should be false with different extimes" do s1.rrule Rule.hourly s1.extime t0 + ONE_HOUR s1.rrule Rule.hourly s2.extime t0 + 3 * ONE_HOUR should be false end end end end ice_cube-0.16.4/spec/examples/schedule_spec.rb000066400000000000000000001005441413434150700212510ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' require 'benchmark' 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 expect(schedule.start_time).to eq(t1) end it 'initializes with a start_time' do t1 = Time.local(2013, 2, 14, 0, 32, 0) schedule = IceCube::Schedule.new(t1) expect(schedule.start_time).to be_a Time expect(schedule.start_time).to eq(t1) end it 'converts initialized DateTime to Time', expect_warnings: true do dt = DateTime.new(2013, 2, 14, 0, 32, 0) schedule = IceCube::Schedule.new(dt) expect(schedule.start_time).to be_a Time expect(schedule.start_time).to eq(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 expect { schedule.next_occurrence }.not_to raise_error end it "should not skip ahead a day when called with a date" do schedule = IceCube::Schedule.new(Time.utc(2014, 1, 1, 12, 34, 56)) do |s| s.add_recurrence_rule IceCube::Rule.hourly end next_hour = schedule.next_occurrence(Date.new(2014, 1, 2)) expect( next_hour ).to eq Time.utc(2014, 1, 2, 00, 34 , 56) end end describe :duration do it 'should be based on end_time' do start = Time.now schedule = IceCube::Schedule.new(start) expect(schedule.duration).to eq(0) schedule.end_time = start + 3600 expect(schedule.duration).to eq(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) expect(schedule.duration).to eq(600) end end describe :occurring_at? do it "should not capture multiple days when called with a date" do schedule = IceCube::Schedule.new do |s| s.start_time = Time.utc(2013, 12, 31, 23, 59, 50) s.duration = 20 s.add_recurrence_rule IceCube::Rule.daily(2) end expect( schedule.occurring_at?(Date.new(2014, 1, 1)) ).to eq true expect( schedule.occurring_at?(Date.new(2014, 1, 2)) ).to eq false end end describe :recurrence_times do it 'should start empty' do expect(IceCube::Schedule.new.recurrence_times).to be_empty end it 'should include added times' do schedule = IceCube::Schedule.new(t0 = Time.now) schedule.add_recurrence_time(t1 = t0 + 3600) expect(schedule.recurrence_times).to eq([t1]) end it 'can include start time' do schedule = IceCube::Schedule.new(t0 = Time.now) schedule.add_recurrence_time(t0) expect(schedule.recurrence_times).to eq([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 expect do schedules.first.conflicts_with?(schedules.last) end.to 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 expect do schedule1.conflicts_with?(schedule2, Time.now + IceCube::ONE_DAY) end.not_to 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) expect do schedule1.conflicts_with?(schedule2) end.not_to 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 expect do schedule1.conflicts_with?(schedule2) end.not_to 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) expect(conflict).to be_truthy 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) expect(conflict).to be_falsey 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) expect(conflict).to be_truthy 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) expect(conflict).to be_truthy 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) expect(schedule2.conflicts_with?(schedule1, start_time + IceCube::ONE_WEEK)). to be_truthy expect(schedule2.conflicts_with?(schedule1, start_time + IceCube::ONE_DAY)). to be_falsey 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) expect(conflict).to be_falsey 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) expect(conflict).to be_falsey 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) expect(conflict).to be_truthy 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) expect(conflict).to be_truthy conflict = schedule2.conflicts_with?(schedule1) expect(conflict).to be_truthy 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) expect(conflict).to be_falsey conflict = schedule2.conflicts_with?(schedule1) expect(conflict).to be_falsey 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) expect(conflict).to be_falsey conflict = schedule2.conflicts_with?(schedule1) expect(conflict).to be_falsey 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) expect(conflict).to be_truthy conflict = schedule2.conflicts_with?(schedule1) expect(conflict).to be_truthy 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) expect(conflict).to be_falsey conflict = schedule2.conflicts_with?(schedule1) expect(conflict).to be_falsey 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 expect(answers).to eq(schedule.first(10)) end it 'should return self' do schedule = IceCube::Schedule.new expect(schedule.each_occurrence { |s| }).to eq(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 } expect(answers).to eq([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| expect(o.end_time).to eq(o + IceCube::ONE_HOUR) } end it 'should include its start time when empty' do schedule = IceCube::Schedule.new(t0 = Time.now) expect(schedule.all_occurrences).to eq([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 expect(schedule.all_occurrences).to eq([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) expect(schedule.all_occurrences).to eq([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 expect do schedule.all_occurrences end.to 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) expect(schedule.all_occurrences.size).to eq(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 expect(occurrences).to eq([ 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) expect(schedule.next_occurrences(1)).to be_empty end it "should not skip ahead a day when called with a date" do schedule = IceCube::Schedule.new(Time.utc(2014, 1, 1, 12, 34, 56)) do |s| s.add_recurrence_rule IceCube::Rule.hourly end next_hours = schedule.next_occurrences(2, Date.new(2014, 1, 2)) expect( next_hours ).to eq [Time.utc(2014, 1, 2, 00, 34 , 56), Time.utc(2014, 1, 2, 01, 34 , 56)] 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 expect(occurrence).to eq(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 :spans do it 'should find occurrence in past with duration beyond the start time' do t0 = Time.utc(2015, 10, 1, 15, 31) schedule = IceCube::Schedule.new(t0, :duration => 2 * IceCube::ONE_HOUR) schedule.add_recurrence_rule IceCube::Rule.daily next_occ = schedule.next_occurrence(t0 + IceCube::ONE_HOUR, :spans => true) expect(next_occ).to eq(t0) end it 'should include occurrence in past with duration beyond the start time' do t0 = Time.utc(2015, 10, 1, 15, 31) schedule = IceCube::Schedule.new(t0, :duration => 2 * IceCube::ONE_HOUR) schedule.add_recurrence_rule IceCube::Rule.daily.count(2) occs = schedule.next_occurrences(10, t0 + IceCube::ONE_HOUR, :spans => true) expect(occs).to eq([t0, t0 + IceCube::ONE_DAY]) end it 'should allow duration span on remaining_occurrences' do t0 = Time.utc(2015, 10, 1, 00, 00) schedule = IceCube::Schedule.new(t0, :duration => IceCube::ONE_DAY) schedule.add_recurrence_rule IceCube::Rule.daily.count(3) occs = schedule.remaining_occurrences(t0 + IceCube::ONE_DAY + IceCube::ONE_HOUR, :spans => true) expect(occs).to eq([t0 + IceCube::ONE_DAY, t0 + 2 * IceCube::ONE_DAY]) end it 'should include occurrences with duration spanning the requested start time' do t0 = Time.utc(2015, 10, 1, 15, 31) schedule = IceCube::Schedule.new(t0, :duration => 30 * IceCube::ONE_DAY) long_event = schedule.remaining_occurrences_enumerator(t0 + IceCube::ONE_DAY, :spans => true).take(1) expect(long_event).to eq([t0]) end it 'should find occurrences between including previous one with duration spanning start' do t0 = Time.utc(2015, 10, 1, 10, 00) schedule = IceCube::Schedule.new(t0, :duration => IceCube::ONE_HOUR) schedule.add_recurrence_rule IceCube::Rule.hourly.count(10) occs = schedule.occurrences_between(t0 + IceCube::ONE_HOUR + 1, t0 + 3 * IceCube::ONE_HOUR + 1, :spans => true) expect(occs.length).to eq(3) end it 'should include long occurrences starting before and ending after' do t0 = Time.utc(2015, 10, 1, 00, 00) schedule = IceCube::Schedule.new(t0, :duration => IceCube::ONE_DAY) occs = schedule.occurrences_between(t0 + IceCube::ONE_HOUR, t0 + IceCube::ONE_DAY - IceCube::ONE_HOUR, :spans => true) expect(occs).to eq([t0]) end it 'should not find occurrence with duration ending on start time' do t0 = Time.utc(2015, 10, 1, 12, 00) schedule = IceCube::Schedule.new(t0, :duration => IceCube::ONE_HOUR) expect(schedule.occurs_between?(t0 + IceCube::ONE_HOUR, t0 + 2 * IceCube::ONE_HOUR, :spans => true)).to be_falsey end it 'should quickly fetch a future time from a recurring schedule' do t0 = Time.utc(2000, 10, 1, 00, 00) t1 = Time.utc(2015, 10, 1, 12, 00) schedule = IceCube::Schedule.new(t0, :duration => IceCube::ONE_HOUR - 1) schedule.add_recurrence_rule IceCube::Rule.hourly occ = nil timing = Benchmark.realtime do occ = schedule.remaining_occurrences_enumerator(t1, :spans => true).take(1) end expect(timing).to be < 0.1 expect(occ).to eq([t1]) end it 'should not include occurrence ending on start time' do t0 = Time.utc(2015, 10, 1, 10, 00) schedule = IceCube::Schedule.new(t0, :duration => IceCube::ONE_HOUR / 2) schedule.add_recurrence_rule IceCube::Rule.minutely(30).count(6) third_occ = schedule.next_occurrence(t0 + IceCube::ONE_HOUR, :spans => true) expect(third_occ).to eq(t0 + IceCube::ONE_HOUR) 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 * IceCube::ONE_DAY) expect(previous).to eq(t0 + IceCube::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) expect(previous).to be_nil end it "should not skip back a day when called with a date" do schedule = IceCube::Schedule.new(Time.utc(2014, 1, 1, 12, 34, 56)) do |s| s.add_recurrence_rule IceCube::Rule.hourly end prev_hour = schedule.previous_occurrence(Date.new(2014, 1, 2)) expect( prev_hour ).to eq Time.utc(2014, 1, 1, 23, 34 , 56) 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 * IceCube::ONE_DAY) expect(previous).to eq([t0 + IceCube::ONE_DAY, t0 + 2 * IceCube::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 * IceCube::ONE_DAY) expect(previous).to eq([t0, t0 + IceCube::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) expect(previous).to eq([]) end it "should not skip back a day when called with a date" do schedule = IceCube::Schedule.new(Time.utc(2014, 1, 1, 12, 34, 56)) do |s| s.add_recurrence_rule IceCube::Rule.hourly end prev_hours = schedule.previous_occurrences(2, Date.new(2014, 1, 2)) expect( prev_hours ).to eq [Time.utc(2014, 1, 1, 22, 34 , 56), Time.utc(2014, 1, 1, 23, 34 , 56)] 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) expect(schedule.last).to eq(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) expect(schedule.last(2)).to eq([t1 - IceCube::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(ArgumentError) end end describe :start_time= 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 = Time.now expect(Time.now - schedule.first.start_time).to 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 expect(schedule.rrules).to 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 expect(schedule.rrules.count).to eq(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 expect(schedule.rrules).to 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 expect([rule]).to eq(rule2) end it 'should return [] if nothing was removed' do schedule = IceCube::Schedule.new Time.now rule = IceCube::Rule.daily expect(schedule.remove_recurrence_rule(rule)).to eq([]) 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 expect(schedule.recurrence_times).to be_empty end it 'should return the time that was removed' do schedule = IceCube::Schedule.new Time.now time = Time.now schedule.rtime time expect(schedule.remove_rtime(time)).to eq(time) end it 'should return nil if the date was not in the schedule' do schedule = IceCube::Schedule.new Time.now expect(schedule.remove_recurrence_time(Time.now)).to 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 expect(schedule.exception_times).to be_empty end it 'should return the date that was removed' do schedule = IceCube::Schedule.new Time.now time = Time.now schedule.extime time expect(schedule.remove_extime(time)).to eq(time) end it 'should return nil if the date was not in the schedule' do schedule = IceCube::Schedule.new Time.now expect(schedule.remove_exception_time(Time.now)).to 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 expect(schedule.occurs_on?(Date.new(2010, 7, 1))).to be_falsey expect(schedule.occurs_on?(Date.new(2010, 7, 2))).to be_truthy expect(schedule.occurs_on?(Date.new(2010, 7, 3))).to be_falsey end specify 'should determine if it occurs on the day of a given UTC Time' do expect(schedule.occurs_on?(Time.utc(2010, 7, 1, 23, 59, 59))).to be_falsey expect(schedule.occurs_on?(Time.utc(2010, 7, 2, 0, 0, 1))).to be_truthy expect(schedule.occurs_on?(Time.utc(2010, 7, 2, 23, 59, 59))).to be_truthy expect(schedule.occurs_on?(Time.utc(2010, 7, 3, 0, 0, 1))).to be_falsey end specify 'should determine if it occurs on the day of a given local Time' do expect(schedule.occurs_on?(Time.local(2010, 7, 1, 23, 59, 59))).to be_falsey expect(schedule.occurs_on?(Time.local(2010, 7, 2, 0, 0, 1))).to be_truthy expect(schedule.occurs_on?(Time.local(2010, 7, 2, 23, 59, 59))).to be_truthy expect(schedule.occurs_on?(Time.local(2010, 7, 3, 0, 0, 1))).to be_falsey end specify 'should determine if it occurs on the day of a given non-local Time' do expect(schedule.occurs_on?(Time.new(2010, 7, 1, 23, 59, 59, "+11:15"))).to be_falsey expect(schedule.occurs_on?(Time.new(2010, 7, 2, 0, 0, 1, "+11:15"))).to be_truthy expect(schedule.occurs_on?(Time.new(2010, 7, 2, 23, 59, 59, "+11:15"))).to be_truthy expect(schedule.occurs_on?(Time.new(2010, 7, 3, 0, 0, 1, "+11:15"))).to be_falsey end specify 'should determine if it occurs on the day of a given ActiveSupport::Time', :requires_active_support => true do Time.zone = "Pacific/Honolulu" expect(schedule.occurs_on?(Time.zone.parse('2010-07-01 23:59:59'))).to be_falsey expect(schedule.occurs_on?(Time.zone.parse('2010-07-02 00:00:01'))).to be_truthy expect(schedule.occurs_on?(Time.zone.parse('2010-07-02 23:59:59'))).to be_truthy expect(schedule.occurs_on?(Time.zone.parse('2010-07-03 00:00:01'))).to be_falsey 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', :requires_active_support => 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 context 'across DST' do let(:start_time) { Time.local(2010, 3, 2, 0, 0, 0) } before { schedule.add_recurrence_rule(IceCube::Rule.monthly) } it 'determines local midnight with time change' do expect(schedule.occurs_on?(Date.new(2010, 7, 2))).to be_truthy end 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)) expect(schedule.occurs_on?(Date.new(2010, 7, 11))).to be_truthy expect(schedule.occurs_on?(Date.new(2010, 7, 12))).to be_truthy expect(schedule.occurs_on?(Date.new(2010, 7, 13))).to be_truthy 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 expect(occurrence.dst?).to eq(start_time.dst?) if start_time.respond_to? :dst? expect(occurrence.utc?).to eq(start_time.utc?) if start_time.respond_to? :utc? expect(occurrence.zone).to eq(start_time.zone) occurrence.utc_offset == start_time.utc_offset end def trap_infinite_loop_beyond(iterations) expect_any_instance_of(IceCube::ValidatedRule).to receive(:finds_acceptable_time?). at_most(iterations).times.and_call_original end end ice_cube-0.16.4/spec/examples/secondly_rule_spec.rb000066400000000000000000000035571413434150700223320ustar00rootroot00000000000000require 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") expect(rule.validations_for(:interval).first.interval).to eq(2) end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.secondly("3") expect(rule.validations_for(:interval).first.interval).to eq(3) end it 'raises an argument error when a bad value is passed' do expect { Rule.secondly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { Rule.secondly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end it "should realign to the first second_of_minute" do t0 = Time.utc(2017, 1, 1, 20, 30, 40) schedule = IceCube::Schedule.new(t0) schedule.rrule IceCube::Rule.secondly(10).second_of_minute(5, 15) expect(schedule.first(2)).to eq [t0 + 25*ONE_SECOND, t0 + 35*ONE_SECOND] end it "raises errors for misaligned interval and minute_of_hour values" do expect { IceCube::Rule.secondly(10).second_of_minute(3, 6) }.to raise_error(ArgumentError, "intervals in second_of_minute(3, 6) must be multiples of interval(10)") end it "raises errors for misaligned second_of_minute values when changing interval" do expect { IceCube::Rule.secondly(3).second_of_minute(3, 6).interval(5) }.to raise_error(ArgumentError, "interval(5) must be a multiple of intervals in second_of_minute(3, 6)") end end end ice_cube-0.16.4/spec/examples/serialization_spec.rb000066400000000000000000000033041413434150700223260ustar00rootroot00000000000000require 'active_support/time' require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule do let(:start_time) { Time.now } let(:schedule) { IceCube::Schedule.new(start_time) } let(:yaml) { described_class.dump(schedule) } describe "::dump(schedule)" do it "serializes a Schedule object as YAML string" do expect(yaml).to start_with "---\n" end context "with ActiveSupport::TimeWithZone" do let(:start_time) { Time.now.in_time_zone("America/Vancouver") } it "serializes time as a Hash" do hash = YAML.load(yaml) expect(hash[:start_time][:time]).to eq start_time.utc expect(hash[:start_time][:zone]).to eq "America/Vancouver" end end [nil, ""].each do |blank| context "when schedule is #{blank.inspect}" do let(:schedule) { blank } it "returns #{blank.inspect}" do expect(yaml).to 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 expect(new_schedule.start_time.to_s).to eq schedule.start_time.to_s end context "with ActiveSupport::TimeWithZone" do let(:start_time) { Time.now.in_time_zone("America/Vancouver") } it "deserializes time from Hash" do expect(new_schedule.start_time).to eq start_time expect(new_schedule.start_time.time_zone).to eq start_time.time_zone end end [nil, ""].each do |blank| context "when yaml is #{blank.inspect}" do let(:yaml) { blank } it "returns #{blank.inspect}" do expect(new_schedule).to be blank end end end end end ice_cube-0.16.4/spec/examples/string_builder_spec.rb000066400000000000000000000011551413434150700224670ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::StringBuilder do describe :sentence do it 'should return empty string when none' do expect(IceCube::StringBuilder.sentence([])).to eq('') end it 'should return sole when one' do expect(IceCube::StringBuilder.sentence(['1'])).to eq('1') end it 'should split on and when two' do expect(IceCube::StringBuilder.sentence(['1', '2'])).to eq('1 and 2') end it 'should comma and when more than two' do expect(IceCube::StringBuilder.sentence(['1', '2', '3'])).to eq('1, 2, and 3') end end end ice_cube-0.16.4/spec/examples/time_util_spec.rb000066400000000000000000000102761413434150700214520ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe TimeUtil do describe :beginning_of_date do let(:utc_time) { Time.utc(2014, 7, 8, 12, 34, 56) } let(:dst_time) { Time.local(2014, 7, 8, 12, 34, 56) } let(:std_time) { Time.local(2014, 1, 1, 12, 34, 56) } it "returns 00:00:00 crossing into DST" do time = TimeUtil.beginning_of_date(dst_time.to_date, std_time) dst_diff = dst_time.utc_offset - std_time.utc_offset expect([time.hour, time.min, time.sec]).to eq [0, 0, 0] expect(time.utc_offset - std_time.utc_offset).to eq dst_diff end it "returns 00:00:00 crossing out of DST" do time = TimeUtil.beginning_of_date(std_time.to_date, dst_time) dst_diff = std_time.utc_offset - dst_time.utc_offset expect([time.hour, time.min, time.sec]).to eq [0, 0, 0] expect(time.utc_offset - dst_time.utc_offset).to eq dst_diff end it "returns 00:00:00 from UTC for local time" do time = TimeUtil.beginning_of_date(utc_time.to_date, dst_time) expect([time.hour, time.min, time.sec]).to eq [0, 0, 0] expect(time.utc_offset).to eq dst_time.utc_offset end it "returns 00:00:00 from local time for UTC" do time = TimeUtil.beginning_of_date(dst_time.to_date, utc_time) expect([time.hour, time.min, time.sec]).to eq [0, 0, 0] expect(time.utc?).to eq true end it "returns 00:00:00 from local time for nonlocal time" do time = TimeUtil.beginning_of_date(dst_time.to_date, std_time.getlocal(7200)) expect([time.hour, time.min, time.sec]).to eq [0, 0, 0] expect(time.utc_offset).to eq 7200 end end describe :wday_to_sym do it 'converts 0..6 to weekday symbols' do expect(TimeUtil.wday_to_sym(1)).to eq(:monday) end it 'returns weekday symbols as is' do expect(TimeUtil.wday_to_sym(:monday)).to eq(:monday) end it 'raises an error for bad input' do expect { TimeUtil.wday_to_sym(:anyday) }.to raise_error(ArgumentError) expect { TimeUtil.wday_to_sym(17) }.to raise_error(ArgumentError) end end describe :sym_to_wday do it 'converts weekday symbols to 0..6 wday numbers' do expect(TimeUtil.sym_to_wday(:monday)).to eq(1) end it 'returns wday numbers as is' do expect(TimeUtil.sym_to_wday(1)).to eq(1) end it 'raises an error for bad input' do expect { TimeUtil.sym_to_wday(:anyday) }.to raise_error(ArgumentError) expect { TimeUtil.sym_to_wday(17) }.to raise_error(ArgumentError) end end describe :sym_to_month do it 'converts month symbols to 1..12 month numbers' do expect(TimeUtil.sym_to_month(:january)).to eq(1) end it 'returns month numbers as is' do expect(TimeUtil.sym_to_month(12)).to eq(12) end it 'raises an error for bad input' do expect { TimeUtil.sym_to_month(13) }.to raise_error(ArgumentError) expect { TimeUtil.sym_to_month(:neveruary) }.to raise_error(ArgumentError) 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 describe :match_zone do let(:date) { Date.new(2014, 1, 1) } WORLD_TIME_ZONES.each do |zone| context "in #{zone}", :system_time_zone => zone do let(:utc_time) { Time.utc(2014, 1, 1, 0, 0, 1) } let(:local_time) { Time.local(2014, 1, 1, 0, 0, 1) } it 'converts Date to beginning of date of local reference time' do expect(TimeUtil.match_zone(date, local_time)).to eq local_time - 1 end it 'converts Date to beginning of date of UTC reference time' do expect(TimeUtil.match_zone(date, utc_time)).to eq utc_time - 1 end end end context "in UTC" do let(:utc_time) { Time.utc(2014, 1, 1, 0, 0, 1) } it 'converts Date to beginning of date of reference time' do expect(TimeUtil.match_zone(date, utc_time)).to eq utc_time - 1 end end end end end ice_cube-0.16.4/spec/examples/to_ical_spec.rb000066400000000000000000000234121413434150700210650ustar00rootroot00000000000000require 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 expect(rule.to_ical).to eq("FREQ=DAILY") end it 'should return a proper ical representation for a basic monthly rule' do rule = IceCube::Rule.weekly expect(rule.to_ical).to eq("FREQ=WEEKLY") end it 'should return a proper ical representation for a basic monthly rule' do rule = IceCube::Rule.monthly expect(rule.to_ical).to eq("FREQ=MONTHLY") end it 'should return a proper ical representation for a basic yearly rule' do rule = IceCube::Rule.yearly expect(rule.to_ical).to eq("FREQ=YEARLY") end it 'should return a proper ical representation for a basic hourly rule' do rule = IceCube::Rule.hourly expect(rule.to_ical).to eq("FREQ=HOURLY") end it 'should return a proper ical representation for a basic minutely rule' do rule = IceCube::Rule.minutely expect(rule.to_ical).to eq("FREQ=MINUTELY") end it 'should return a proper ical representation for a basic secondly rule' do rule = IceCube::Rule.secondly expect(rule.to_ical).to eq("FREQ=SECONDLY") end it 'should be able to serialize a .day rule to_ical' do rule = IceCube::Rule.daily.day(:monday, :tuesday) expect(rule.to_ical).to eq("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]) expect(rule.to_ical).to eq("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) expect(rule.to_ical).to eq("FREQ=DAILY;BYMONTHDAY=23") end it 'should be able to serialize a .day_of_year rule to_ical' do rule = IceCube::Rule.yearly.day_of_year(100,200) expect(rule.to_ical).to eq("FREQ=YEARLY;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) expect(rule.to_ical).to eq("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) expect(rule.to_ical).to eq("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) expect(rule.to_ical).to eq("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) expect(rule.to_ical).to eq("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]) expect(['FREQ=DAILY;BYDAY=TU,1MO,-1MO', 'FREQ=DAILY;BYDAY=1MO,-1MO,TU'].include?(rule.to_ical)).to be_truthy 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) expect([ '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)).to be_truthy 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)) expect(schedule.to_ical).to eq("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)) expect(schedule.to_ical).to eq("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' expect(schedule.to_ical).to eq(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" expect(schedule.to_ical).to eq(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' expect(schedule.to_ical).to eq(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" expect(schedule.to_ical).to eq(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" expect(schedule.to_ical).to eq(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" expect(schedule.to_ical).to eq(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' expect(schedule.to_ical).to eq(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' expect(schedule.to_ical).to eq(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" expect(schedule.to_ical).to eq(expectation) end it 'should not modify the duration when running to_ical' do schedule = IceCube::Schedule.new(Time.now, :duration => 3600) schedule.to_ical expect(schedule.duration).to eq(3600) end it 'should default to to_ical using local time' do time = Time.now schedule = IceCube::Schedule.new(Time.now) expect(schedule.to_ical).to eq("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 expect(schedule.to_ical).to eq("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) expect(schedule.to_ical).to eq("DTSTART;TZID=#{time.zone}:#{time.strftime('%Y%m%dT%H%M%S')}") # default false expect(schedule.to_ical(false)).to eq("DTSTART;TZID=#{time.zone}:#{time.strftime('%Y%m%dT%H%M%S')}") expect(schedule.to_ical(true)).to eq("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.utc(2123, 12, 31, 12, 34, 56.25) expect(rule.to_ical).to match "FREQ=WEEKLY;UNTIL=21231231T123456Z" end it 'should be able to serialize to ical with a count date' do rule = IceCube::Rule.weekly.count(5) expect(rule.to_ical).to eq "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) expect(rule.to_ical).to eq("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) expect(rule.to_ical).to eq("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) expect(rule.to_ical).to eq("FREQ=WEEKLY;INTERVAL=#{interval};WKST=MO") end it 'should not repeat interval when updating rule' do rule = IceCube::Rule.weekly rule.interval(2) expect(rule.to_ical).to match(/^FREQ=WEEKLY;INTERVAL=2/) end end ice_cube-0.16.4/spec/examples/to_s_de_spec.rb000066400000000000000000000204461413434150700210730ustar00rootroot00000000000000# encoding: utf-8 require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule, 'to_s' do before :each do I18n.locale = :de end after :all do I18n.locale = :en end it 'should represent its start time by default' do t0 = Time.local(2013, 2, 14) expect(IceCube::Schedule.new(t0).to_s).to eq('14. Februar 2013') end it 'should have a useful base to_s representation for a secondly rule' do expect(IceCube::Rule.secondly.to_s).to eq('Jede Sekunde') expect(IceCube::Rule.secondly(2).to_s).to eq('Alle 2 Sekunden') end it 'should have a useful base to_s representation for a minutely rule' do expect(IceCube::Rule.minutely.to_s).to eq('Jede Minute') expect(IceCube::Rule.minutely(2).to_s).to eq('Alle 2 Minuten') end it 'should have a useful base to_s representation for a hourly rule' do expect(IceCube::Rule.hourly.to_s).to eq('Stündlich') expect(IceCube::Rule.hourly(2).to_s).to eq('Alle 2 Stunden') end it 'should have a useful base to_s representation for a daily rule' do expect(IceCube::Rule.daily.to_s).to eq('Täglich') expect(IceCube::Rule.daily(2).to_s).to eq('Alle 2 Tage') end it 'should have a useful base to_s representation for a weekly rule' do expect(IceCube::Rule.weekly.to_s).to eq('Wöchentlich') expect(IceCube::Rule.weekly(2).to_s).to eq('Alle 2 Wochen') end it 'should have a useful base to_s representation for a monthly rule' do expect(IceCube::Rule.monthly.to_s).to eq('Monatlich') expect(IceCube::Rule.monthly(2).to_s).to eq('Alle 2 Monate') end it 'should have a useful base to_s representation for a yearly rule' do expect(IceCube::Rule.yearly.to_s).to eq('Jährlich') expect(IceCube::Rule.yearly(2).to_s).to eq('Alle 2 Jahre') end it 'should work with various sentence types properly' do expect(IceCube::Rule.weekly.to_s).to eq('Wöchentlich') expect(IceCube::Rule.weekly.day(:monday).to_s).to eq('Wöchentlich Montags') expect(IceCube::Rule.weekly.day(:monday, :tuesday).to_s).to eq('Wöchentlich Montags und Dienstags') expect(IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday).to_s).to eq('Wöchentlich Montags, Dienstags und Mittwochs') end it 'should show saturday and sunday as weekends' do expect(IceCube::Rule.weekly.day(:saturday, :sunday).to_s).to eq('Wöchentlich am Wochenende') end it 'should not show saturday and sunday as weekends when other days are present also' do expect(IceCube::Rule.weekly.day(:sunday, :monday, :saturday).to_s).to eq( 'Wöchentlich Sonntags, Montags und Samstags' ) end it 'should reorganize days to be in order' do expect(IceCube::Rule.weekly.day(:tuesday, :monday).to_s).to eq( 'Wöchentlich Montags und Dienstags' ) end it 'should show weekdays as such' do expect(IceCube::Rule.weekly.day( :monday, :tuesday, :wednesday, :thursday, :friday ).to_s).to eq('Wöchentlich an Wochentagen') end it 'should not show weekdays as such when a weekend day is present' do expect(IceCube::Rule.weekly.day( :sunday, :monday, :tuesday, :wednesday, :thursday, :friday ).to_s).to eq('Wöchentlich Sonntags, Montags, Dienstags, Mittwochs, Donnerstags und Freitags') # '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) expect(schedule.to_s).to eq("20. März 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) expect(schedule.to_s).to eq("20. März 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) expect(schedule.to_s).to eq('20. März 2010 / 21. März 2010') end it 'should order dates that are out of order' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 19) expect(schedule.to_s).to eq('19. März 2010 / 20. März 2010') end it 'should remove duplicated start time' do schedule = IceCube::Schedule.new t0 = Time.local(2010, 3, 20) schedule.add_recurrence_time t0 expect(schedule.to_s).to eq('20. März 2010') end it 'should remove duplicate rtimes' do schedule = IceCube::Schedule.new Time.local(2010, 3, 19) schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 20) expect(schedule.to_s).to eq('19. März 2010 / 20. März 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 expect(schedule.to_s).to eq('20. März 2010 / Wöchentlich') 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) expect(schedule.to_s).to eq('Wöchentlich / außer am 20. März 2010 / außer am 21. März 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]) expect(schedule.to_s).to eq(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 expect(rule_str).to eq('Monatlich am letzten Donnerstag') 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 expect(rule_str).to eq('Jährlich im Juni und Juli') 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 expect(rule_str).to eq('Monatlich am vorletzten Donnerstag') 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 expect(rule_str).to eq('Monatlich am 1. Donnerstag und letzten Donnerstag') 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 expect(rule_str).to eq('Monatlich am 1., 15. und 30. Tag des Monats') 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 expect(rule_str).to eq('Jährlich am 30. Tag des Jahres') 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 expect(rule_str).to eq('Täglich in der 6. und 12. Stunde des Tages') 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 expect(rule_str).to eq('Stündlich in der 10., 11., 12., 13., 14. und 15. Minute der Stunde') 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 expect(rule_str).to eq('Jede Minute in der 10. und 11. Sekunde der 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)) expect(schedule.to_s).to eq('Wöchentlich bis zum 3. Februar 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) expect(schedule.to_s).to eq('Wöchentlich 1 mal') 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) expect(schedule.to_s).to eq('Wöchentlich 2 mal') end endice_cube-0.16.4/spec/examples/to_s_en_spec.rb000066400000000000000000000213331413434150700211010ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule, 'to_s' do shared_examples "to_s in English" do it 'should represent its start time by default' do t0 = Time.local(2013, 2, 14) expect(IceCube::Schedule.new(t0).to_s).to eq('February 14, 2013') end it 'should have a useful base to_s representation for a secondly rule' do expect(IceCube::Rule.secondly.to_s).to eq('Secondly') expect(IceCube::Rule.secondly(2).to_s).to eq('Every 2 seconds') end it 'should have a useful base to_s representation for a minutely rule' do expect(IceCube::Rule.minutely.to_s).to eq('Minutely') expect(IceCube::Rule.minutely(2).to_s).to eq('Every 2 minutes') end it 'should have a useful base to_s representation for a hourly rule' do expect(IceCube::Rule.hourly.to_s).to eq('Hourly') expect(IceCube::Rule.hourly(2).to_s).to eq('Every 2 hours') end it 'should have a useful base to_s representation for a daily rule' do expect(IceCube::Rule.daily.to_s).to eq('Daily') expect(IceCube::Rule.daily(2).to_s).to eq('Every 2 days') end it 'should have a useful base to_s representation for a weekly rule' do expect(IceCube::Rule.weekly.to_s).to eq('Weekly') expect(IceCube::Rule.weekly(2).to_s).to eq('Every 2 weeks') end it 'should have a useful base to_s representation for a monthly rule' do expect(IceCube::Rule.monthly.to_s).to eq('Monthly') expect(IceCube::Rule.monthly(2).to_s).to eq('Every 2 months') end it 'should have a useful base to_s representation for a yearly rule' do expect(IceCube::Rule.yearly.to_s).to eq('Yearly') expect(IceCube::Rule.yearly(2).to_s).to eq('Every 2 years') end it 'should work with various sentence types properly' do expect(IceCube::Rule.weekly.to_s).to eq('Weekly') expect(IceCube::Rule.weekly.day(:monday).to_s).to eq('Weekly on Mondays') expect(IceCube::Rule.weekly.day(:monday, :tuesday).to_s).to eq('Weekly on Mondays and Tuesdays') expect(IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday).to_s).to eq('Weekly on Mondays, Tuesdays, and Wednesdays') end it 'should show saturday and sunday as weekends' do expect(IceCube::Rule.weekly.day(:saturday, :sunday).to_s).to eq('Weekly on Weekends') end it 'should not show saturday and sunday as weekends when other days are present also' do expect(IceCube::Rule.weekly.day(:sunday, :monday, :saturday).to_s).to eq( 'Weekly on Sundays, Mondays, and Saturdays' ) end it 'should reorganize days to be in order' do expect(IceCube::Rule.weekly.day(:tuesday, :monday).to_s).to eq( 'Weekly on Mondays and Tuesdays' ) end it 'should show weekdays as such' do expect(IceCube::Rule.weekly.day( :monday, :tuesday, :wednesday, :thursday, :friday ).to_s).to eq('Weekly on Weekdays') end it 'should not show weekdays as such when a weekend day is present' do expect(IceCube::Rule.weekly.day( :sunday, :monday, :tuesday, :wednesday, :thursday, :friday ).to_s).to eq('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) expect(schedule.to_s).to eq("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) expect(schedule.to_s).to eq("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) expect(schedule.to_s).to eq('March 20, 2010 / March 21, 2010') end it 'should order dates that are out of order' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 19) expect(schedule.to_s).to eq('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 expect(schedule.to_s).to eq('March 20, 2010') end it 'should remove duplicate rtimes' do schedule = IceCube::Schedule.new Time.local(2010, 3, 19) schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 20) expect(schedule.to_s).to eq('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 expect(schedule.to_s).to eq('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) expect(schedule.to_s).to eq('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]) expect(schedule.to_s).to eq(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 expect(rule_str).to eq('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 expect(rule_str).to eq('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 expect(rule_str).to eq('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 expect(rule_str).to eq('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 expect(rule_str).to eq('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 expect(rule_str).to eq('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 expect(rule_str).to eq('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 expect(rule_str).to eq('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 expect(rule_str).to eq('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)) expect(schedule.to_s).to eq('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) expect(schedule.to_s).to eq('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) expect(schedule.to_s).to eq('Weekly 2 times') end end context "without I18n" do before { allow(IceCube::I18n).to receive(:backend) { IceCube::NullI18n } } it_behaves_like "to_s in English" end context "with I18n" do before(:each) { I18n.locale = :en } it "uses I18n" do expect(IceCube::I18n.backend).to eq(I18n) end it_behaves_like "to_s in English" end end ice_cube-0.16.4/spec/examples/to_s_es_spec.rb000066400000000000000000000203761413434150700211140ustar00rootroot00000000000000# encoding: utf-8 require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule, 'to_s' do before :each do I18n.locale = :es end after :all do I18n.locale = :en end it 'should represent its start time by default' do t0 = Time.local(2013, 2, 14) expect(IceCube::Schedule.new(t0).to_s).to eq('14 de Febrero de 2013') end it 'should have a useful base to_s representation for a secondly rule' do expect(IceCube::Rule.secondly.to_s).to eq('Cada segundo') expect(IceCube::Rule.secondly(2).to_s).to eq('Cada 2 segundos') end it 'should have a useful base to_s representation for a minutely rule' do expect(IceCube::Rule.minutely.to_s).to eq('Cada minuto') expect(IceCube::Rule.minutely(2).to_s).to eq('Cada 2 minutos') end it 'should have a useful base to_s representation for a hourly rule' do expect(IceCube::Rule.hourly.to_s).to eq('Cada hora') expect(IceCube::Rule.hourly(2).to_s).to eq('Cada 2 horas') end it 'should have a useful base to_s representation for a daily rule' do expect(IceCube::Rule.daily.to_s).to eq('Diariamente') expect(IceCube::Rule.daily(2).to_s).to eq('Cada 2 días') end it 'should have a useful base to_s representation for a weekly rule' do expect(IceCube::Rule.weekly.to_s).to eq('Semanalmente') expect(IceCube::Rule.weekly(2).to_s).to eq('Cada 2 semanas') end it 'should have a useful base to_s representation for a monthly rule' do expect(IceCube::Rule.monthly.to_s).to eq('Mensualmente') expect(IceCube::Rule.monthly(2).to_s).to eq('Cada 2 meses') end it 'should have a useful base to_s representation for a yearly rule' do expect(IceCube::Rule.yearly.to_s).to eq('Anualmente') expect(IceCube::Rule.yearly(2).to_s).to eq('Cada 2 años') end it 'should work with various sentence types properly' do expect(IceCube::Rule.weekly.to_s).to eq('Semanalmente') expect(IceCube::Rule.weekly.day(:monday).to_s).to eq('Semanalmente los lunes') expect(IceCube::Rule.weekly.day(:monday, :tuesday).to_s).to eq('Semanalmente los lunes y los martes') expect(IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday).to_s).to eq('Semanalmente los lunes, los martes y los miércoles') end it 'should show saturday and sunday as weekends' do expect(IceCube::Rule.weekly.day(:saturday, :sunday).to_s).to eq('Semanalmente en fin de semana') end it 'should not show saturday and sunday as weekends when other days are present also' do expect(IceCube::Rule.weekly.day(:sunday, :monday, :saturday).to_s).to eq( 'Semanalmente los domingos, los lunes y los sábados' ) end it 'should reorganize days to be in order' do expect(IceCube::Rule.weekly.day(:tuesday, :monday).to_s).to eq( 'Semanalmente los lunes y los martes' ) end it 'should show weekdays as such' do expect(IceCube::Rule.weekly.day( :monday, :tuesday, :wednesday, :thursday, :friday ).to_s).to eq('Semanalmente en días laborables') end it 'should not show weekdays as such when a weekend day is present' do expect(IceCube::Rule.weekly.day( :sunday, :monday, :tuesday, :wednesday, :thursday, :friday ).to_s).to eq('Semanalmente los domingos, los lunes, los martes, los miércoles, los jueves y los viernes') 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) expect(schedule.to_s).to eq("20 de Marzo de 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) expect(schedule.to_s).to eq('20 de Marzo de 2010, 21 de Marzo de 2010') end it 'should order dates that are out of order' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 19) expect(schedule.to_s).to eq('19 de Marzo de 2010, 20 de Marzo de 2010') end it 'should remove duplicated start time' do schedule = IceCube::Schedule.new t0 = Time.local(2010, 3, 20) schedule.add_recurrence_time t0 expect(schedule.to_s).to eq('20 de Marzo de 2010') end it 'should remove duplicate rtimes' 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, 20) expect(schedule.to_s).to eq('20 de Marzo de 2010') end it 'should work with rules and dates' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) schedule.add_recurrence_time Time.local(2010, 3, 20) schedule.add_recurrence_rule IceCube::Rule.weekly expect(schedule.to_s).to eq('20 de Marzo de 2010, Semanalmente') end it 'should work with rules and dates and exdates' 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) # TODO: this text should be improved to add sentence connector expect(schedule.to_s).to eq('Semanalmente, excepto el 20 de Marzo de 2010, excepto el 21 de Marzo de 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]) expect(schedule.to_s).to eq(schedule.rrules[0].to_s) end it 'should be able to say the last monday of the month' do rule_str = IceCube::Rule.monthly.day_of_week(:thursday => [-1]).to_s expect(rule_str).to eq('Mensualmente en el último Jueves') 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 expect(rule_str).to eq('Anualmente en Junio y Julio') 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 expect(rule_str).to eq('Mensualmente en el penúltimo Jueves') 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 expect(rule_str).to eq('Mensualmente en los días 1º, 15º y 30º del mes') 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 expect(rule_str).to eq('Anualmente en el día 30º') 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 expect(rule_str).to eq('Diariamente en las horas 6º y 12º') 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 expect(rule_str).to eq('Cada hora en los minutos 10º, 11º, 12º, 13º, 14º y 15º') 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 expect(rule_str).to eq('Cada minuto en los segundos 10º y 11º del minuto') 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)) expect(schedule.to_s).to eq('Semanalmente hasta el 3 de Febrero de 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) expect(schedule.to_s).to eq('Semanalmente 1 vez') 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) expect(schedule.to_s).to eq('Semanalmente 2 veces') end # it 'should work when an end_time is set' do # schedule = IceCube::Schedule.new(Time.local(2012, 8, 31), :end_time => Time.local(2012, 10, 31)) # schedule.add_recurrence_rule IceCube::Rule.daily.count(2) # schedule.to_s.should == 'Diariamente 2 veces, hasta el 31 de Octubre 2012' # end end ice_cube-0.16.4/spec/examples/to_s_ja_spec.rb000066400000000000000000000175231413434150700210770ustar00rootroot00000000000000# encoding: utf-8 require File.dirname(__FILE__) + '/../spec_helper' describe IceCube::Schedule, 'to_s', locale: 'ja' do it 'should represent its start time by default' do t0 = Time.local(2013, 2, 14) expect(IceCube::Schedule.new(t0).to_s).to eq('2013年02月14日') end it 'should have a useful base to_s representation for a secondly rule' do expect(IceCube::Rule.secondly.to_s).to eq('毎秒') expect(IceCube::Rule.secondly(2).to_s).to eq('2秒ごと') end it 'should have a useful base to_s representation for a minutely rule' do expect(IceCube::Rule.minutely.to_s).to eq('毎分') expect(IceCube::Rule.minutely(2).to_s).to eq('2分ごと') end it 'should have a useful base to_s representation for a hourly rule' do expect(IceCube::Rule.hourly.to_s).to eq('毎時間') expect(IceCube::Rule.hourly(2).to_s).to eq('2時間ごと') end it 'should have a useful base to_s representation for a daily rule' do expect(IceCube::Rule.daily.to_s).to eq('毎日') expect(IceCube::Rule.daily(2).to_s).to eq('2日ごと') end it 'should have a useful base to_s representation for a weekly rule' do expect(IceCube::Rule.weekly.to_s).to eq('毎週') expect(IceCube::Rule.weekly(2).to_s).to eq('2週間ごと') end it 'should have a useful base to_s representation for a monthly rule' do expect(IceCube::Rule.monthly.to_s).to eq('毎月') expect(IceCube::Rule.monthly(2).to_s).to eq('2ヶ月ごと') end it 'should have a useful base to_s representation for a yearly rule' do expect(IceCube::Rule.yearly.to_s).to eq('毎年') expect(IceCube::Rule.yearly(2).to_s).to eq('2年ごと') end it 'should work with various sentence types properly' do expect(IceCube::Rule.weekly.to_s).to eq('毎週') expect(IceCube::Rule.weekly.day(:monday).to_s).to eq('毎週月曜') expect(IceCube::Rule.weekly.day(:monday, :tuesday).to_s).to eq('毎週月曜、火曜') expect(IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday).to_s).to eq('毎週月曜、火曜、水曜') end it 'should show saturday and sunday as weekends' do expect(IceCube::Rule.weekly.day(:saturday, :sunday).to_s).to eq('毎週週末') end it 'should not show saturday and sunday as weekends when other days are present also' do expect(IceCube::Rule.weekly.day(:sunday, :monday, :saturday).to_s).to eq( '毎週日曜、月曜、土曜' ) end it 'should reorganize days to be in order' do expect(IceCube::Rule.weekly.day(:tuesday, :monday).to_s).to eq( '毎週月曜、火曜' ) end it 'should show weekdays as such' do expect(IceCube::Rule.weekly.day( :monday, :tuesday, :wednesday, :thursday, :friday ).to_s).to eq('毎週平日') end it 'should not show weekdays as such when a weekend day is present' do expect(IceCube::Rule.weekly.day( :sunday, :monday, :tuesday, :wednesday, :thursday, :friday ).to_s).to eq('毎週日曜、月曜、火曜、水曜、木曜、金曜') end it 'should show start time for an empty schedule' do schedule = IceCube::Schedule.new Time.local(2010, 3, 20) expect(schedule.to_s).to eq("2010年03月20日") 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) expect(schedule.to_s).to eq("2010年03月20日") 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) expect(schedule.to_s).to eq('2010年03月20日 / 2010年03月21日') end it 'should order dates that are out of order' do schedule = IceCube::Schedule.new(Time.local(2010, 3, 20)) do |s| s.add_recurrence_time s.start_time - ONE_DAY end expect(schedule.to_s).to eq('2010年03月19日 / 2010年03月20日') end it 'should remove duplicated start time' do schedule = IceCube::Schedule.new(Time.local(2010, 3, 20)) do |s| s.add_recurrence_time s.start_time end expect(schedule.to_s).to eq('2010年03月20日') end it 'should remove duplicate rtimes' do schedule = IceCube::Schedule.new(Time.local(2010, 3, 19)) do |s| s.add_recurrence_time s.start_time + ONE_DAY s.add_recurrence_time s.start_time + ONE_DAY end expect(schedule.to_s).to eq('2010年03月19日 / 2010年03月20日') 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 expect(schedule.to_s).to eq('2010年03月20日 / 毎週') 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) expect(schedule.to_s).to eq('毎週 / 2010年03月20日以外 / 2010年03月21日以外') 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]) expect(schedule.to_s).to eq(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 expect(rule_str).to eq('毎月最終木曜') 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 expect(rule_str).to eq('毎年6月、7月') 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 expect(rule_str).to eq('毎月最後から2番目の木曜') 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 expect(rule_str).to eq('毎月1木曜、最終木曜') 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 expect(rule_str).to eq('毎月1、15、30日') 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 expect(rule_str).to eq('毎年30日') 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 expect(rule_str).to eq('毎日6、12時') 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 expect(rule_str).to eq('毎時間10、11、12、13、14、15分') 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 expect(rule_str).to eq('毎分10、11秒') 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)) expect(schedule.to_s).to eq('2012年02月03日まで毎週') end it 'should be able to reflect count' do schedule = IceCube::Schedule.new(Time.now) schedule.add_recurrence_rule IceCube::Rule.weekly.count(1) expect(schedule.to_s).to eq('毎週1回') 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) expect(schedule.to_s).to eq('毎週2回') end end ice_cube-0.16.4/spec/examples/to_yaml_spec.rb000066400000000000000000000315561413434150700211270ustar00rootroot00000000000000require File.dirname(__FILE__) + '/../spec_helper' module IceCube describe Schedule, 'to_yaml' do before do require 'active_support/time' Time.zone = 'Eastern Time (US & Canada)' end [: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(Time.zone.now) schedule.add_recurrence_rule Rule.send(type, 3) expect(Schedule.from_yaml(schedule.to_yaml).first(3).inspect).to eq(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 expect(schedule).to 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 expect(result1.map { |r| r.to_s }).to eq(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 expect(schedule.first(10).map { |r| r.to_s }).to eq(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.zone.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 expect(schedule.first(10).map { |r| r.to_s }).to eq(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.zone.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 expect(schedule.first(10).map { |r| r.to_s }).to eq(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 expect(schedule.first(10).map { |r| r.to_s }).to eq(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 expect(schedule.first(10).map { |r| r.to_s }).to eq(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 expect(schedule.first(10).map { |r| r.to_s }).to eq(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 expect(schedule.first(10).map { |r| r.to_s }).to eq(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 expect(schedule.first(10).map { |r| r.to_s }).to eq(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) expect(rule.to_yaml.include?('object')).to be_falsey 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) expect(schedule.to_yaml.include?('object')).to be_falsey 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', :requires_active_support => true do Time.zone = "Eastern Time (US & Canada)" start_time = Time.zone.local(2011, 11, 5, 12, 0, 0) schedule = Schedule.new(start_time) schedule = Schedule.from_yaml(schedule.to_yaml) # round trip ice_cube_start_time = schedule.start_time expect(ice_cube_start_time).to eq(start_time) expect(ice_cube_start_time.utc_offset).to eq(start_time.utc_offset) end it 'should be able to roll forward times and get back times in an array - Time' do start_time = Time.now schedule = Schedule.new(start_time) schedule = Schedule.from_yaml(schedule.to_yaml) # round trip ice_cube_start_time = schedule.start_time expect(ice_cube_start_time.to_s).to eq(start_time.to_s) expect(ice_cube_start_time.class).to eq(Time) expect(ice_cube_start_time.utc_offset).to eq(start_time.utc_offset) end it 'should be able to go back and forth to yaml and then call occurrences' do start_time = Time.local(2011, 5, 10, 12, 0, 0) schedule1 = Schedule.new(start_time) schedule1.add_recurrence_time start_time schedule2 = Schedule.from_yaml(schedule1.to_yaml) # round trip end_time = Time.now + ONE_DAY expect(schedule1.occurrences(end_time)).to eq(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 expect(schedule.extimes.map(&:to_s)).to eq([time.to_s]) end it 'crazy shit' do start_time = Time.zone.now schedule = Schedule.new(start_time) schedule.add_recurrence_rule Rule.weekly.day(:wednesday) schedule.add_recurrence_time start_time schedule = Schedule.from_hash(schedule.to_hash) schedule = Schedule.from_yaml(schedule.to_yaml) schedule.occurrences(start_time + 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 expect(Schedule.from_hash(schedule.to_hash).duration).to eq(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 } expect do hash.to_yaml end.not_to 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) expect(rt_schedule.start_time.utc_offset).to eq(schedule.start_time.utc_offset) end it 'should be backward compatible with old yaml Time format', expect_warnings: true do yaml = "---\n:end_time:\n:rdates: []\n:rrules: []\n:duration:\n:exdates: []\n:start_time: 2010-10-18T14:35:47-07:00" schedule = Schedule.from_yaml(yaml) expect(schedule.start_time).to be_a(Time) end it 'should work to_yaml with non-TimeWithZone' do schedule = Schedule.new(Time.now) expect(schedule.to_yaml.length).to 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 expect(schedule.occurs_on?(schedule.start_time.to_date + 6)).to be_falsey expect(schedule.occurs_on?(schedule.start_time.to_date + 7)).to be_truthy expect(schedule.occurs_on?(schedule.start_time.to_date + 8)).to be_falsey 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 expect(schedule.occurs_on?(schedule.start_time.to_date + 6)).to be_falsey expect(schedule.occurs_on?(schedule.start_time.to_date + 7)).to be_truthy expect(schedule.occurs_on?(schedule.start_time.to_date + 8)).to be_falsey 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) expect(schedule.occurs_on?(Date.new(2010, 10, 20))).to be_falsey expect(schedule.occurs_on?(Date.new(2010, 10, 21))).to be_truthy expect(schedule.occurs_on?(Date.new(2010, 10, 22))).to be_falsey 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) expect(rule.until_time.utc_offset).to eq(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 expect(rule.occurrence_count).to eq(5) end it 'should be able to bring a Rule to_yaml and back with an until Date' do rule = Rule.daily.until(Date.today >> 1) rule = Rule.from_yaml rule.to_yaml expect(rule.until_time).to eq(Date.today >> 1) end it 'should be able to bring a Rule to_yaml and back with an until Time' do t1 = Time.now + ONE_HOUR rule = Rule.daily.until(t1) rule = Rule.from_yaml rule.to_yaml expect(rule.until_time).to eq(t1) end it 'should be able to bring a Rule to_yaml and back with an until TimeWithZone' do Time.zone = "America/Vancouver" t1 = Time.zone.now + ONE_HOUR rule = Rule.daily.until(t1) rule = Rule.from_yaml rule.to_yaml expect(rule.until_time).to eq(t1) 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 expect(rule.week_start).to eq(: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 expect(rule.week_start).to eq(: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_time => time, :rrules => [ { :validations => { :day => [1] }, :rule_type => "IceCube::DailyRule", :interval => 1 } ], :rtimes => [], :extimes => [] } string_data = { 'start_time' => time, 'rrules' => [ { 'validations' => { 'day' => [1] }, 'rule_type' => "IceCube::DailyRule", 'interval' => 1 } ], 'rtimes' => [], 'extimes' => [] } symbol_yaml = Schedule.from_hash(symbol_data).to_yaml string_yaml = Schedule.from_hash(string_data).to_yaml expect(YAML.load(symbol_yaml)).to eq(YAML.load(string_yaml)) end it 'should raise an ArgumentError when trying to deserialize an invalid rule type' do data = {:rule_type => 'IceCube::FakeRule', :interval => 1} expect { Rule.from_hash(data) }.to raise_error(ArgumentError, 'Invalid rule frequency type: Fake') end it 'should raise an ArgumentError when trying to deserialize an invalid validation' do data = {:validations => {:fake => []}, :rule_type => 'IceCube::DailyRule', :interval => 1} expect { Rule.from_hash(data) }.to raise_error(ArgumentError, 'Invalid rule validation type: fake') end end end ice_cube-0.16.4/spec/examples/validated_rule_spec.rb000066400000000000000000000031701413434150700224360ustar00rootroot00000000000000require '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 t0 = Time.new(2013, 2, 25, 0, 0, 0) expect(rule.next_time(t0, t0, nil)).to eq(t0) end it "Should return the next month when starting one second in the future" do t0 = Time.new(2013, 2, 25, 0, 0, 0) t1 = Time.new(2013, 3, 25, 0, 0, 0) expect(rule.next_time(t0 + 1, t0, nil)).to eq t1 end it 'should return the next month near end of longer month [#171]' do t0 = Time.new(2013, 1, 1) t1 = Time.new(2013, 2, 1) [27, 28, 29, 30, 31].each do |day| expect(rule.next_time(Time.new(2013, 1, day), t0, nil)).to eq t1 end end context "DST edge", system_time_zone: "Europe/London" do let(:t0) { Time.local(2013, 3, 31) } it "should not return the same time on a DST edge when starting one second in the future (results in infinite loop [#98])" do expect(rule.next_time(t0 + 1, t0, nil)).to eq Time.local(2013, 4, 30) end it "previous failing test with DST edge taken into account" do expect(rule.next_time(t0 + ONE_HOUR + 1, t0, nil)).to eq Time.local(2013, 4, 30) end end end it 'should match times with usec' do t0 = Time.new(2012, 12, 21, 12, 21, 12.12121212) rule = IceCube::Rule.secondly expect(rule.next_time(t0 + 1, t0, nil)).to eq(t0 + 1) end end end ice_cube-0.16.4/spec/examples/weekly_rule_spec.rb000066400000000000000000000366061413434150700220130ustar00rootroot00000000000000require 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") expect(rule.validations_for(:interval).first.interval).to eq(2) end it 'converts a string integer to an actual int when using the initializer' do rule = Rule.weekly("3") expect(rule.validations_for(:interval).first.interval).to eq(3) end it 'raises an argument error when a bad value is passed' do expect { Rule.weekly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { Rule.weekly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end end describe WeeklyRule do context 'in Vancouver time', :system_time_zone => 'America/Vancouver' do it 'should include nearest time in DST start hour' do schedule = Schedule.new(Time.local(2013, 3, 3, 2, 30, 0)) schedule.add_recurrence_rule Rule.weekly expect(schedule.first(3)).to eq([ 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(Time.local(2013, 10, 27, 2, 30, 0)) schedule.add_recurrence_rule Rule.weekly expect(schedule.first(3)).to eq([ 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 t0 = Time.new(2013, 1, 1) rule = Rule.weekly(7) rule.interval(2) expect(rule.next_time(t0 + 1, t0, nil)).to eq(t0 + 2 * ONE_WEEK) 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) expect(times.size).to eq(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 expect(schedule.occurrences(t0 + 4 * ONE_WEEK).size).to eq(8) end it 'should set days from symbol args' do schedule = Schedule.new(WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day(:monday, :wednesday) expect(schedule.rrules.first.validations_for(:day).map(&:day)).to eq([1, 3]) end it 'should set days from array of symbols' do schedule = Schedule.new(WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day([:monday, :wednesday]) expect(schedule.rrules.first.validations_for(:day).map(&:day)).to eq([1, 3]) end it 'should set days from integer args' do schedule = Schedule.new(WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day(1, 3) expect(schedule.rrules.first.validations_for(:day).map(&:day)).to eq([1, 3]) end it 'should set days from array of integers' do schedule = Schedule.new(WEDNESDAY) schedule.add_recurrence_rule Rule.weekly.day([1, 3]) expect(schedule.rrules.first.validations_for(:day).map(&:day)).to eq([1, 3]) end it 'should raise an error on invalid input' do schedule = Schedule.new(WEDNESDAY) expect { schedule.add_recurrence_rule Rule.weekly.day(["1", "3"]) }.to raise_error(ArgumentError) end it 'should ignore weekday validation when no days are specified' do schedule = Schedule.new(t0 = WEDNESDAY) schedule.add_recurrence_rule Rule.weekly(2).day([]) times = schedule.occurrences(t0 + 3 * ONE_WEEK) expect(times).to eq [t0, t0 + 2 * ONE_WEEK] 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) expect(times).to eq([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) expect(times).to eq([t0, t0 + 2 * ONE_WEEK]) end it 'should occur every 2nd tuesday of a month' do schedule = Schedule.new(Time.now) schedule.add_recurrence_rule Rule.monthly.hour_of_day(11).day_of_week(:tuesday => [2]) schedule.first(48).each do |d| expect(d.hour).to eq(11) expect(d.wday).to eq(2) end end it 'should be able to start on sunday but repeat on wednesdays' do schedule = Schedule.new(Time.local(2010, 8, 1)) schedule.add_recurrence_rule Rule.weekly.day(:monday) expect(schedule.first(3)).to eq([ Time.local(2010, 8, 2), Time.local(2010, 8, 9), Time.local(2010, 8, 16) ]) end # February 2012 # Su Mo Tu We Th Fr Sa # 1 2 3 4 # 5 6 7 8 9 10 11 # 12 13 14 15 16 17 18 # 19 20 21 22 23 24 25 # 26 27 28 29 it 'should start weekly rules on monday when monday is the week start' do schedule = Schedule.new(Time.local(2012, 2, 7)) schedule.add_recurrence_rule Rule.weekly(2, :monday).day(:tuesday, :sunday) expect(schedule.first(3)).to eq([ 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(Time.local(2012,2,7)) schedule.add_recurrence_rule Rule.weekly(2).day(:tuesday, :sunday) expect(schedule.first(3)).to eq([ Time.local(2012, 2, 7), Time.local(2012, 2, 19), Time.local(2012, 2, 21) ]) end it 'should find the next date on a biweekly sunday searching from a few days before the date' do t0 = Time.utc(2017, 1, 15, 9, 0, 0) t1 = Time.utc(2017, 1, 24) t2 = t0 + (2 * ONE_WEEK) schedule = Schedule.new(t0, :duration => IceCube::ONE_HOUR) schedule.add_recurrence_rule Rule.weekly(2, :sunday).day(:sunday) t3 = schedule.next_occurrence(t1, :spans => true) expect(t3).to eq(t2) end # March 2016 # Su Mo Tu We Th Fr Sa # 1 2 3 4 5 # 6 7 8 9 10 11 12 # 13 14 15 16 17 18 19 # 20 21 22 23 24 25 26 # 27 28 29 30 31 it 'finds correct next_occurrence for biweekly rules realigned from beginning of start week' do schedule = IceCube::Schedule.new(Time.utc(2016, 3, 3)) schedule.add_recurrence_rule IceCube::Rule.weekly(2).day(:sunday) result = schedule.next_occurrence(Time.utc(2016, 3, 3)) expect(result).to eq Time.utc(2016, 3, 13) end # January 2017 # Su Mo Tu We Th Fr Sa # 1 2 3 4 5 6 7 # 8 9 10 11 12 13 14 # 15 16 17 18 19 20 21 # 22 23 24 25 26 27 28 # 29 30 31 it 'finds correct next_occurrence for biweekly rules realigned from skipped week' do schedule = IceCube::Schedule.new(Time.utc(2017, 1, 2)) schedule.add_recurrence_rule IceCube::Rule.weekly(2).day(:monday, :tuesday) result = schedule.next_occurrence(Time.utc(2017, 1, 9)) expect(result).to eq Time.utc(2017, 1, 16) end it 'finds correct previous_occurrence for biweekly rule realigned from skipped week' do schedule = IceCube::Schedule.new(Time.utc(2017, 1, 2)) schedule.add_recurrence_rule IceCube::Rule.weekly(2).day(:monday, :tuesday) result = schedule.previous_occurrence(Time.utc(2017, 1, 9)) expect(result).to eq Time.utc(2017, 1, 3) end it 'should validate week_start input' do expect { Rule.weekly(2, :someday) }.to raise_error(ArgumentError) end it 'should produce correct days for bi-weekly interval, starting on a non-sunday' do schedule = IceCube::Schedule.new(Time.local(2015, 3, 3)) schedule.add_recurrence_rule IceCube::Rule.weekly(2, :monday).day(:tuesday) range_start = Time.local(2015, 3, 15) times = schedule.occurrences_between(range_start, range_start + IceCube::ONE_WEEK) expect(times.first).to eq Time.local(2015, 3, 17) end it 'should produce correct days for monday-based bi-weekly interval, starting on a sunday' do schedule = IceCube::Schedule.new(Time.local(2015, 3, 1)) schedule.add_recurrence_rule IceCube::Rule.weekly(2, :monday).day(:sunday) range_start = Time.local(2015, 3, 1) times = schedule.occurrences_between(range_start, range_start + IceCube::ONE_WEEK) expect(times.first).to eq Time.local(2015, 3, 1) end it "should stay aligned to the start week when selecting occurrences with the spans option" do t0 = Time.local(2017, 1, 15) schedule = IceCube::Schedule.new(t0, duration: ONE_HOUR) schedule.add_recurrence_rule IceCube::Rule.weekly(2).day(:sunday) ts = schedule.occurrences_between(t0, t0 + ONE_WEEK * 4, spans: true) expect(ts).to eq([t0, t0 + ONE_WEEK * 2, t0 + ONE_WEEK * 4]) end context "with Monday week start" do # June 2017 # Mo Tu We Th Fr Sa Su # 1 2 3 4 # 5 6 7 8 9 10 11 # 12 13 14 15 16 17 18 # 19 20 21 22 23 24 25 # 26 27 28 29 30 it "should align next_occurrences with first valid weekday when schedule starts on a Monday" do schedule = IceCube::Schedule.new(Time.utc(2017, 6, 5)) except_tuesday = [:monday, :wednesday, :thursday, :friday, :saturday, :sunday] schedule.add_recurrence_rule IceCube::Rule.weekly(2, :monday).day(except_tuesday) sample = [ Time.utc(2017, 6, 5), Time.utc(2017, 6, 7), Time.utc(2017, 6, 8), Time.utc(2017, 6, 9), Time.utc(2017, 6, 10), Time.utc(2017, 6, 11), Time.utc(2017, 6, 19) ] expect(schedule.first(7)).to eq sample expect(schedule.next_occurrences(3, sample[4] - 1)).to eq sample[4..6] end it "should align next_occurrence with first valid weekday when schedule starts on a Monday" do t0 = Time.utc(2017, 6, 5) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.weekly(2, :monday).day(:monday, :thursday) sample = [ Time.utc(2017, 6, 5), Time.utc(2017, 6, 8), Time.utc(2017, 6, 19), Time.utc(2017, 6, 22) ] expect(schedule.first(4)).to eq(sample) expect(schedule.next_occurrence(sample[2] - 1)).to eq(sample[2]) end it "should align next_occurrence with first valid weekday when schedule starts on a Wednesday" do t0 = Time.utc(2017, 6, 7) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.weekly(2, :monday).day(:wednesday, :sunday) sample = [ Time.utc(2017, 6, 7), Time.utc(2017, 6, 11), Time.utc(2017, 6, 21), Time.utc(2017, 6, 25) ] expect(schedule.first(4)).to eq sample expect(schedule.next_occurrence(sample[0] + 7 * ONE_DAY)).to eq sample[2] expect(schedule.next_occurrence(sample[2] + ONE_DAY)).to eq sample[3] end end it "should align next_occurrence with the earliest hour validation" do t0 = Time.utc(2017, 7, 28, 20, 30, 40) schedule = IceCube::Schedule.new(t0) schedule.add_recurrence_rule IceCube::Rule.weekly.day(:saturday).hour_of_day(19).minute_of_hour(29).second_of_minute(39) expect(schedule.next_occurrence(t0)).to eq Time.utc(2017, 7, 29, 19, 29, 39) end describe "using occurs_between with a biweekly schedule" do [[0, 1, 2], [0, 6, 1], [5, 1, 6], [6, 5, 7]].each do |wday, offset, lead| start_time = Time.utc(2014, 1, 5, 9, 0, 0) expected_time = start_time + (IceCube::ONE_DAY * 14) offset_wday = (wday + offset) % 7 context "starting on weekday #{wday} selecting weekday #{offset} with a #{lead} day advance window" do let(:biweekly) { IceCube::Rule.weekly(2).day(0, 1, 2, 3, 4, 5, 6) } let(:schedule) { IceCube::Schedule.new(start_time + (IceCube::ONE_DAY * wday), :duration => IceCube::ONE_HOUR) { |s| s.rrule biweekly } } let(:expected_date) { expected_time + (IceCube::ONE_DAY * offset_wday) } let(:range) { [expected_date - (IceCube::ONE_DAY * lead), expected_date] } it "should include weekday #{offset_wday} of the expected week" do expect(schedule.occurrences_between(range.first, range.last)).to include expected_date end it "should include weekday #{offset_wday} of the expected week with the spans option" do expect(schedule.occurrences_between(range.first, range.last, spans: true)).to include expected_date end end end # August 2018 # Su Mo Tu We Th Fr Sa # 1 2 3 4 # 5 6 7 8 9 10 11 # 12 13 14 15 16 17 18 # 19 20 21 22 23 24 25 # 26 27 28 29 30 31 context 'when day of start_time does not align with specified day rule' do let(:start_time) { Time.utc(2018, 8, 7, 10, 0, 0) } let(:end_time) { Time.utc(2018, 8, 7, 15, 0, 0) } let(:biweekly) { IceCube::Rule.weekly(2).day(:saturday).hour_of_day(10) } let(:schedule) { IceCube::Schedule.new(start_time, end_time: end_time) { |s| s.rrule biweekly } } let(:range) { [Time.utc(2018, 8, 11, 7, 0, 0), Time.utc(2018, 8, 12, 6, 59, 59)] } let(:expected_date) { Time.utc(2018, 8, 11, 10, 0, 0) } it 'should align to the correct day with the spans option' do expect(schedule.occurrences_between(range.first, range.last, spans: true)).to include expected_date end end end describe "using occurs_between with a weekly schedule" do [[6, 5, 7]].each do |wday, offset, lead| start_week = Time.utc(2014, 1, 5) expected_week = start_week + ONE_WEEK offset_wday = (wday + offset) % 7 context "starting on weekday #{wday} selecting weekday #{offset} with a #{lead} day advance window" do let(:weekly) { IceCube::Rule.weekly(1).day(0, 1, 2, 3, 4, 5, 6) } let(:schedule) { IceCube::Schedule.new(start_week + wday * IceCube::ONE_DAY) { |s| s.rrule weekly } } let(:expected_date) { expected_week + offset_wday * IceCube::ONE_DAY } let(:range) { [expected_date - lead * ONE_DAY, expected_date] } it "should include weekday #{offset_wday} of the expected week" do wday_of_start_week = start_week + wday * IceCube::ONE_DAY expect(schedule.occurrences_between(range.first, range.last)).to include expected_date expect(schedule.occurrences_between(range.first, range.last).first).to eq(wday_of_start_week) end end end end end end ice_cube-0.16.4/spec/examples/yearly_rule_spec.rb000066400000000000000000000075701413434150700220160ustar00rootroot00000000000000require 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 = IceCube::Rule.yearly.interval("2") expect(rule.validations_for(:interval).first.interval).to eq(2) end it 'converts a string integer to an actual int when using the initializer' do rule = IceCube::Rule.yearly("3") expect(rule.validations_for(:interval).first.interval).to eq(3) end it 'raises an argument error when a bad value is passed' do expect { Rule.yearly("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end it 'raises an argument error when a bad value is passed using the interval method' do expect { Rule.yearly.interval("invalid") }.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.") end end describe IceCube::YearlyRule do it 'should update previous interval' do t0 = Time.utc(2013, 5, 1) rule = Rule.yearly(3) rule.interval(1) expect(rule.next_time(t0 + 1, t0, nil)).to eq(t0 + (IceCube::ONE_DAY * 365)) end it 'should be able to specify complex yearly rules' do start_time = Time.local(2010, 7, 12, 5, 0, 0) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:april).day_of_week(:monday => [1, -1]) one_year = 365 * IceCube::ONE_DAY expect(schedule.occurrences(start_time + one_year).size).to eq(2) end it 'should produce the correct number of days for @interval = 1' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.yearly #check assumption expect(schedule.occurrences(start_time + 370 * IceCube::ONE_DAY).size).to eq(2) end it 'should produce the correct number of days for @interval = 2' do start_time = Time.now schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.yearly(2) #check assumption expect(schedule.occurrences(start_time + 370 * IceCube::ONE_DAY)).to eq([start_time]) end it 'should produce the correct days for @interval = 1 when you specify months' do start_time = Time.utc(2010, 1, 1) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:january, :april, :november) months_of_year = [Time.utc(2010,1,1), Time.utc(2010,4,1), Time.utc(2010,11,1)] expect(schedule.occurrences(Time.utc(2010, 12, 31))).to eq months_of_year end it 'should produce the correct days for @interval = 1 when you specify days' do start_time = Time.utc(2010, 1, 1) schedule = IceCube::Schedule.new(start_time) schedule.add_recurrence_rule IceCube::Rule.yearly.day_of_year(155, 200) days_of_year = [Time.utc(2010, 6, 4), Time.utc(2010, 7, 19)] expect(schedule.occurrences(Time.utc(2010, 12, 31))).to eq days_of_year end it 'should produce the correct 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) days_of_year = [Time.utc(2010, 4, 10), Time.utc(2010, 12, 31)] expect(schedule.occurrences(Time.utc(2010, 12, 31))).to eq days_of_year end it 'should handle negative offset day of year for leap years' do schedule = IceCube::Schedule.new(Time.utc(2010, 1, 1)) schedule.add_recurrence_rule IceCube::Rule.yearly.day_of_year(-1) days_of_year = [Time.utc(2010, 12, 31), Time.utc(2011, 12, 31), Time.utc(2012, 12, 31), Time.utc(2013, 12, 31), Time.utc(2014, 12, 31)] expect(schedule.occurrences(Time.utc(2014, 12, 31))).to eq days_of_year end end ice_cube-0.16.4/spec/spec_helper.rb000066400000000000000000000034361413434150700171200ustar00rootroot00000000000000require "bundler/setup" require 'ice_cube' require 'timeout' begin require 'simplecov' SimpleCov.start rescue LoadError # okay end IceCube.compatibility = 12 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 ] # TODO: enable warnings here and update specs to call IceCube objects correctly def Object.const_missing(sym) case sym when :Schedule, :Rule, :Occurrence, :TimeUtil, :ONE_DAY, :ONE_HOUR, :ONE_MINUTE # warn "Use IceCube::#{sym}", caller[0] IceCube.const_get(sym) else super end end RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" config.expect_with :rspec do |c| c.syntax = :expect end Dir[File.dirname(__FILE__) + '/support/**/*'].each { |f| require f } config.warnings = true config.include WarningHelpers config.before :each do |example| if example.metadata[:requires_active_support] raise 'ActiveSupport required but not present' unless defined?(ActiveSupport) end end config.around :each, system_time_zone: true do |example| orig_zone = ENV['TZ'] ENV['TZ'] = example.metadata[:system_time_zone] example.run ENV['TZ'] = orig_zone end config.around :each, locale: true do |example| orig_locale = I18n.locale I18n.locale = example.metadata[:locale] example.run I18n.locale = orig_locale end config.around :each, expect_warnings: true do |example| capture_warnings do example.run end end config.around :each do |example| Timeout.timeout(example.metadata.fetch(:timeout, 1)) do example.run end end end ice_cube-0.16.4/spec/support/000077500000000000000000000000001413434150700160105ustar00rootroot00000000000000ice_cube-0.16.4/spec/support/warning_helpers.rb000066400000000000000000000003231413434150700215220ustar00rootroot00000000000000module WarningHelpers def capture_warnings StringIO.open("") do |stderr| stderr, $stderr = $stderr, stderr yield stderr, $stderr = $stderr, stderr stderr.string end end end