repeatable 0.5.0 → 0.6.0

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.
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: