montrose 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile +0 -1
- data/README.md +135 -123
- data/lib/montrose.rb +4 -2
- data/lib/montrose/chainable.rb +14 -0
- data/lib/montrose/clock.rb +16 -3
- data/lib/montrose/frequency.rb +1 -1
- data/lib/montrose/options.rb +31 -21
- data/lib/montrose/rule.rb +1 -0
- data/lib/montrose/rule/except.rb +23 -0
- data/lib/montrose/rule/time_of_day.rb +1 -5
- data/lib/montrose/stack.rb +1 -0
- data/lib/montrose/utils.rb +17 -0
- data/lib/montrose/version.rb +1 -1
- metadata +4 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a88cd103fc68290cf832c8e41ec2e8a1ca4d4a87
         | 
| 4 | 
            +
              data.tar.gz: c74c51dad1844316155c1b138b0b34f548d27dfd
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 3d186764038aaeb902fb809225a5e26779831e55a3ed0cc11998a6395821c9ab4e34af9d0152f43558f660c39d02ad9e2eff82630e24d56986e1958d0ff51de6
         | 
| 7 | 
            +
              data.tar.gz: 4c939c9c4e6a416f20fb24b718d6fab5026b17650f72e5769b03abaa40ecc10d002a6a8210cd0ec6ae44c832d41020a222cf5e248597ede1754d830e8587d92b
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,14 @@ | |
| 1 | 
            +
            ### 0.3.0 - (2016-02-19)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * enhancements
         | 
| 4 | 
            +
              * Adds `:except` option and chainable method to filter timestamps by date (by
         | 
| 5 | 
            +
                @thewatts)
         | 
| 6 | 
            +
            * bug fixes
         | 
| 7 | 
            +
              * Fix recurrences when specifying both `:starts` and `:at` by treating
         | 
| 8 | 
            +
                `:starts` value like a date
         | 
| 9 | 
            +
              * Respect recurrence rules using multiple `:at` values
         | 
| 10 | 
            +
              * Using `Montrose.r` without any arguments no longer throws `ArgumentError`
         | 
| 11 | 
            +
             | 
| 1 12 | 
             
            ### 0.2.2 - 2016-02-08
         | 
| 2 13 |  | 
| 3 14 | 
             
            * bug fixes
         | 
| @@ -5,6 +16,7 @@ | |
| 5 16 | 
             
            * enhancements
         | 
| 6 17 | 
             
              * Adds `Montrose.r` method for starting a new recurrence
         | 
| 7 18 | 
             
              * Adds `Chainable` alias methods including `#starts`, `#until`, `#repeat`
         | 
| 19 | 
            +
              * README updates (by @thegcat)
         | 
| 8 20 |  | 
| 9 21 | 
             
            ### 0.2.1 - 2016-02-03
         | 
| 10 22 |  | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -7,7 +7,9 @@ | |
| 7 7 |  | 
| 8 8 | 
             
            Montrose is an easy-to-use library for defining recurring events in Ruby. It uses a simple chaining system for building recurrences, inspired heavily by the design principles of [HTTP.rb](https://github.com/httprb/http) and rule definitions available in [Recurrence](https://github.com/fnando/recurrence).
         | 
| 9 9 |  | 
| 10 | 
            -
            [ | 
| 10 | 
            +
            * [Introductory blog post](http://bit.ly/1PA68Zb)
         | 
| 11 | 
            +
            * [NYC.rb
         | 
| 12 | 
            +
              presentation](https://speaderdeck.com/rossta/recurring-events-with-montrose)
         | 
| 11 13 |  | 
| 12 14 | 
             
            ## Installation
         | 
| 13 15 |  | 
| @@ -25,6 +27,135 @@ Or install it yourself as: | |
| 25 27 |  | 
| 26 28 | 
             
                $ gem install montrose
         | 
| 27 29 |  | 
| 30 | 
            +
            ## Why
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            Dealing with recurring events is hard. `Montrose` provides a simple interface for specifying and enumerating recurring events as `Time` objects.
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            More specifically, this project intends to:
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            * embrace Ruby idioms
         | 
| 37 | 
            +
            * support Ruby 2.1+
         | 
| 38 | 
            +
            * be reasonably performant
         | 
| 39 | 
            +
            * serialize to yaml, hash, and [ical](http://www.kanzaki.com/docs/ical/rrule.html#basic) formats
         | 
| 40 | 
            +
            * be suitable for integration with persistence libraries
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            What `Montrose` doesn't do:
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            * support all calendaring use cases under the sun
         | 
| 45 | 
            +
            * schedule recurring jobs for you. See instead [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler), [sidekiq-cron](https://github.com/ondrejbartas/sidekiq-cron), [sidetiq](https://github.com/tobiassvn/sidetiq), [whenever](https://github.com/javan/whenever)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            ## Concepts
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            Montrose allows you to easily create "recurrence" objects through chaining:
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            ```ruby
         | 
| 52 | 
            +
            # Every Monday at 10:30am
         | 
| 53 | 
            +
            Montrose.weekly.on(:monday).at("10:30 am")
         | 
| 54 | 
            +
            => #<Montrose::Recurrence...>
         | 
| 55 | 
            +
            ```
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            Each chained recurrence returns a **new object** so they can be composed and merged. In both examples below, recurrence `r4` represents 'every week on Tuesday and Thursday at noon for four occurrences'.
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            ```ruby
         | 
| 60 | 
            +
            # Example 1 - building recurrence in succession
         | 
| 61 | 
            +
            r1 = Montrose.every(:week)
         | 
| 62 | 
            +
            r2 = r1.on([:tuesday, :thursday])
         | 
| 63 | 
            +
            r3 = r2.at("12 pm")
         | 
| 64 | 
            +
            r4 = r3.total(4)
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            # Example 2 - merging distinct recurrences
         | 
| 67 | 
            +
            r1 = Montrose.every(:week)
         | 
| 68 | 
            +
            r2 = Montrose.on([:tuesday, :thursday])
         | 
| 69 | 
            +
            r3 = Montrose.at("12 pm")
         | 
| 70 | 
            +
            r4 = r1.merge(r2).merge(r3).total(4)
         | 
| 71 | 
            +
            ```
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            Most recurrence methods accept additional options if you favor the hash-syntax:
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            ```ruby
         | 
| 76 | 
            +
            Montrose.r(every: :week, on: :monday, at: "10:30 am")
         | 
| 77 | 
            +
            => #<Montrose::Recurrence...>
         | 
| 78 | 
            +
            ```
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            See [the docs for `Montrose::Chainable`](https://rossta.net/montrose/Montrose/Chainable.html) for more info on recurrence creation methods.
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            A Montrose recurrence responds to `#events`, which returns an [`Enumerator`](/blog/what-is-enumerator.html) that can generate timestamps:
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            ```ruby
         | 
| 85 | 
            +
            r = Montrose.hourly
         | 
| 86 | 
            +
            => #<Montrose::Recurrence...>
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            r.events
         | 
| 89 | 
            +
            => #<Enumerator:...>
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            r.events.take(10)
         | 
| 92 | 
            +
            => [2016-02-03 18:26:08 -0500,
         | 
| 93 | 
            +
            2016-02-03 19:26:08 -0500,
         | 
| 94 | 
            +
            2016-02-03 20:26:08 -0500,
         | 
| 95 | 
            +
            2016-02-03 21:26:08 -0500,
         | 
| 96 | 
            +
            2016-02-03 22:26:08 -0500,
         | 
| 97 | 
            +
            2016-02-03 23:26:08 -0500,
         | 
| 98 | 
            +
            2016-02-04 00:26:08 -0500,
         | 
| 99 | 
            +
            2016-02-04 01:26:08 -0500,
         | 
| 100 | 
            +
            2016-02-04 02:26:08 -0500,
         | 
| 101 | 
            +
            2016-02-04 03:26:08 -0500]
         | 
| 102 | 
            +
            ```
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            Montrose recurrences are themselves enumerable:
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            ```ruby
         | 
| 107 | 
            +
            # Every month starting a year from now on Friday the 13th for 5 occurrences
         | 
| 108 | 
            +
            r = Montrose.monthly.starting(1.year.from_now).on(friday: 13).repeat(5)
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            r.map(&:to_date)
         | 
| 111 | 
            +
            => [Fri, 13 Oct 2017,
         | 
| 112 | 
            +
            Fri, 13 Apr 2018,
         | 
| 113 | 
            +
            Fri, 13 Jul 2018,
         | 
| 114 | 
            +
            Fri, 13 Sep 2019,
         | 
| 115 | 
            +
            Fri, 13 Dec 2019]
         | 
| 116 | 
            +
            ```
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            Conceptually, recurrences can represent an infinite sequence. When we say
         | 
| 119 | 
            +
            simply "every day", there is no implied ending. It's therefore possible to
         | 
| 120 | 
            +
            create a recurrence that can enumerate forever, so use your `Enumerable` methods wisely.
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            ```ruby
         | 
| 123 | 
            +
            # Every day starting now
         | 
| 124 | 
            +
            r = Montrose.daily
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            # this expression will never complete, Ctrl-c!
         | 
| 127 | 
            +
            r.map(&:to_date)
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            # use `lazy` enumerator to avoid eager enumeration
         | 
| 130 | 
            +
            r.lazy.map(&:to_date).select { |d| d.mday > 25 }.take(5).to_a
         | 
| 131 | 
            +
            => [Fri, 26 Feb 2016,
         | 
| 132 | 
            +
            Sat, 27 Feb 2016,
         | 
| 133 | 
            +
            Sun, 28 Feb 2016,
         | 
| 134 | 
            +
            Mon, 29 Feb 2016,
         | 
| 135 | 
            +
            Sat, 26 Mar 2016]
         | 
| 136 | 
            +
            ```
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            It's straightforward to convert a recurrence to a hash and back.
         | 
| 139 | 
            +
             | 
| 140 | 
            +
            ```ruby
         | 
| 141 | 
            +
            opts = Montrose::Recurrence.new(every: 10.minutes).to_h
         | 
| 142 | 
            +
            => {:every=>:minute, :interval=>10}
         | 
| 143 | 
            +
             | 
| 144 | 
            +
            Montrose::Recurrence.new(opts).take(3)
         | 
| 145 | 
            +
            => [2016-02-03 19:06:07 -0500,
         | 
| 146 | 
            +
            2016-02-03 19:16:07 -0500,
         | 
| 147 | 
            +
            2016-02-03 19:26:07 -0500]
         | 
| 148 | 
            +
            ```
         | 
| 149 | 
            +
             | 
| 150 | 
            +
            A recurrence object must minimally specify a frequency, e.g. `:minute`, `:hour`, `:day`, `:week`, `:month`, or, `:year`, to be viable. Otherwise, you'll see an informative error message when attempting to enumerate the recurrence.
         | 
| 151 | 
            +
             | 
| 152 | 
            +
            ```ruby
         | 
| 153 | 
            +
            r = Montrose.at("12pm")
         | 
| 154 | 
            +
            => #<Montrose::Recurrence...>
         | 
| 155 | 
            +
            r.each
         | 
| 156 | 
            +
            Montrose::ConfigurationError: Please specify the :every option
         | 
| 157 | 
            +
            ```
         | 
| 158 | 
            +
             | 
| 28 159 | 
             
            ## Usage
         | 
| 29 160 |  | 
| 30 161 | 
             
            ```ruby
         | 
| @@ -60,7 +191,7 @@ Montrose.weekly(total: 10) | |
| 60 191 | 
             
            # weekly until December 23, 2015
         | 
| 61 192 | 
             
            ends_on = Date.new(2015, 12, 23)
         | 
| 62 193 | 
             
            starts_on = ends_on - 15.weeks
         | 
| 63 | 
            -
            Montrose.every(:week, until: ends_on, starts: starts_on
         | 
| 194 | 
            +
            Montrose.every(:week, until: ends_on, starts: starts_on)
         | 
| 64 195 |  | 
| 65 196 | 
             
            # every other week forever
         | 
| 66 197 | 
             
            Montrose.every(2.weeks)
         | 
| @@ -219,7 +350,6 @@ Montrose.yearly(yday: [1, 100]) # yearly on the 1st and 100th day of year | |
| 219 350 | 
             
            Montrose.yearly(on: { january: 31 })
         | 
| 220 351 | 
             
            Montrose.r(every: :year, on: { 10 => 31 }, interval: 3)
         | 
| 221 352 |  | 
| 222 | 
            -
            # TODO: Remove a date in the series with :except date(s)
         | 
| 223 353 | 
             
            Montrose.daily(:day, except: "2017-01-31")
         | 
| 224 354 | 
             
            Montrose.daily(except: [Date.today, "2017-01-31"])
         | 
| 225 355 |  | 
| @@ -242,125 +372,6 @@ r.events.take(10).each { |date| puts date.to_s } | |
| 242 372 | 
             
            r.events.lazy.select { |time| time > 1.month.from_now }.take(3).each { |date| puts date.to_s }
         | 
| 243 373 | 
             
            ```
         | 
| 244 374 |  | 
| 245 | 
            -
            ## Why?
         | 
| 246 | 
            -
             | 
| 247 | 
            -
            `Montrose` aims to provide a simple interface for specifying and enumerating recurring events as `Time` objects.
         | 
| 248 | 
            -
             | 
| 249 | 
            -
            More specifically, this project intends to:
         | 
| 250 | 
            -
             | 
| 251 | 
            -
            * embrace Ruby idioms
         | 
| 252 | 
            -
            * support Ruby 2.1+
         | 
| 253 | 
            -
            * be reasonably performant
         | 
| 254 | 
            -
            * serialize to yaml, hash, and [ical](http://www.kanzaki.com/docs/ical/rrule.html#basic) formats
         | 
| 255 | 
            -
            * be suitable for integration with persistence libraries
         | 
| 256 | 
            -
             | 
| 257 | 
            -
            What `Montrose` doesn't do:
         | 
| 258 | 
            -
             | 
| 259 | 
            -
            * support all calendaring use cases under the sun
         | 
| 260 | 
            -
            * schedule recurring jobs for you. See instead [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler), [sidetiq](https://github.com/tobiassvn/sidetiq), [whenever](https://github.com/javan/whenever)
         | 
| 261 | 
            -
             | 
| 262 | 
            -
            ## Concepts
         | 
| 263 | 
            -
             | 
| 264 | 
            -
            Montrose allows you to easily create "recurrence" objects through chaining:
         | 
| 265 | 
            -
             | 
| 266 | 
            -
            ```ruby
         | 
| 267 | 
            -
            # Every Monday at 10:30am
         | 
| 268 | 
            -
            Montrose.weekly.on(:monday).at("10:30 am")
         | 
| 269 | 
            -
            => #<Montrose::Recurrence...>
         | 
| 270 | 
            -
            ```
         | 
| 271 | 
            -
             | 
| 272 | 
            -
            Or the constructor hash-syntax:
         | 
| 273 | 
            -
             | 
| 274 | 
            -
            ```ruby
         | 
| 275 | 
            -
            Montrose.r(every: :week, on: :monday, at: "10:30 am")
         | 
| 276 | 
            -
            => #<Montrose::Recurrence...>
         | 
| 277 | 
            -
            ```
         | 
| 278 | 
            -
             | 
| 279 | 
            -
            A Montrose recurrence responds to `#events`, which returns an [`Enumerator`](/blog/what-is-enumerator.html) that can generate timestamps:
         | 
| 280 | 
            -
             | 
| 281 | 
            -
            ```ruby
         | 
| 282 | 
            -
            r = Montrose.hourly
         | 
| 283 | 
            -
            => #<Montrose::Recurrence...>
         | 
| 284 | 
            -
             | 
| 285 | 
            -
            r.events
         | 
| 286 | 
            -
            => #<Enumerator:...>
         | 
| 287 | 
            -
             | 
| 288 | 
            -
            r.events.take(10)
         | 
| 289 | 
            -
            => [2016-02-03 18:26:08 -0500,
         | 
| 290 | 
            -
            2016-02-03 19:26:08 -0500,
         | 
| 291 | 
            -
            2016-02-03 20:26:08 -0500,
         | 
| 292 | 
            -
            2016-02-03 21:26:08 -0500,
         | 
| 293 | 
            -
            2016-02-03 22:26:08 -0500,
         | 
| 294 | 
            -
            2016-02-03 23:26:08 -0500,
         | 
| 295 | 
            -
            2016-02-04 00:26:08 -0500,
         | 
| 296 | 
            -
            2016-02-04 01:26:08 -0500,
         | 
| 297 | 
            -
            2016-02-04 02:26:08 -0500,
         | 
| 298 | 
            -
            2016-02-04 03:26:08 -0500]
         | 
| 299 | 
            -
            ```
         | 
| 300 | 
            -
             | 
| 301 | 
            -
            Montrose recurrences are themselves enumerable:
         | 
| 302 | 
            -
             | 
| 303 | 
            -
            ```ruby
         | 
| 304 | 
            -
            # Every month starting a year from now on Friday the 13th for 5 occurrences
         | 
| 305 | 
            -
            r = Montrose.monthly.starting(1.year.from_now).on(friday: 13).repeat(5)
         | 
| 306 | 
            -
             | 
| 307 | 
            -
            r.map(&:to_date)
         | 
| 308 | 
            -
            => [Fri, 13 Oct 2017,
         | 
| 309 | 
            -
            Fri, 13 Apr 2018,
         | 
| 310 | 
            -
            Fri, 13 Jul 2018,
         | 
| 311 | 
            -
            Fri, 13 Sep 2019,
         | 
| 312 | 
            -
            Fri, 13 Dec 2019]
         | 
| 313 | 
            -
            ```
         | 
| 314 | 
            -
             | 
| 315 | 
            -
            Each chained recurrence returns a new object so they can be composed and
         | 
| 316 | 
            -
            merged:
         | 
| 317 | 
            -
             | 
| 318 | 
            -
            ```ruby
         | 
| 319 | 
            -
            # Every week
         | 
| 320 | 
            -
            r1 = Montrose.every(:week)
         | 
| 321 | 
            -
            r2 = Montrose.on([:tuesday, :thursday])
         | 
| 322 | 
            -
            r3 = Montrose.at("12 pm")
         | 
| 323 | 
            -
            r4 = Montrose.total(4)
         | 
| 324 | 
            -
             | 
| 325 | 
            -
            r1.merge(r2).merge(r3).merge(r4).to_a
         | 
| 326 | 
            -
            => [2016-02-04 12:00:00 -0500,
         | 
| 327 | 
            -
            2016-02-09 12:00:00 -0500,
         | 
| 328 | 
            -
            2016-02-11 12:00:00 -0500,
         | 
| 329 | 
            -
            2016-02-16 12:00:00 -0500]
         | 
| 330 | 
            -
            ```
         | 
| 331 | 
            -
             | 
| 332 | 
            -
            Conceptually, recurrences can represent an infinite sequence. When we say
         | 
| 333 | 
            -
            simply "every day", there is no implied ending. It's therefore possible to
         | 
| 334 | 
            -
            create a recurrence that can enumerate forever.
         | 
| 335 | 
            -
             | 
| 336 | 
            -
            ```ruby
         | 
| 337 | 
            -
            # Every day starting now
         | 
| 338 | 
            -
            r = Montrose.daily
         | 
| 339 | 
            -
             | 
| 340 | 
            -
            # this expression will never complete, Ctrl-c!
         | 
| 341 | 
            -
            r.map(&:to_date)
         | 
| 342 | 
            -
             | 
| 343 | 
            -
            # so use your `Enumerable` methods wisely
         | 
| 344 | 
            -
            r.lazy.map(&:to_date).select { |d| d.mday > 25 }.take(5).to_a
         | 
| 345 | 
            -
            => [Fri, 26 Feb 2016,
         | 
| 346 | 
            -
            Sat, 27 Feb 2016,
         | 
| 347 | 
            -
            Sun, 28 Feb 2016,
         | 
| 348 | 
            -
            Mon, 29 Feb 2016,
         | 
| 349 | 
            -
            Sat, 26 Mar 2016]
         | 
| 350 | 
            -
            ```
         | 
| 351 | 
            -
             | 
| 352 | 
            -
            It's straightforward to convert a recurrence to a hash and back.
         | 
| 353 | 
            -
             | 
| 354 | 
            -
            ```ruby
         | 
| 355 | 
            -
            opts = Montrose::Recurrence.new(every: 10.minutes).to_h
         | 
| 356 | 
            -
            => {:every=>:minute, :interval=>10}
         | 
| 357 | 
            -
             | 
| 358 | 
            -
            Montrose::Recurrence.new(opts).take(3)
         | 
| 359 | 
            -
            => [2016-02-03 19:06:07 -0500,
         | 
| 360 | 
            -
            2016-02-03 19:16:07 -0500,
         | 
| 361 | 
            -
            2016-02-03 19:26:07 -0500]
         | 
| 362 | 
            -
            ```
         | 
| 363 | 
            -
             | 
| 364 375 | 
             
            ## Inspiration
         | 
| 365 376 |  | 
| 366 377 | 
             
            Montrose is named after the beautifully diverse and artistic [neighborhood in Houston, Texas](https://en.wikipedia.org/wiki/Montrose,_Houston).
         | 
| @@ -369,9 +380,10 @@ Montrose is named after the beautifully diverse and artistic [neighborhood in Ho | |
| 369 380 |  | 
| 370 381 | 
             
            Check out following related projects, all of which have provided inspiration for `Montrose`.
         | 
| 371 382 |  | 
| 372 | 
            -
            * [recurrence](https://github.com/fnando/recurrence)
         | 
| 373 383 | 
             
            * [ice_cube](https://github.com/seejohnrun/ice_cube)
         | 
| 384 | 
            +
            * [recurrence](https://github.com/fnando/recurrence)
         | 
| 374 385 | 
             
            * [runt](https://github.com/mlipper/runt)
         | 
| 386 | 
            +
            * [http.rb](https://github.com/httprb/http) - not a recurrence project, but inspirational to design, implementation, and interface of `Montrose`
         | 
| 375 387 |  | 
| 376 388 | 
             
            ## Development
         | 
| 377 389 |  | 
    
        data/lib/montrose.rb
    CHANGED
    
    
    
        data/lib/montrose/chainable.rb
    CHANGED
    
    | @@ -194,6 +194,20 @@ module Montrose | |
| 194 194 | 
             
                  merge(at: time)
         | 
| 195 195 | 
             
                end
         | 
| 196 196 |  | 
| 197 | 
            +
                # Create a recurrence with dates except dates given
         | 
| 198 | 
            +
                #
         | 
| 199 | 
            +
                # @param date [String, Date] represents date
         | 
| 200 | 
            +
                #
         | 
| 201 | 
            +
                # @example
         | 
| 202 | 
            +
                #   Montrose.daily.except("2016-03-01")
         | 
| 203 | 
            +
                #   Montrose.daily.except(Date.today)
         | 
| 204 | 
            +
                #
         | 
| 205 | 
            +
                # @return [Montrose::Recurrence]
         | 
| 206 | 
            +
                #
         | 
| 207 | 
            +
                def except(date)
         | 
| 208 | 
            +
                  merge(except: date)
         | 
| 209 | 
            +
                end
         | 
| 210 | 
            +
             | 
| 197 211 | 
             
                # Create a recurrence for given days of month
         | 
| 198 212 | 
             
                #
         | 
| 199 213 | 
             
                # @param days [Fixnum] days of month, e.g. 1, 2, -1, ...
         | 
    
        data/lib/montrose/clock.rb
    CHANGED
    
    | @@ -6,8 +6,9 @@ module Montrose | |
| 6 6 | 
             
                  @options = Montrose::Options.merge(opts)
         | 
| 7 7 | 
             
                  @time = nil
         | 
| 8 8 | 
             
                  @every = @options.fetch(:every) { fail ConfigurationError, "Required option :every not provided" }
         | 
| 9 | 
            -
                  @starts = @options.fetch(:starts)
         | 
| 10 9 | 
             
                  @interval = @options.fetch(:interval)
         | 
| 10 | 
            +
                  @start_time = @options.fetch(:start_time)
         | 
| 11 | 
            +
                  @at = @options.fetch(:at, nil)
         | 
| 11 12 | 
             
                end
         | 
| 12 13 |  | 
| 13 14 | 
             
                # Advances time to new unit by increment and sets
         | 
| @@ -18,13 +19,25 @@ module Montrose | |
| 18 19 | 
             
                end
         | 
| 19 20 |  | 
| 20 21 | 
             
                def peek
         | 
| 21 | 
            -
                  return @ | 
| 22 | 
            +
                  return @start_time if @time.nil?
         | 
| 22 23 |  | 
| 23 | 
            -
                  @ | 
| 24 | 
            +
                  if @at
         | 
| 25 | 
            +
                    times = @at.map { |(hour, min)| @time.change(hour: hour, min: min) }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    min_next = times.select { |t| t > @time }.min and return min_next
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    advance_step(times.min || @time)
         | 
| 30 | 
            +
                  else
         | 
| 31 | 
            +
                    advance_step(@time)
         | 
| 32 | 
            +
                  end
         | 
| 24 33 | 
             
                end
         | 
| 25 34 |  | 
| 26 35 | 
             
                private
         | 
| 27 36 |  | 
| 37 | 
            +
                def advance_step(time)
         | 
| 38 | 
            +
                  time.advance(step)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 28 41 | 
             
                def step
         | 
| 29 42 | 
             
                  @step ||= smallest_step or fail ConfigurationError, "No step for #{@options.inspect}"
         | 
| 30 43 | 
             
                end
         | 
    
        data/lib/montrose/frequency.rb
    CHANGED
    
    
    
        data/lib/montrose/options.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            module Montrose
         | 
| 2 2 | 
             
              class Options
         | 
| 3 | 
            +
                include Montrose::Utils
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
                @default_starts = nil
         | 
| 4 6 | 
             
                @default_until = nil
         | 
| 5 7 | 
             
                @default_every = nil
         | 
| @@ -65,7 +67,6 @@ module Montrose | |
| 65 67 |  | 
| 66 68 | 
             
                  def default_options
         | 
| 67 69 | 
             
                    {
         | 
| 68 | 
            -
                      starts: default_starts,
         | 
| 69 70 | 
             
                      until: default_until,
         | 
| 70 71 | 
             
                      interval: 1
         | 
| 71 72 | 
             
                    }
         | 
| @@ -86,6 +87,7 @@ module Montrose | |
| 86 87 | 
             
                def_option :between
         | 
| 87 88 | 
             
                def_option :at
         | 
| 88 89 | 
             
                def_option :on
         | 
| 90 | 
            +
                def_option :except
         | 
| 89 91 |  | 
| 90 92 | 
             
                def initialize(opts = {})
         | 
| 91 93 | 
             
                  defaults = {
         | 
| @@ -163,7 +165,7 @@ module Montrose | |
| 163 165 | 
             
                end
         | 
| 164 166 |  | 
| 165 167 | 
             
                def day=(days)
         | 
| 166 | 
            -
                  @day = nested_map_arg(days) { |d|  | 
| 168 | 
            +
                  @day = nested_map_arg(days) { |d| day_number!(d) }
         | 
| 167 169 | 
             
                end
         | 
| 168 170 |  | 
| 169 171 | 
             
                def mday=(mdays)
         | 
| @@ -179,7 +181,7 @@ module Montrose | |
| 179 181 | 
             
                end
         | 
| 180 182 |  | 
| 181 183 | 
             
                def month=(months)
         | 
| 182 | 
            -
                  @month = map_arg(months) { |d|  | 
| 184 | 
            +
                  @month = map_arg(months) { |d| month_number!(d) }
         | 
| 183 185 | 
             
                end
         | 
| 184 186 |  | 
| 185 187 | 
             
                def between=(range)
         | 
| @@ -194,11 +196,7 @@ module Montrose | |
| 194 196 | 
             
                end
         | 
| 195 197 |  | 
| 196 198 | 
             
                def at=(time)
         | 
| 197 | 
            -
                   | 
| 198 | 
            -
                  now = Time.now
         | 
| 199 | 
            -
                  first = times.map { |t| t < now ? t + 24.hours : t }.min
         | 
| 200 | 
            -
                  self[:starts] = first if first
         | 
| 201 | 
            -
                  @at = times
         | 
| 199 | 
            +
                  @at = map_arg(time) { |t| as_time_parts(t) }
         | 
| 202 200 | 
             
                end
         | 
| 203 201 |  | 
| 204 202 | 
             
                def on=(arg)
         | 
| @@ -209,12 +207,30 @@ module Montrose | |
| 209 207 | 
             
                  @on = arg
         | 
| 210 208 | 
             
                end
         | 
| 211 209 |  | 
| 210 | 
            +
                def except=(date)
         | 
| 211 | 
            +
                  @except = map_arg(date) { |d| as_date(d) }
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
             | 
| 212 214 | 
             
                def inspect
         | 
| 213 215 | 
             
                  "#<#{self.class} #{to_h.inspect}>"
         | 
| 214 216 | 
             
                end
         | 
| 215 217 |  | 
| 218 | 
            +
                def start_time
         | 
| 219 | 
            +
                  time = starts || default_starts
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                  if at
         | 
| 222 | 
            +
                    at.map { |(hour, min)| time.change(hour: hour, min: min) }.min || time
         | 
| 223 | 
            +
                  else
         | 
| 224 | 
            +
                    time
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
                end
         | 
| 227 | 
            +
             | 
| 216 228 | 
             
                private
         | 
| 217 229 |  | 
| 230 | 
            +
                def default_starts
         | 
| 231 | 
            +
                  self.class.default_starts
         | 
| 232 | 
            +
                end
         | 
| 233 | 
            +
             | 
| 218 234 | 
             
                def nested_map_arg(arg, &block)
         | 
| 219 235 | 
             
                  case arg
         | 
| 220 236 | 
             
                  when Hash
         | 
| @@ -233,7 +249,7 @@ module Montrose | |
| 233 249 | 
             
                end
         | 
| 234 250 |  | 
| 235 251 | 
             
                def map_days(arg)
         | 
| 236 | 
            -
                  map_arg(arg) { |d|  | 
| 252 | 
            +
                  map_arg(arg) { |d| day_number!(d) }
         | 
| 237 253 | 
             
                end
         | 
| 238 254 |  | 
| 239 255 | 
             
                def map_mdays(arg)
         | 
| @@ -275,9 +291,9 @@ module Montrose | |
| 275 291 | 
             
                end
         | 
| 276 292 |  | 
| 277 293 | 
             
                def month_or_day(key)
         | 
| 278 | 
            -
                  month =  | 
| 294 | 
            +
                  month = month_number(key)
         | 
| 279 295 | 
             
                  return [:month, month] if month
         | 
| 280 | 
            -
                  day =  | 
| 296 | 
            +
                  day = day_number(key)
         | 
| 281 297 | 
             
                  return [:day, day] if day
         | 
| 282 298 | 
             
                  fail ConfigurationError, "Did not recognize #{key} as a month or day"
         | 
| 283 299 | 
             
                end
         | 
| @@ -289,17 +305,11 @@ module Montrose | |
| 289 305 | 
             
                  item
         | 
| 290 306 | 
             
                end
         | 
| 291 307 |  | 
| 292 | 
            -
                def  | 
| 293 | 
            -
                  return  | 
| 308 | 
            +
                def as_time_parts(arg)
         | 
| 309 | 
            +
                  return arg if arg.is_a?(Array)
         | 
| 294 310 |  | 
| 295 | 
            -
                   | 
| 296 | 
            -
                   | 
| 297 | 
            -
                    Time.parse(time)
         | 
| 298 | 
            -
                  when time.respond_to?(:to_time)
         | 
| 299 | 
            -
                    time.to_time
         | 
| 300 | 
            -
                  else
         | 
| 301 | 
            -
                    Array(time).flat_map { |d| as_time(d) }
         | 
| 302 | 
            -
                  end
         | 
| 311 | 
            +
                  time = as_time(arg)
         | 
| 312 | 
            +
                  [time.hour, time.min]
         | 
| 303 313 | 
             
                end
         | 
| 304 314 |  | 
| 305 315 | 
             
                def parse_frequency(input)
         | 
    
        data/lib/montrose/rule.rb
    CHANGED
    
    | @@ -38,6 +38,7 @@ require "montrose/rule/before" | |
| 38 38 | 
             
            require "montrose/rule/day_of_month"
         | 
| 39 39 | 
             
            require "montrose/rule/day_of_week"
         | 
| 40 40 | 
             
            require "montrose/rule/day_of_year"
         | 
| 41 | 
            +
            require "montrose/rule/except"
         | 
| 41 42 | 
             
            require "montrose/rule/hour_of_day"
         | 
| 42 43 | 
             
            require "montrose/rule/month_of_year"
         | 
| 43 44 | 
             
            require "montrose/rule/nth_day_of_month"
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            module Montrose
         | 
| 2 | 
            +
              module Rule
         | 
| 3 | 
            +
                class Except
         | 
| 4 | 
            +
                  include Montrose::Rule
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def self.apply_options(opts)
         | 
| 7 | 
            +
                    opts[:except]
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # Initializes rule
         | 
| 11 | 
            +
                  #
         | 
| 12 | 
            +
                  # @param [Date] dates - array of date objects
         | 
| 13 | 
            +
                  #
         | 
| 14 | 
            +
                  def initialize(dates)
         | 
| 15 | 
            +
                    @dates = dates
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def include?(time)
         | 
| 19 | 
            +
                    !@dates.include?(time.to_date)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -16,7 +16,7 @@ module Montrose | |
| 16 16 | 
             
                  end
         | 
| 17 17 |  | 
| 18 18 | 
             
                  def include?(time)
         | 
| 19 | 
            -
                     | 
| 19 | 
            +
                    @times.include?(parts(time))
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 |  | 
| 22 22 | 
             
                  private
         | 
| @@ -24,10 +24,6 @@ module Montrose | |
| 24 24 | 
             
                  def parts(time)
         | 
| 25 25 | 
             
                    [time.hour, time.min]
         | 
| 26 26 | 
             
                  end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                  def times_of_day
         | 
| 29 | 
            -
                    @times_of_day ||= @times.map { |t| parts(t) }
         | 
| 30 | 
            -
                  end
         | 
| 31 27 | 
             
                end
         | 
| 32 28 | 
             
              end
         | 
| 33 29 | 
             
            end
         | 
    
        data/lib/montrose/stack.rb
    CHANGED
    
    
    
        data/lib/montrose/utils.rb
    CHANGED
    
    | @@ -5,6 +5,23 @@ module Montrose | |
| 5 5 | 
             
                MONTHS = Date::MONTHNAMES
         | 
| 6 6 | 
             
                DAYS = Date::DAYNAMES
         | 
| 7 7 |  | 
| 8 | 
            +
                def as_time(time)
         | 
| 9 | 
            +
                  return nil unless time
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  case
         | 
| 12 | 
            +
                  when time.is_a?(String)
         | 
| 13 | 
            +
                    Time.parse(time)
         | 
| 14 | 
            +
                  when time.respond_to?(:to_time)
         | 
| 15 | 
            +
                    time.to_time
         | 
| 16 | 
            +
                  else
         | 
| 17 | 
            +
                    Array(time).flat_map { |d| as_time(d) }
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def as_date(time)
         | 
| 22 | 
            +
                  as_time(time).to_date
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 8 25 | 
             
                def month_number(name)
         | 
| 9 26 | 
             
                  case name
         | 
| 10 27 | 
             
                  when Symbol, String
         | 
    
        data/lib/montrose/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: montrose
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ross Kaffenberger
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2016-02- | 
| 11 | 
            +
            date: 2016-02-20 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activesupport
         | 
| @@ -153,6 +153,7 @@ files: | |
| 153 153 | 
             
            - lib/montrose/rule/day_of_month.rb
         | 
| 154 154 | 
             
            - lib/montrose/rule/day_of_week.rb
         | 
| 155 155 | 
             
            - lib/montrose/rule/day_of_year.rb
         | 
| 156 | 
            +
            - lib/montrose/rule/except.rb
         | 
| 156 157 | 
             
            - lib/montrose/rule/hour_of_day.rb
         | 
| 157 158 | 
             
            - lib/montrose/rule/month_of_year.rb
         | 
| 158 159 | 
             
            - lib/montrose/rule/nth_day_matcher.rb
         | 
| @@ -186,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 186 187 | 
             
                  version: '0'
         | 
| 187 188 | 
             
            requirements: []
         | 
| 188 189 | 
             
            rubyforge_project: 
         | 
| 189 | 
            -
            rubygems_version: 2.5.1
         | 
| 190 | 
            +
            rubygems_version: 2.4.5.1
         | 
| 190 191 | 
             
            signing_key: 
         | 
| 191 192 | 
             
            specification_version: 4
         | 
| 192 193 | 
             
            summary: Recurring events in Ruby
         |