schedulicon 0.0.1

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.
@@ -0,0 +1,17 @@
1
+ module Schedulicon
2
+ require 'schedulicon/version'
3
+ require 'schedulicon/day_of_week'
4
+ require 'schedulicon/day_in_month'
5
+ require 'schedulicon/schedule'
6
+ require 'schedulicon/attributes'
7
+
8
+ # Days of the week
9
+ MONDAY = 1
10
+ TUESDAY = 2
11
+ WEDNESDAY = 3
12
+ THURSDAY = 4
13
+ FRIDAY = 5
14
+ SATURDAY = 6
15
+ SUNDAY = 7
16
+
17
+ end
@@ -0,0 +1,41 @@
1
+ # Public: mixin for generating methods required by html forms
2
+ #
3
+ module Schedulicon
4
+ module Attributes
5
+
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ extend ClassMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ def schedule_attribute(*names)
15
+ names.each do |name|
16
+ define_method(name) { instance_variable_get("@#{name}") || instance_variable_set("@#{name}", Schedule.new) }
17
+ define_method("#{name}=") do |value| instance_variable_set("@#{name}", value) end
18
+
19
+ define_method("#{name}_frequency") { send(name).frequency }
20
+ define_method("#{name}_frequency=") do |value| send(name).frequency = value.to_sym unless value.blank? end
21
+
22
+ define_method("#{name}_day_of_week") { send(name).day_of_week.to_i }
23
+ define_method("#{name}_day_of_week=") do |value| send(name).day_of_week = value end
24
+
25
+ define_method("#{name}_continues") { send(name).end_on ? :until : :forever }
26
+ define_method("#{name}_continues=") do |value| send(name).send("end_on=", nil) if value.to_sym != :until end
27
+
28
+ define_method("#{name}_until") { send(name).end_on }
29
+ define_method("#{name}_until=") do |value|
30
+ return unless value
31
+ if value.is_a? String
32
+ return if value.empty?
33
+ value = Time.parse(value)
34
+ end
35
+ send(name).end_on = value
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,56 @@
1
+ # Public: Represents a day in month expression
2
+ #
3
+ # examples
4
+ #
5
+ # DayInMonth.new(Schedulicon::THURSDAY, 2) # second Thursday
6
+ # DayInMonth.new(Schedulicon::SATURDAY, -1) # last Saturday
7
+ #
8
+ module Schedulicon
9
+ class DayInMonth
10
+
11
+ attr_reader :day, :frequency
12
+
13
+ def initialize(day, frequency)
14
+ @day, @frequency = day, frequency
15
+ end
16
+
17
+ def ordinal
18
+ {
19
+ :every => 0,
20
+ :first => 1,
21
+ :second => 2,
22
+ :third => 3,
23
+ :fourth => 4,
24
+ :last => -1
25
+ }[frequency]
26
+ end
27
+ # Public: generate array of dates for the range
28
+ #
29
+ # starts_at - DateTime range starts at
30
+ # end_on - DateTime rande ends on
31
+ #
32
+ # Returns Array of DateTime
33
+ def dates(start_at, end_on)
34
+ start_at.to_datetime.step(end_on).select { |d| week_matches(d) && d.cwday == day }
35
+ end
36
+
37
+ # Private: calculates whether date is in the ordinal week for the expression
38
+ #
39
+ def week_matches(date)
40
+ ordinal > 0 ? week_in_month(date) == ordinal : week_from_end(date) == ordinal.abs
41
+ end
42
+
43
+ # Private: calculates the month week number for a date
44
+ #
45
+ def week_in_month(date)
46
+ ((date.day - 1) / 7) + 1
47
+ end
48
+
49
+ # Private: calculates the month week number from end of month for a date
50
+ #
51
+ def week_from_end(date)
52
+ last_day = DateTime.new(date.next_month.year, date.next_month.month, 1) - 1
53
+ ((last_day.day - date.day) / 7) + 1
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ # Public: Represents a day of week expression
2
+ #
3
+ # examples
4
+ #
5
+ # DayOfWeek.new(Schedulicon::THURSDAY) # every Thursday
6
+ #
7
+ module Schedulicon
8
+ class DayOfWeek
9
+
10
+ attr_reader :day
11
+
12
+ def initialize(day)
13
+ @day = day
14
+ end
15
+
16
+ # Public: generate array of dates for the range
17
+ #
18
+ # starts_at - DateTime range starts at
19
+ # end_on - DateTime rande ends on
20
+ #
21
+ # Returns Array of DateTime
22
+ def dates(start_at, end_on)
23
+ start_at.to_datetime.step(end_on).select { |d| d.cwday == @day }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,78 @@
1
+ # Public: Describes a schedule in a portable fashion
2
+ #
3
+ require 'time'
4
+ require 'date'
5
+
6
+ module Schedulicon
7
+ class Schedule
8
+ FREQUENCIES = [:every, :first, :second, :third, :fourth, :last]
9
+
10
+ attr_accessor :frequency, :day_of_week, :start_at, :end_on
11
+
12
+ def initialize(attrs={})
13
+ @frequency = attrs[:frequency] || :every
14
+ @day_of_week = attrs[:day_of_week] || Schedulicon::MONDAY
15
+ @start_at = attrs[:start_at]
16
+ @end_on = attrs[:end_on]
17
+ end
18
+
19
+ # Public: Hash of attributes that can be used to deserialise item from
20
+ #
21
+ def to_hash
22
+ hash = {
23
+ 'frequency' => frequency,
24
+ 'start_at' => start_at.xmlschema,
25
+ 'day_of_week' => day_of_week
26
+ }
27
+ hash['end_on'] = end_on.xmlschema if end_on
28
+ hash
29
+ end
30
+
31
+ # Public: load schedule from the hash
32
+ #
33
+ def self.load(attrs)
34
+ schedule = Schedule.new
35
+ schedule.start_at = Time.xmlschema(attrs['start_at']).localtime
36
+ schedule.end_on = Time.xmlschema(attrs['end_on']).localtime if attrs['end_on']
37
+ schedule.day_of_week = attrs['day_of_week'].to_i
38
+ schedule.frequency = attrs['frequency'].to_sym
39
+ schedule
40
+ end
41
+
42
+ # Public: generates the expression required to generate the schedule dates
43
+ #
44
+ def expression
45
+ case frequency
46
+ when :every
47
+ DayOfWeek.new(day_of_week)
48
+ else
49
+ DayInMonth.new(day_of_week, frequency)
50
+ end
51
+ end
52
+
53
+ # Public: generate a list of dates for the schedule
54
+ #
55
+ def dates(from=nil, to=nil)
56
+ from ||= start_at
57
+ to ||= ((end_on && end_on.to_date) || from.to_date >> 12)
58
+ expression.dates(from, to).collect {|date| date.to_time }
59
+ end
60
+
61
+ # Public: override equality operator to compare values
62
+ #
63
+ def ==(other)
64
+ return false unless other
65
+ return false unless other.respond_to?(:frequency) && other.frequency == frequency
66
+ return false unless other.respond_to?(:day_of_week) && other.day_of_week == day_of_week
67
+ return false unless time_attribute_equal(other, :start_at)
68
+ return false unless time_attribute_equal(other, :end_on)
69
+ true
70
+ end
71
+
72
+ def time_attribute_equal(other, name)
73
+ return false unless other.respond_to? name
74
+ return other.send(name).utc == self.send(name).utc if other.send(name) && self.send(name)
75
+ true
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module Schedulicon
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ class DummyModel
4
+ include Attributes
5
+ schedule_attribute :event_schedule
6
+ end
7
+
8
+ describe Attributes do
9
+ before(:each) do
10
+ @model = DummyModel.new
11
+ end
12
+
13
+ describe ".schedule_attribute" do
14
+ context "when root attribute not set" do
15
+ it "returns a Schedule" do
16
+ @model.event_schedule.should be_an_instance_of(Schedule)
17
+ end
18
+ it "frequency should be every" do
19
+ @model.event_schedule_frequency.should eq(:every)
20
+ end
21
+ it "day_of_week should be monday" do
22
+ @model.event_schedule_day_of_week.should eq(Schedulicon::MONDAY)
23
+ end
24
+ it "continues should be forever" do
25
+ @model.event_schedule_continues.should eq(:forever)
26
+ end
27
+ it "until should be nil" do
28
+ @model.event_schedule_until.should be_nil
29
+ end
30
+ end
31
+ context "#until" do
32
+ context "when set with valid date string" do
33
+
34
+ subject { @model.event_schedule_until = "12/07/2012" }
35
+
36
+ it "sets the until to a valid time" do
37
+ subject
38
+ @model.event_schedule_until.should eq(Time.new(2012, 7, 12))
39
+ end
40
+ end
41
+ context "when set with empty date string" do
42
+
43
+ subject { @model.event_schedule_until = "" }
44
+
45
+ it "sets the until to a valid time" do
46
+ subject
47
+ @model.event_schedule_until.should be_nil
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe DayInMonth do
4
+ describe "#dates" do
5
+ before(:each) do
6
+ @start_at = DateTime.new(2012, 6, 1, 19, 0)
7
+ @end_on = DateTime.new(2012, 9, 1, 19, 0)
8
+ end
9
+
10
+ subject { DayInMonth.new(@day, @frequency).dates(@start_at, @end_on) }
11
+
12
+ context "when 3rd Thursday" do
13
+ before(:each) do
14
+ @day = Schedulicon::THURSDAY
15
+ @frequency = :third
16
+ end
17
+
18
+ it "first should be 21 June" do
19
+ subject[0].should eq(DateTime.new(2012, 6, 21, 19, 0))
20
+ end
21
+ it "second should be 19 July" do
22
+ subject[1].should eq(DateTime.new(2012, 7, 19, 19, 0))
23
+ end
24
+ it "third should be 16 August" do
25
+ subject[2].should eq(DateTime.new(2012, 8, 16, 19, 0))
26
+ end
27
+ it "should return 3 items" do
28
+ subject.count.should eq(3)
29
+ end
30
+ end
31
+
32
+ context "when last Tuesday" do
33
+ before(:each) do
34
+ @day = Schedulicon::TUESDAY
35
+ @frequency = :last
36
+ end
37
+ it "first should be 26 June" do
38
+ subject[0].should eq(DateTime.new(2012, 6, 26, 19, 0))
39
+ end
40
+ it "second should be 31 July" do
41
+ subject[1].should eq(DateTime.new(2012, 7, 31, 19, 0))
42
+ end
43
+ it "third should be 28 August" do
44
+ subject[2].should eq(DateTime.new(2012, 8, 28, 19, 0))
45
+ end
46
+ it "should return 3 items" do
47
+ subject.count.should eq(3)
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "#week_from_end" do
53
+
54
+ subject { DayInMonth.new(Schedulicon::THURSDAY, :last).week_from_end(@date) }
55
+
56
+ context "when in last week" do
57
+ before(:each) do
58
+ @date = DateTime.new(2012, 6, 28)
59
+ end
60
+ it "is 1" do
61
+ subject.should eq(1)
62
+ end
63
+ end
64
+ context "when in penultimate week" do
65
+ before(:each) do
66
+ @date = DateTime.new(2012, 6, 22)
67
+ end
68
+ it "is 2" do
69
+ subject.should eq(2)
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe DayOfWeek do
4
+ describe "#dates" do
5
+ before(:each) do
6
+ @start_at = DateTime.new(2012, 6, 1, 19, 0)
7
+ @end_on = DateTime.new(2012, 7, 1, 19, 0)
8
+ end
9
+
10
+ context "when Monday" do
11
+ before(:each) do
12
+ @day = Schedulicon::MONDAY
13
+ end
14
+
15
+ subject { DayOfWeek.new(@day).dates(@start_at, @end_on) }
16
+
17
+ it "first should be 4 June" do
18
+ subject[0].should eq(DateTime.new(2012, 6, 4, 19, 0))
19
+ end
20
+ it "second should be 11 June" do
21
+ subject[1].should eq(DateTime.new(2012, 6, 11, 19, 0))
22
+ end
23
+ it "third should be 18 June" do
24
+ subject[2].should eq(DateTime.new(2012, 6, 18, 19, 0))
25
+ end
26
+ it "fourth should be 25 June" do
27
+ subject[3].should eq(DateTime.new(2012, 6, 25, 19, 0))
28
+ end
29
+ it "should return 4 items" do
30
+ subject.count.should eq(4)
31
+ end
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+
3
+ describe Schedule do
4
+ describe ".load" do
5
+ before(:each) do
6
+ @frequency = :first
7
+ @day_of_week = Schedulicon::WEDNESDAY
8
+ @start_at = random_time
9
+ end
10
+
11
+ subject { Schedule.load({'frequency' => @frequency.to_s, 'day_of_week' => @day_of_week.to_s, 'start_at' => @start_at.xmlschema }) }
12
+
13
+ it "sets the start time" do
14
+ subject.start_at.to_s.should eq(@start_at.to_s)
15
+ end
16
+ it "sets the day_of_week" do
17
+ subject.day_of_week.should eq(@day_of_week)
18
+ end
19
+ it "sets the day on the expression" do
20
+ subject.frequency.should eq(@frequency)
21
+ end
22
+ end
23
+
24
+ describe "#to_hash" do
25
+ before(:each) do
26
+ @schedule = Schedule.new
27
+ @schedule.frequency = :first
28
+ @schedule.day_of_week = Schedulicon::WEDNESDAY
29
+ @schedule.start_at = random_time
30
+ @schedule.end_on = random_time
31
+ end
32
+
33
+ subject { @schedule.to_hash }
34
+
35
+ it "sets the start time" do
36
+ subject['start_at'].should eq(@schedule.start_at.xmlschema)
37
+ end
38
+ it "sets the start time" do
39
+ subject['end_on'].should eq(@schedule.end_on.xmlschema)
40
+ end
41
+ it "sets the day_of_week" do
42
+ subject['day_of_week'].should eq(@schedule.day_of_week)
43
+ end
44
+ it "sets the day on the expression" do
45
+ subject['frequency'].should eq(@schedule.frequency)
46
+ end
47
+ end
48
+
49
+ describe "#expression" do
50
+ before(:each) do
51
+ @schedule = Schedule.new
52
+ end
53
+
54
+ subject { @schedule.expression }
55
+
56
+ context "when every Friday" do
57
+ before(:each) do
58
+ @schedule.day_of_week = Schedulicon::FRIDAY
59
+ @schedule.frequency = :every
60
+ end
61
+ it "should be a DayOfWeek" do
62
+ subject.should be_an_instance_of(DayOfWeek)
63
+ end
64
+ it "should have Friday as the day" do
65
+ subject.day.should eq(Schedulicon::FRIDAY)
66
+ end
67
+ end
68
+
69
+ context "when 3rd Tuesday of the month" do
70
+ before(:each) do
71
+ @schedule.day_of_week = Schedulicon::TUESDAY
72
+ @schedule.frequency = :third
73
+ end
74
+ it "should be a DayInMonth" do
75
+ subject.should be_an_instance_of(DayInMonth)
76
+ end
77
+ it "should have Tuesday as the day" do
78
+ subject.day.should eq(Schedulicon::TUESDAY)
79
+ end
80
+ it "should have 3 as the ordinal" do
81
+ subject.ordinal.should eq(3)
82
+ end
83
+ end
84
+
85
+ context "when last Sunday of the month" do
86
+ before(:each) do
87
+ @schedule.day_of_week = Schedulicon::SUNDAY
88
+ @schedule.frequency = :last
89
+ end
90
+ it "should be a DayInMonth" do
91
+ subject.should be_an_instance_of(DayInMonth)
92
+ end
93
+ it "should have Tuesday as the day" do
94
+ subject.day.should eq(Schedulicon::SUNDAY)
95
+ end
96
+ it "should have 3 as the ordinal" do
97
+ subject.ordinal.should eq(-1)
98
+ end
99
+ end
100
+ end
101
+
102
+ describe "#dates" do
103
+ before(:each) do
104
+ @schedule = Schedule.new
105
+ @schedule.start_at = @start_at = random_time
106
+ @schedule.end_on = @end_on = random_time
107
+ @expression = mock('Expression', :dates => [])
108
+ @schedule.stub(:expression) { @expression }
109
+ @from = random_time
110
+ @to = random_time
111
+ end
112
+
113
+ subject { @schedule.dates(@from, @to) }
114
+
115
+ it "calls the expression#dates with args" do
116
+ @expression.should_receive(:dates).with(@from, @to)
117
+ subject
118
+ end
119
+ context "when no argumens passed" do
120
+ before(:each) do
121
+ @from = nil
122
+ @to = nil
123
+ end
124
+ it "calls the expression#dates with args" do
125
+ @expression.should_receive(:dates).with(@start_at, @end_on.to_date)
126
+ subject
127
+ end
128
+ context "when end_on is nil" do
129
+ before(:each) do
130
+ @schedule.end_on = nil
131
+ end
132
+ it "calls end as 12 months from start" do
133
+ @expression.should_receive(:dates).with(@start_at, @start_at.to_date >> 12)
134
+ subject
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe "#==" do
141
+ before(:each) do
142
+ @start_at = random_time
143
+ @schedule = Schedule.new(:frequency => :second, :day_of_week => Schedulicon::SATURDAY, :start_at => @start_at)
144
+ end
145
+
146
+ subject { @schedule == @other }
147
+
148
+ context "when other is nil" do
149
+ before(:each) do
150
+ @other = nil
151
+ end
152
+ it "is false" do
153
+ subject.should eq(false)
154
+ end
155
+ end
156
+ context "when other is equal" do
157
+ before(:each) do
158
+ @other = Schedule.new(:frequency => :second, :day_of_week => Schedulicon::SATURDAY, :start_at => @start_at)
159
+ end
160
+ it "is true" do
161
+ subject.should eq(true)
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,24 @@
1
+ require 'time'
2
+ require 'date'
3
+ require 'rake'
4
+ require 'rspec'
5
+ require "#{Rake.application.original_dir}/lib/schedulicon"
6
+
7
+ include Schedulicon
8
+
9
+ RSpec.configure do |config|
10
+ # Use color in STDOUT
11
+ config.color_enabled = true
12
+ end
13
+
14
+ def random_string
15
+ (0...24).map{ ('a'..'z').to_a[rand(26)] }.join
16
+ end
17
+
18
+ def random_integer
19
+ rand(9999)
20
+ end
21
+
22
+ def random_time
23
+ Time.now - random_integer
24
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schedulicon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adam Bird
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-11 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Gem for recurring events
15
+ email: adam.bird@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/schedulicon/attributes.rb
21
+ - lib/schedulicon/day_in_month.rb
22
+ - lib/schedulicon/day_of_week.rb
23
+ - lib/schedulicon/schedule.rb
24
+ - lib/schedulicon/version.rb
25
+ - lib/schedulicon.rb
26
+ - spec/schedulicon/attributes_spec.rb
27
+ - spec/schedulicon/day_in_month_spec.rb
28
+ - spec/schedulicon/day_of_week_spec.rb
29
+ - spec/schedulicon/schedule_spec.rb
30
+ - spec/spec_helper.rb
31
+ homepage: http://github.com/adambird/schedulicon
32
+ licenses: []
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.24
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Gem for recurring events
55
+ test_files:
56
+ - spec/schedulicon/attributes_spec.rb
57
+ - spec/schedulicon/day_in_month_spec.rb
58
+ - spec/schedulicon/day_of_week_spec.rb
59
+ - spec/schedulicon/schedule_spec.rb
60
+ - spec/spec_helper.rb