├── .ruby-version ├── .standard.yml ├── lib ├── montrose │ ├── version.rb │ ├── errors.rb │ ├── frequency │ │ ├── minutely.rb │ │ ├── secondly.rb │ │ ├── hourly.rb │ │ ├── daily.rb │ │ ├── monthly.rb │ │ ├── yearly.rb │ │ └── weekly.rb │ ├── refinements │ │ └── array_concat.rb │ ├── rule │ │ ├── except.rb │ │ ├── month_of_year.rb │ │ ├── day_of_week.rb │ │ ├── week_of_year.rb │ │ ├── hour_of_day.rb │ │ ├── minute_of_hour.rb │ │ ├── total.rb │ │ ├── after.rb │ │ ├── between.rb │ │ ├── time_of_day.rb │ │ ├── until.rb │ │ ├── day_of_year.rb │ │ ├── day_of_month.rb │ │ ├── nth_day_matcher.rb │ │ ├── covering.rb │ │ ├── during.rb │ │ ├── nth_day_of_month.rb │ │ └── nth_day_of_year.rb │ ├── week.rb │ ├── hour.rb │ ├── minute.rb │ ├── year_day.rb │ ├── month_day.rb │ ├── time_of_day.rb │ ├── month.rb │ ├── stack.rb │ ├── utils.rb │ ├── rule.rb │ ├── day.rb │ ├── frequency.rb │ ├── ical.rb │ ├── clock.rb │ ├── schedule.rb │ ├── options.rb │ └── chainable.rb └── montrose.rb ├── bin ├── console ├── bundle-all ├── spec ├── setup ├── update ├── m ├── rake ├── guard ├── _guard-core └── standardrb ├── gemfiles ├── activesupport_5.2.gemfile ├── activesupport_6.0.gemfile ├── activesupport_6.1.gemfile ├── activesupport_7.0.gemfile ├── activesupport_7.1.gemfile ├── activesupport_7.2.gemfile ├── activesupport_8.0.gemfile ├── activesupport_5.2.gemfile.lock ├── activesupport_6.0.gemfile.lock ├── activesupport_6.1.gemfile.lock ├── activesupport_7.0.gemfile.lock ├── activesupport_7.1.gemfile.lock ├── activesupport_7.2.gemfile.lock └── activesupport_8.0.gemfile.lock ├── .rubocop.yml ├── .gitignore ├── Gemfile ├── .codeclimate.yml ├── spec ├── support │ └── trace.rb ├── montrose │ ├── rule │ │ ├── after_spec.rb │ │ ├── month_of_year_spec.rb │ │ ├── except_spec.rb │ │ ├── hour_of_day_spec.rb │ │ ├── week_of_year_spec.rb │ │ ├── day_of_week_spec.rb │ │ ├── day_of_month_spec.rb │ │ ├── time_of_day_spec.rb │ │ ├── total_spec.rb │ │ ├── until_spec.rb │ │ ├── day_of_year_spec.rb │ │ ├── during_spec.rb │ │ └── covering_spec.rb │ ├── frequency │ │ ├── secondly_spec.rb │ │ ├── minutely_spec.rb │ │ ├── yearly_spec.rb │ │ ├── daily_spec.rb │ │ ├── weekly_spec.rb │ │ ├── monthly_spec.rb │ │ └── hourly_spec.rb │ ├── frequency_spec.rb │ ├── utils_spec.rb │ ├── month_spec.rb │ ├── day_spec.rb │ ├── clock_spec.rb │ ├── examples_spec.rb │ ├── schedule_spec.rb │ ├── recurrence_spec.rb │ └── chainable_spec.rb ├── montrose_spec.rb └── spec_helper.rb ├── Rakefile ├── LICENSE.txt ├── montrose.gemspec ├── Guardfile ├── CODE_OF_CONDUCT.md ├── Gemfile.lock ├── .circleci └── config.yml └── CHANGELOG.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2 2 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | ruby_version: 2.7 2 | -------------------------------------------------------------------------------- /lib/montrose/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | VERSION = "0.18.0" 5 | end 6 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "montrose" 5 | 6 | require "pry" 7 | Pry.start 8 | -------------------------------------------------------------------------------- /gemfiles/activesupport_5.2.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activesupport", "~> 5.2" 4 | 5 | gemspec path: "../" 6 | -------------------------------------------------------------------------------- /gemfiles/activesupport_6.0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activesupport", "~> 6.0" 4 | 5 | gemspec path: "../" 6 | -------------------------------------------------------------------------------- /gemfiles/activesupport_6.1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activesupport", "~> 6.1" 4 | 5 | gemspec path: "../" 6 | -------------------------------------------------------------------------------- /gemfiles/activesupport_7.0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activesupport", "~> 7.0" 4 | 5 | gemspec path: "../" 6 | -------------------------------------------------------------------------------- /gemfiles/activesupport_7.1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activesupport", "~> 7.1" 4 | 5 | gemspec path: "../" 6 | -------------------------------------------------------------------------------- /gemfiles/activesupport_7.2.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activesupport", "~> 7.2" 4 | 5 | gemspec path: "../" 6 | -------------------------------------------------------------------------------- /gemfiles/activesupport_8.0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "activesupport", "~> 8.0" 4 | 5 | gemspec path: "../" 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | 4 | inherit_gem: 5 | standard: config/base.yml 6 | 7 | AllCops: 8 | TargetRubyVersion: 2.7 9 | -------------------------------------------------------------------------------- /bin/bundle-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | Dir["gemfiles/*.gemfile"].each do |gemfile| 4 | system({"BUNDLE_GEMFILE" => gemfile}, "bundle") 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | /profile.log 10 | /trace.txt 11 | /.ruby-gemset 12 | /vendor/ 13 | -------------------------------------------------------------------------------- /lib/montrose/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | Error = Class.new(StandardError) 5 | ConfigurationError = Class.new(Error) 6 | SerializationError = Class.new(Error) 7 | end 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | 7 | group :development do 8 | gem "guard" 9 | gem "guard-minitest" 10 | gem "guard-rubocop" 11 | gem "pry-byebug" 12 | end 13 | -------------------------------------------------------------------------------- /bin/spec: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bin/rake spec 6 | 7 | # Do any other automated setup that you need to do here 8 | for filename in gemfiles/*.gemfile; do 9 | BUNDLE_GEMFILE=$filename bin/rake spec 10 | done 11 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | for filename in gemfiles/*.gemfile; do 9 | BUNDLE_GEMFILE=$filename bundle install 10 | done 11 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle update 6 | 7 | # Do any other automated setup that you need to do here 8 | for filename in gemfiles/*.gemfile; do 9 | BUNDLE_GEMFILE=$filename bundle update 10 | done 11 | -------------------------------------------------------------------------------- /lib/montrose/frequency/minutely.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | class Frequency 5 | class Minutely < Frequency 6 | def include?(time) 7 | matches_interval?((time - @starts) / 1.minute) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/montrose/frequency/secondly.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | class Frequency 5 | class Secondly < Frequency 6 | def include?(time) 7 | matches_interval?((time - @starts) / 1.second) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | bundler-audit: 4 | enabled: false 5 | duplication: 6 | enabled: true 7 | config: 8 | languages: 9 | - ruby 10 | fixme: 11 | enabled: true 12 | rubocop: 13 | enabled: true 14 | ratings: 15 | paths: 16 | - Gemfile.lock 17 | - "**.rb" 18 | exclude_paths: 19 | - spec/ 20 | -------------------------------------------------------------------------------- /spec/support/trace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if ENV["TRACE"] 4 | stack_size = ENV["TRACE"].to_i 5 | trace_out = open("trace.txt", "w") 6 | 7 | set_trace_func(proc { |event, file, line, id, _binding, classname| 8 | trace_out.puts "#{file}:#{line} #{classname}##{id}" if event == "call" && caller.length > stack_size 9 | }) 10 | end 11 | -------------------------------------------------------------------------------- /lib/montrose/frequency/hourly.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | class Frequency 5 | class Hourly < Frequency 6 | def include?(time) 7 | matches_interval?((time - @starts) / 1.hour) 8 | end 9 | 10 | def to_cron 11 | "#{@starts.min} #{interval_str} * * *" 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/montrose/frequency/daily.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | class Frequency 5 | class Daily < Frequency 6 | def include?(time) 7 | matches_interval? time.to_date - @starts.to_date 8 | end 9 | 10 | def to_cron 11 | "#{@starts.min} #{@starts.hour} #{interval_str} * *" 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /bin/m: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'm' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('m', 'm') 17 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rake' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rake', 'rake') 17 | -------------------------------------------------------------------------------- /lib/montrose/frequency/monthly.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | class Frequency 5 | class Monthly < Frequency 6 | def include?(time) 7 | matches_interval?((time.month - @starts.month) + (time.year - @starts.year) * 12) 8 | end 9 | 10 | def to_cron 11 | "#{@starts.min} #{@starts.hour} #{@starts.day} #{interval_str} *" 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /bin/guard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'guard' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('guard', 'guard') 17 | -------------------------------------------------------------------------------- /bin/_guard-core: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application '_guard-core' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('guard', '_guard-core') 17 | -------------------------------------------------------------------------------- /lib/montrose/frequency/yearly.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | class Frequency 5 | class Yearly < Frequency 6 | def include?(time) 7 | matches_interval? time.year - @starts.year 8 | end 9 | 10 | def to_cron 11 | raise "Intervals unsupported" unless @interval == 1 12 | 13 | "#{@starts.min} #{@starts.hour} #{@starts.day} #{@starts.month} *" 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/montrose/rule/after_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | describe Montrose::Rule::After do 6 | let(:rule) { Montrose::Rule::After.new(time_now) } 7 | 8 | describe "#include?" do 9 | it { assert rule.include?(time_now) } 10 | it { assert rule.include?(time_now + 10.days) } 11 | it { refute rule.include?(time_now - 10.days) } 12 | end 13 | 14 | describe "#continue?" do 15 | it { refute rule.continue?(time_now) } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/montrose/refinements/array_concat.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Refinements 5 | module ArrayConcat 6 | refine Object do 7 | def array_concat(other) 8 | Array(self) + other 9 | end 10 | end 11 | 12 | refine Hash do 13 | # array concat for Hash not supported 14 | # so we just return self 15 | def array_concat(_other) 16 | self 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/montrose/rule/except.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class Except 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:except] 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param [Date] dates - array of date objects 15 | # 16 | def initialize(dates) 17 | @dates = dates 18 | end 19 | 20 | def include?(time) 21 | !@dates.include?(time.to_date) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/montrose/week.rb: -------------------------------------------------------------------------------- 1 | module Montrose 2 | class Week 3 | class << self 4 | NUMBERS = (-53.upto(-1).to_a + 1.upto(53).to_a) 5 | 6 | def parse(arg) 7 | return nil unless arg.present? 8 | 9 | Array(arg).map { |value| assert(value.to_i) } 10 | end 11 | 12 | def assert(number) 13 | test = number.abs 14 | raise ConfigurationError, "Out of range: #{NUMBERS.inspect} does not include #{test}" unless NUMBERS.include?(number.abs) 15 | 16 | number 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/montrose/rule/month_of_year.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class MonthOfYear 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:month] 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param [Array] months - valid month numbers 15 | # 16 | def initialize(months) 17 | @months = months 18 | end 19 | 20 | def include?(time) 21 | @months.include?(time.month) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/montrose/rule/day_of_week.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class DayOfWeek 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:day] 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param [Array] days - valid days of week, e.g. [1, 2, 7] 15 | # 16 | def initialize(days) 17 | @days = days 18 | end 19 | 20 | def include?(time) 21 | @days.include?(time.wday) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/montrose/rule/week_of_year.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class WeekOfYear 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:week] 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param [Array[Fixnum]] weeks - valid weeks of year 15 | # 16 | def initialize(weeks) 17 | @weeks = weeks 18 | end 19 | 20 | def include?(time) 21 | @weeks.include?(time.to_date.cweek) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/montrose/hour.rb: -------------------------------------------------------------------------------- 1 | module Montrose 2 | class Hour 3 | HOURS_IN_DAY = 1.upto(24).to_a.freeze 4 | 5 | class << self 6 | def parse(arg) 7 | case arg 8 | when String 9 | parse(arg.split(",")) 10 | else 11 | Array(arg).map { |h| assert(h.to_i) }.presence 12 | end 13 | end 14 | 15 | def assert(hour) 16 | raise ConfigurationError, "Out of range: #{HOURS_IN_DAY.inspect} does not include #{hour}" unless HOURS_IN_DAY.include?(hour) 17 | 18 | hour 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/montrose/rule/hour_of_day.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class HourOfDay 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:hour] 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param hours [Array] valid hours of days, e.g. [1, 2, 24] 15 | # 16 | def initialize(hours) 17 | @hours = hours 18 | end 19 | 20 | def include?(time) 21 | @hours.include?(time.hour) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/montrose/rule/month_of_year_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | describe Montrose::Rule::MonthOfYear do 6 | let(:rule) { Montrose::Rule::MonthOfYear.new([1, 12]) } 7 | 8 | describe "#include?" do 9 | it { assert rule.include?(Time.local(2016, 1)) } 10 | it { refute rule.include?(Time.local(2016, 2)) } 11 | it { assert rule.include?(Time.local(2016, 12)) } 12 | it { refute rule.include?(Time.local(2016, 11)) } 13 | end 14 | 15 | describe "#continue?" do 16 | it { assert rule.continue? } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/montrose/rule/minute_of_hour.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class MinuteOfHour 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:minute] 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param minutes [Array] valid minutes of hour, e.g. [0, 20, 59] 15 | # 16 | def initialize(minutes) 17 | @minutes = minutes 18 | end 19 | 20 | def include?(time) 21 | @minutes.include?(time.min) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/montrose/rule/except_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | describe Montrose::Rule::Except do 6 | let(:dates) { [Date.today, Date.today + 5.days] } 7 | let(:rule) { Montrose::Rule::Except.new(dates) } 8 | 9 | describe "#include?" do 10 | it { refute rule.include?(time_now) } 11 | it { assert rule.include?(time_now + 1.days) } 12 | it { refute rule.include?(time_now + 5.days) } 13 | it { assert rule.include?(time_now + 10.days) } 14 | end 15 | 16 | describe "#continue?" do 17 | it { assert rule.continue? } 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/montrose/minute.rb: -------------------------------------------------------------------------------- 1 | module Montrose 2 | class Minute 3 | MINUTES_IN_HOUR = 0.upto(59).to_a.freeze 4 | 5 | class << self 6 | def parse(arg) 7 | case arg 8 | when String 9 | parse(arg.split(",")) 10 | else 11 | Array(arg).map { |m| assert(m.to_i) }.presence 12 | end 13 | end 14 | 15 | def assert(minute) 16 | raise ConfigurationError, "Out of range: #{MINUTES_IN_HOUR.inspect} does not include #{minute}" unless MINUTES_IN_HOUR.include?(minute) 17 | 18 | minute 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/montrose/rule/total.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class Total 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:total] 10 | end 11 | 12 | def initialize(max) 13 | @max = max 14 | @count = 0 15 | end 16 | 17 | def include?(time) 18 | continue?(time) 19 | end 20 | 21 | def advance!(time) 22 | @count += 1 23 | continue?(time) 24 | end 25 | 26 | def continue?(_time) 27 | @count <= @max 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/montrose/rule/after.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class After 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts.start_time 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param [Time] start_time - lower bound timestamp 15 | # 16 | def initialize(start_time) 17 | @start_time = start_time 18 | end 19 | 20 | def include?(time) 21 | time >= @start_time 22 | end 23 | 24 | def continue?(_time) 25 | false 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/montrose/year_day.rb: -------------------------------------------------------------------------------- 1 | module Montrose 2 | class YearDay 3 | class << self 4 | YDAYS = 1.upto(366).to_a 5 | 6 | def parse(ydays) 7 | return nil unless ydays.present? 8 | 9 | case ydays 10 | when String 11 | parse(ydays.split(",")) 12 | else 13 | Array(ydays).map { |d| assert(d.to_i) } 14 | end 15 | end 16 | 17 | def assert(number) 18 | test = number.abs 19 | raise ConfigurationError, "Out of range: #{YDAYS.inspect} does not include #{test}" unless YDAYS.include?(number.abs) 20 | 21 | number 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/montrose/rule/between.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class Between 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:between].is_a?(Range) && opts[:between] 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param [Range] between - timestamp range 15 | # 16 | def initialize(between) 17 | @between = between 18 | end 19 | 20 | def include?(time) 21 | @between.cover?(time) 22 | end 23 | 24 | def continue?(time) 25 | time < @between.max 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/montrose/rule/time_of_day.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Montrose 4 | module Rule 5 | class TimeOfDay 6 | include Montrose::Rule 7 | 8 | def self.apply_options(opts) 9 | opts[:at] 10 | end 11 | 12 | # Initializes rule 13 | # 14 | # @param [Array