repeatable 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7810dbdca8bf11e8e30101cc1bd22afbcaf698a0
4
- data.tar.gz: e1408eb13dbf184b1115d90ae814db6582b0a640
3
+ metadata.gz: 2a36439cf2a982c3ef546a1d7247517788a7f013
4
+ data.tar.gz: 81df50553e0f1e6894feed2b03f4512321c6973c
5
5
  SHA512:
6
- metadata.gz: ebd63d839565ebe7971e5566462dad20d851a9a110eee829d1f53ec8868559fa6b46f14c8528e067827daa95da6e9929a479ca852d69e54884b4a6c071a3b139
7
- data.tar.gz: b623c8e4040e6a8276f86c7b71078019fb830dd2ff88b0d5e784618580350cd5b8c51e45ce3846126765a97a1c448ea3e9d9d1f2261ded030cb7ad7ee195dcab
6
+ metadata.gz: d489cdad639be6b5cd0dbbb9e320bc73bbacdc4df5fb402fd0c54d2355d8ca6cdb26015804d50378c020b1c677cc368e9669d182d4611d7899e37cb57f75a36b
7
+ data.tar.gz: 3cad5c6ff55fb408a85a4ebbea1ad5ba3c90db7b2733050af10bb8d7f3ffa79534a52a87c6d163fa505c30cdf52962d57b47a36e7b319449cf95eec021817d78
data/.travis.yml CHANGED
@@ -1,10 +1,12 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.8
4
- - 2.2.4
5
- - 2.3.0
3
+ - 2.1.9
4
+ - 2.2.5
5
+ - 2.3.1
6
6
  sudo: false
7
7
  addons:
8
8
  code_climate:
9
9
  repo_token:
10
10
  secure: fyZ7Ycc23fzfh8bBiqPUhlJGcIcnanZWqVc4nsKDJeE1G5ScW01+ot1g4miDWxo7w80gKRKP/PfoYf6lOQ1B5yJJCV0Z5fjoTW3y+EKksMuGNCFaWh8R73MIPlVtfOazGyI2t3l4zDWoik906wHHNQJFveMZawvZGrVMWF6YxKg=
11
+ after_success:
12
+ - bundle exec codeclimate-test-reporter
data/CHANGELOG.md CHANGED
@@ -2,7 +2,21 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
- [Commits](https://github.com/molawson/repeatable/compare/v0.5.0...master)
5
+ [Commits](https://github.com/molawson/repeatable/compare/v0.6.0...master)
6
+
7
+ ### 0.6.0 (2017-05-04)
8
+
9
+ Features:
10
+
11
+ * Add `Expression::Difference` for set differences between 2 schedules ([@danott][])
12
+ * Allow `Expression::DayInMonth` to take `:last` (or `'last'`) for its `day:` argument ([@PatrickLerner][])
13
+ * Allow `Expression::WeekdayInMonth` to take negative `count` argument for last, second-to-last, etc. of a given weekday ([@danielma][])
14
+
15
+ Bugfixes:
16
+
17
+ * Fix `Expression::RangeInYear` to properly handle using `start_day` and `end_day` when `start_month == end_month` ([@danielma][])
18
+
19
+ [Commits](https://github.com/molawson/repeatable/compare/v0.5.0...v0.6.0)
6
20
 
7
21
  ### 0.5.0 (2016-01-27)
8
22
 
@@ -65,3 +79,5 @@ Initial Release
65
79
 
66
80
 
67
81
  [@danott]: https://github.com/danott
82
+ [@PatrickLerner]: https://github.com/PatrickLerner
83
+ [@danielma]: https://github.com/danielma
data/Gemfile CHANGED
@@ -4,4 +4,7 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'pry'
7
- gem 'codeclimate-test-reporter', group: :test, require: nil
7
+ group :test do
8
+ gem 'simplecov'
9
+ gem 'codeclimate-test-reporter', '~> 1.0.0'
10
+ end
data/README.md CHANGED
@@ -22,6 +22,10 @@ Or install it yourself as:
22
22
 
23
23
  $ gem install repeatable
24
24
 
25
+ ## Requirements
26
+
27
+ Because this gem relies heavily on required keyword arguments, especially to make dumping and parsing of schedules simpler, this code will only work on **Ruby 2.1** and higher.
28
+
25
29
  ## Usage
26
30
 
27
31
  ### Building a Schedule
@@ -33,9 +37,9 @@ You can create a schedule in one of two ways.
33
37
  Instantiate and compose each of the `Repeatable::Expression` objects manually.
34
38
 
35
39
  ```ruby
36
- second_monday = Repeatabe::Expression::WeekdayInMonth.new(weekday: 1, count: 2)
40
+ second_monday = Repeatable::Expression::WeekdayInMonth.new(weekday: 1, count: 2)
37
41
  oct_thru_dec = Repeatable::Expression::RangeInYear.new(start_month: 10, end_month: 12)
38
- intersection = Repeatable::Expresson::Intersection.new(second_monday, oct_thru_dec)
42
+ intersection = Repeatable::Expression::Intersection.new(second_monday, oct_thru_dec)
39
43
 
40
44
  schedule = Repeatable::Schedule.new(intersection)
41
45
  ```
@@ -49,7 +53,8 @@ Or describe the same structure with a `Hash`, and the gem will handle instantiat
49
53
  arg = {
50
54
  intersection: [
51
55
  { weekday_in_month: { weekday: 1, count: 2 } },
52
- { range_in_year: { start_month: 10, end_month: 12 } }
56
+ { range_in_year: { start_month: 10, end_month: 12 } },
57
+ { exact_date: { date: "2015-08-01" } }
53
58
  ]
54
59
  }
55
60
 
@@ -73,6 +78,10 @@ Repeatable::Expression::Union.new(expressions)
73
78
  { intersection: [] }
74
79
  Repeatable::Expression::Intersection.new(expressions)
75
80
 
81
+ # Date is part of the first set (`included`) but not part of the second set (`excluded`)
82
+ { difference: { included: expression, excluded: another_expression } }
83
+ Repeatable::Expression::Difference.new(included: expression, excluded: another_expression)
84
+
76
85
 
77
86
  # DATES
78
87
 
@@ -84,6 +93,10 @@ Repeatable::Expression::Weekday.new(weekday: 0)
84
93
  { weekday_in_month: { weekday: 1, count: 3 } }
85
94
  Repeatable::Expression::WeekdayInMonth.new(weekday: 1, count: 3)
86
95
 
96
+ # The last Thursday of every month
97
+ { weekday_in_month: { weekday: 4, count: -1 } }
98
+ Repeatable::Expression::WeekdayInMonth.new(weekday: 4, count: -1)
99
+
87
100
  # Every other Monday, starting from December 1, 2015
88
101
  { biweekly: { weekday: 1, start_date: '2015-12-01' } }
89
102
  Repeatable::Expression::Biweekly.new(weekday: 1, start_date: Date.new(2015, 12, 1))
@@ -92,6 +105,10 @@ Repeatable::Expression::Biweekly.new(weekday: 1, start_date: Date.new(2015, 12,
92
105
  { day_in_month: { day: 13 } }
93
106
  Repeatable::Expression::DayInMonth.new(day: 13)
94
107
 
108
+ # The last day of every month
109
+ { day_in_month: { day: :last } }
110
+ Repeatable::Expression::DayInMonth.new(day: :last)
111
+
95
112
  # All days in October
96
113
  { range_in_year: { start_month: 10 } }
97
114
  Repeatable::Expression::RangeInYear.new(start_month: 10)
@@ -103,6 +120,10 @@ Repeatable::Expression::RangeInYear.new(start_month: 10, end_month: 12)
103
120
  # All days from October 1 through December 20
104
121
  { range_in_year: { start_month: 10, end_month: 12, start_day: 1, end_day: 20 } }
105
122
  Repeatable::Expression::RangeInYear.new(start_month: 10, end_month: 12, start_day: 1, end_day: 20)
123
+
124
+ # only December 21, 2012
125
+ { exact_date: { date: '2012-12-21' } }
126
+ Repeatable::Expression::ExactDate.new(date: Date.new(2012, 12, 21)
106
127
  ```
107
128
 
108
129
  #### Schedule Errors
data/lib/repeatable.rb CHANGED
@@ -14,6 +14,7 @@ require 'repeatable/expression'
14
14
  require 'repeatable/expression/base'
15
15
 
16
16
  require 'repeatable/expression/date'
17
+ require 'repeatable/expression/exact_date'
17
18
  require 'repeatable/expression/weekday'
18
19
  require 'repeatable/expression/biweekly'
19
20
  require 'repeatable/expression/weekday_in_month'
@@ -23,6 +24,7 @@ require 'repeatable/expression/range_in_year'
23
24
  require 'repeatable/expression/set'
24
25
  require 'repeatable/expression/union'
25
26
  require 'repeatable/expression/intersection'
27
+ require 'repeatable/expression/difference'
26
28
 
27
29
  require 'repeatable/schedule'
28
30
  require 'repeatable/parser'
@@ -24,6 +24,22 @@ module Repeatable
24
24
  )
25
25
  end
26
26
 
27
+ def union(other)
28
+ Union.new(self, other)
29
+ end
30
+ alias + union
31
+ alias | union
32
+
33
+ def intersection(other)
34
+ Intersection.new(self, other)
35
+ end
36
+ alias & intersection
37
+
38
+ def difference(other)
39
+ Difference.new(included: self, excluded: other)
40
+ end
41
+ alias - difference
42
+
27
43
  private
28
44
 
29
45
  def hash_key
@@ -6,7 +6,11 @@ module Repeatable
6
6
  end
7
7
 
8
8
  def include?(date)
9
- date.day == day
9
+ if day.to_s == 'last'
10
+ date.next_day.month != date.month
11
+ else
12
+ date.day == day
13
+ end
10
14
  end
11
15
 
12
16
  private
@@ -0,0 +1,28 @@
1
+ module Repeatable
2
+ module Expression
3
+ class Difference < Base
4
+ def initialize(included:, excluded:)
5
+ @included = included
6
+ @excluded = excluded
7
+ end
8
+
9
+ def include?(date)
10
+ included.include?(date) && !excluded.include?(date)
11
+ end
12
+
13
+ def to_h
14
+ Hash[hash_key, { included: included.to_h, excluded: excluded.to_h }]
15
+ end
16
+
17
+ def ==(other)
18
+ other.is_a?(self.class) &&
19
+ included == other.included &&
20
+ excluded == other.excluded
21
+ end
22
+
23
+ protected
24
+
25
+ attr_reader :included, :excluded
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module Repeatable
2
+ module Expression
3
+ class ExactDate < Date
4
+ def initialize(date:)
5
+ @date = Date(date)
6
+ end
7
+
8
+ def include?(other_date)
9
+ date == other_date
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :date
15
+ end
16
+ end
17
+ end
@@ -9,7 +9,13 @@ module Repeatable
9
9
  end
10
10
 
11
11
  def include?(date)
12
- months_include?(date) || start_month_include?(date) || end_month_include?(date)
12
+ return true if months_include?(date)
13
+
14
+ if start_month == end_month
15
+ start_month_include?(date) && end_month_include?(date)
16
+ else
17
+ start_month_include?(date) || end_month_include?(date)
18
+ end
13
19
  end
14
20
 
15
21
  def to_h
@@ -19,11 +19,33 @@ module Repeatable
19
19
  end
20
20
 
21
21
  def week_matches?(date)
22
- week_in_month(date.day) == count
22
+ if negative_count?
23
+ week_from_end(date) == count
24
+ else
25
+ week_from_beginning(date) == count
26
+ end
23
27
  end
24
28
 
25
- def week_in_month(day)
26
- ((day - 1) / 7) + 1
29
+ def week_from_beginning(date)
30
+ week_in_month(date.day - 1)
31
+ end
32
+
33
+ def week_from_end(date)
34
+ -week_in_month(last_date_of_month(date).day - date.day)
35
+ end
36
+
37
+ def week_in_month(zero_indexed_day)
38
+ (zero_indexed_day / 7) + 1
39
+ end
40
+
41
+ def last_date_of_month(date)
42
+ next_month = date.next_month
43
+ first_day_of_next_month = ::Date.new(next_month.year, next_month.month, 1)
44
+ first_day_of_next_month.prev_day
45
+ end
46
+
47
+ def negative_count?
48
+ count < 0
27
49
  end
28
50
  end
29
51
  end
@@ -32,6 +32,12 @@ module Repeatable
32
32
  when Repeatable::Expression::Set
33
33
  args = value.map { |hash| build_expression(hash) }
34
34
  klass.new(*args)
35
+ when Repeatable::Expression::Difference
36
+ value = symbolize_keys(value)
37
+ klass.new(
38
+ included: build_expression(value[:included]),
39
+ excluded: build_expression(value[:excluded])
40
+ )
35
41
  else
36
42
  klass.new(symbolize_keys(value))
37
43
  end
@@ -1,3 +1,3 @@
1
1
  module Repeatable
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: repeatable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mo Lawson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-27 00:00:00.000000000 Z
11
+ date: 2017-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -77,6 +77,8 @@ files:
77
77
  - lib/repeatable/expression/biweekly.rb
78
78
  - lib/repeatable/expression/date.rb
79
79
  - lib/repeatable/expression/day_in_month.rb
80
+ - lib/repeatable/expression/difference.rb
81
+ - lib/repeatable/expression/exact_date.rb
80
82
  - lib/repeatable/expression/intersection.rb
81
83
  - lib/repeatable/expression/range_in_year.rb
82
84
  - lib/repeatable/expression/set.rb
@@ -108,9 +110,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
110
  version: '0'
109
111
  requirements: []
110
112
  rubyforge_project:
111
- rubygems_version: 2.4.5
113
+ rubygems_version: 2.5.2
112
114
  signing_key:
113
115
  specification_version: 4
114
116
  summary: Describe recurring event schedules and calculate their occurrences
115
117
  test_files: []
116
- has_rdoc: