schedulicon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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