Montrose Ruby Library

repository·main·Indexed 21 days ago

https://github.com/rossta/montrose

A Ruby library for defining and enumerating recurring events using a chainable interface. It models recurrences as Time objects and supports serialization to YAML, Hash, and iCal. Key features include high-level methods for daily, weekly, monthly, and yearly patterns, a Schedule class for combining multiple rules, and ActiveRecord serialization support for Ruby on Rails.

Tokens
9K
Snippets
39
Records
40
Agent score
74%

What's inside Montrose

  1. Enumerate events from a recurrence

    main

    A Montrose recurrence object is itself Enumerable and responds to #events, which returns an Enumerator of timestamps.

    Warning: Some recurrences (like Montrose.daily) represent infinite sequences. Attempting to eagerly enumerate them (e.g., using .map or .to_a without limits) will cause the process to hang. To safely work with infinite recurrences, use the .lazy enumerator to apply filters and limits.

    # Basic enumeration
    r = Montrose.hourly
    r.events.take(10)
    
    # Handling infinite sequences with .lazy
    r = Montrose.daily
    r.lazy.map(&:to_date).select { |d| d.mday > 25 }.take(5).to_a
  2. How to combine multiple recurrences with Montrose::Schedule

    main

    If you need to combine several different recurrence rules into a single stream of events, use Montrose::Schedule. A schedule acts as a collection of rules that behaves as a single stream of events.

    You can build a schedule using a block and the << operator, which accepts either a Montrose::Recurrence object or a hash of valid recurrence options.

    recurrence_1 = Montrose.monthly(day: { friday: [1] })
    recurrence_2 = Montrose.weekly(on: :tuesday)
    
    schedule = Montrose::Schedule.build do |s|
      s << recurrence_1
      s << recurrence_2
    end
    
    # You can also add via option hashes
    schedule = Montrose::Schedule.build do |s|
      s << { day: { friday: [1] } }
      s << { on: :tuesday }
    end
    
    # A schedule behaves like a single stream of events
    schedule.each do |event|
      puts event
    end
  3. How to enumerate recurrence events

    main

    A Montrose::Recurrence object is an enumerable. You can iterate over its events using #each or access the underlying enumerator via #events.

    To avoid infinite loops with infinite recurrences, use #events.lazy or #take(n) to limit the number of events retrieved.

    r = Montrose.every(:month, mday: 31, until: "January 1, 2017")
    
    # Iterate through all
    r.each { |time| puts time.to_s }
    
    # Take a specific amount
    r.take(10).to_a
    
    # Use lazy enumeration for infinite series
    r.events.lazy.select { |time| time > 1.month.from_now }.take(3).each { |date| puts date.to_s }
  4. How Montrose recurrence objects work

    main

    Montrose uses a chaining system to build recurrence objects. Each method call in a chain returns a new object, allowing you to compose or merge different recurrence rules.

    Recurrences can be built in two ways:

    1. Succession: Chaining methods one after another.
    2. Merging: Creating distinct recurrence objects and combining them using the #merge method.

    You can also use the Montrose.r method to initialize a recurrence using a hash of options.

    # Building in succession
    r1 = Montrose.every(:week)
    r2 = r1.on([:tuesday, :thursday])
    r3 = r2.at("12 pm")
    r4 = r3.total(4)
    
    # Merging distinct recurrences
    r1 = Montrose.every(:week)
    r2 = Montrose.on([:tuesday, :thursday])
    r3 = Montrose.at("12 pm")
    r4 = r1.merge(r2).merge(r3).total(4)
    
    # Using hash syntax
    Montrose.r(every: :week, on: :monday, at: "10:30 am")
  5. Install Montrose

    main

    To use Montrose in your Ruby application, add it to your Gemfile and run bundle, or install it directly via the gem command.

    # Add to Gemfile
    gem "montrose"

    Then run

    $ bundle

    Or install directly

    $ gem install montrose

  6. Serialize recurrences in Ruby on Rails

    main

    Montrose::Recurrence and Montrose::Schedule objects support the ActiveRecord serialization API. This allows you to store a recurrence rule in a single database column.

    # Serializing a Recurrence
    class RecurringEvent < ApplicationRecord
      serialize :recurrence, Montrose::Recurrence
    end
    
    # Serializing a Schedule
    class RecurringEvent < ApplicationRecord
      serialize :recurrence, Montrose::Schedule
    end
  7. What are Montrose Rules and how do they work?

    main

    In Montrose, a Rule is a constraint applied to a recurrence sequence. Rules determine whether a specific time is included in the sequence and how the sequence progresses.

    Rules follow a specific interface that allows them to be composed within a recurrence expression:

    1. include?(_time): Returns true if the given time satisfies the rule's constraints, and false otherwise.
    2. advance!(_time): Used to move the recurrence forward based on the current time.
    3. continue?(_time): Determines if the recurrence should continue after the given time.

    Rules can be initialized from an options hash using the from_options(opts) pattern, which internally calls apply_options(opts) to transform raw configuration into rule-specific parameters.

  8. Define hourly and minute recurrences

    main

    Use Montrose.hourly, Montrose.minutely, or Montrose.every(duration) for high-frequency patterns.

    For time-of-day precision, use the .during method on a recurrence object to restrict occurrences to specific windows.

    .during accepts:

    • Semantic time-of-day strings (e.g., "9am-4:40pm").
    • Ruby time ranges.
    • Hour, min, sec tuple pairs (e.g., [9, 0, 0]).
    # every 20 minutes from 9:00 AM to 4:40 PM every day
    r = Montrose.every(20.minutes)
    r.during("9am-4:40pm")
    
    # every 20 minutes during multiple time-of-day ranges
    Montrose.every(20.minutes).during("9am-12pm", "1pm-5pm")
    
    # Minutely
    Montrose.minutely(until: "9:00 PM")
  9. Create a new recurrence with Montrose

    main

    You can create a new recurrence object using several shorthand methods or by instantiating the class directly. Montrose provides high-level methods for common patterns like daily, weekly, monthly, and yearly.

    require "montrose"
    
    # A new recurrence
    Montrose.r
    Montrose.recurrence
    Montrose::Recurrence.new
  10. Define monthly recurrences

    main

    Use Montrose.monthly or Montrose.every(:month) to define monthly patterns. Montrose supports complex monthly rules including specific days of the month, specific weekdays, or specific month ranges.

    Common options:

    • mday: Specific day of the month (e.g., 1, -1 for last day, or a range 10..15).
    • day: Specific weekday rules (e.g., { friday: [1] } for the first Friday).
    • month: Specific month or range of months (e.g., :january or 6..8).
    • on: Complex weekday/day combinations (e.g., { friday: 13 }).
    # monthly on the first Friday for ten occurrences
    Montrose.monthly(day: { friday: [1] }, total: 10)
    
    # every other month on the first and last Sunday of the month for 10 occurrences
    Montrose.every(:month, day: { sunday: [1, -1] }, interval: 2, total: 10)
    
    # monthly on the 2nd and 15th of the month for 10 occurrences
    Montrose.every(:month, mday: [2, 15], total: 10)
    
    # every Friday 13th, forever
    Montrose.monthly(on: { friday: 13 })
  11. Define weekly recurrences

    main

    Use Montrose.weekly or Montrose.every(:week) to define weekly patterns. You can specify specific days of the week using the on option.

    Common options:

    • on: An array of days (e.g., [:tuesday, :thursday]) or a single day symbol.
    • at: Specific time of day.
    • interval: Frequency (e.g., 2.weeks).
    # weekly for 10 occurrences
    Montrose.weekly(total: 10)
    
    # weekly on Tuesday and Thursday for five weeks
    Montrose.weekly(on: [:tuesday, :thursday],
      between: Date.new(2015, 9, 1)..Date.new(2015, 10, 5))
    
    # every other week on Monday, Wednesday and Friday until December 23 2015
    Montrose.every(2.weeks,
      on: [:monday, :wednesday, :friday],
      starts: Date.new(2015, 9, 1))
  12. Define yearly recurrences

    main

    Use Montrose.yearly or Montrose.every(:year) to define yearly patterns.

    Common options:

    • month: Specific month(s).
    • yday: Day of the year (e.g., [1, 100]).
    • week: Specific week number.
    • on: Specific day/month combinations.
    # yearly in June and July for 10 occurrences
    Montrose.yearly(month: [:june, :july], total: 10)
    
    # every third year on the 1st, 100th and 200th day for 10 occurrences
    Montrose.yearly(yday: [1, 100, 200], total: 10)
    
    # every Thursday in March, forever
    Montrose.monthly(month: :march, on: :thursday, at: "12 pm")