blackcal 0.2.0 → 0.3.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
  SHA256:
3
- metadata.gz: c23e691a5c3523795779efef15fe053f74136c0f0c1ce61acd8a57f8b988f761
4
- data.tar.gz: 631199640ed9b4af0c913ae4a8b6c54ec11d5b3098996dcc0dedcac7dfb3c1e5
3
+ metadata.gz: db977cf2c574a0e8d76891abfa30a6b8140e5ec07bfc0f2b23a215b03732d166
4
+ data.tar.gz: 8b3deb0295ba37f2ff1440cf8e4eed055dcdd77ac728a25e26b48edce491e381
5
5
  SHA512:
6
- metadata.gz: 15d610fec732303f9ea7badd1f8139298cb1161bdabf1baf6cee2c355a11cd826f3544747e7baa9159afed3818e83ae06d7fa32a68df7a4612c573e7daa49e27
7
- data.tar.gz: 1792395fc246472ca622b39920c67f5829d911f1431c8853c1697ab7d380fba6bc278af3a4c24c21f73d579d6019e3f89f2b58c6bef5eefa8299513944f474f0
6
+ metadata.gz: f13e108edab2e980c18b9650145b084bf01ec391e007d78612991a4a4337d47040a7dae5bcce4ad61acc8ddcb786809bafbdea3b6cf743d5e4e14c7b26035e03
7
+ data.tar.gz: 31e1e04b3929f06519a67a855e276b3771fc360b4e889d3f35b7bc5b3f4e0928617c8dfe2f0edd0cccca671b95e54d9d01c532cbaa4d7f08a145d219ed903271
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
3
+ --no-private
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ # v0.3.0
4
+
5
+ * Minute level resolution support
6
+ - Enhance `Schedule#to_matrix`
7
+ - Enhance `TimeOfDayRange`
8
+ - New `TimeOfDay` class
9
+ * Include `Enumerable` in all ranges
10
+
3
11
  # v0.2.0
4
12
 
5
13
  * Add `Blackcal::schedule`
data/README.md CHANGED
@@ -24,27 +24,27 @@ Schedule Mondays and Tuesdays
24
24
  ```ruby
25
25
  schedule = Blackcal.schedule(weekdays: [:monday, :tuesday])
26
26
  schedule.cover?('2019-01-01 19:00')
27
- # => false
28
- schedule.cover?('2019-01-02 11:00')
29
27
  # => true
28
+ schedule.cover?('2019-01-02 19:00')
29
+ # => false
30
30
  ```
31
31
 
32
32
  Schedule between 6pm and 7am every day
33
33
  ```ruby
34
34
  schedule = Blackcal.schedule(start_hour: 18, finish_hour: 7)
35
35
  schedule.cover?('2019-01-01 19:00')
36
- # => false
37
- schedule.cover?('2019-01-01 11:00')
38
36
  # => true
37
+ schedule.cover?('2019-01-01 11:00')
38
+ # => false
39
39
  ```
40
40
 
41
41
  Schedule day 15 and 17 of month
42
42
  ```ruby
43
43
  schedule = Blackcal.schedule(days: [15, 17])
44
44
  schedule.cover?('2019-01-15 19:00')
45
- # => false
46
- schedule.cover?('2019-01-01 11:00')
47
45
  # => true
46
+ schedule.cover?('2019-01-01 11:00')
47
+ # => false
48
48
  ```
49
49
 
50
50
  All options at once - schedule January, Mondays and Tuesdays, day 15-25, between 18pm and 7am
@@ -56,9 +56,9 @@ schedule = Blackcal.schedule(
56
56
  days: (15..25).to_a
57
57
  )
58
58
  schedule.cover?('2019-01-15 19:00')
59
- # => false
60
- schedule.cover?('2019-02-01 11:00')
61
59
  # => true
60
+ schedule.cover?('2019-02-01 11:00')
61
+ # => false
62
62
  ```
63
63
 
64
64
  Define when the schedule is active
@@ -68,9 +68,14 @@ Blackcal.schedule(start_time: '2018-01-01 11:00', finish_time: '2019-01-01 11:00
68
68
 
69
69
  Matrix representation
70
70
  ```ruby
71
- schedule = Blackcal.schedule(weekdays: :friday, start_hour: 10, finish_hour: 14)
71
+ schedule = Blackcal.schedule(weekdays: :friday, start_hour: 0, finish_hour: 14)
72
72
  schedule.to_matrix(start_date: '2018-09-14', finish_date: '2018-09-16')
73
- # => [[true, ...], [true, ...]]
73
+ # => [[true, ...], [false, ...]]
74
+
75
+ # defaults to hour resolution, but you can get minute resolution too
76
+ schedule = Blackcal.schedule(weekdays: :friday, start_hour: 0, finish_hour: 14)
77
+ schedule.to_matrix(resolution: :min, start_date: '2018-09-14', finish_date: '2018-09-16')
78
+ # => [[true, ...], [false, ...]]
74
79
  ```
75
80
 
76
81
  ## Development
data/blackcal.gemspec CHANGED
@@ -24,6 +24,9 @@ Gem::Specification.new do |spec|
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
26
26
 
27
+ spec.add_development_dependency 'github-markup', '~> 2.0'
28
+ spec.add_development_dependency 'redcarpet', '~> 3.4'
29
+ spec.add_development_dependency 'simplecov', '~> 0.16'
27
30
  spec.add_development_dependency 'bundler', '~> 1.16'
28
31
  spec.add_development_dependency 'byebug'
29
32
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -3,7 +3,9 @@
3
3
  module Blackcal
4
4
  # Number range
5
5
  class DayRange
6
- # @return [Array<Symbol>] numbers in range
6
+ include Enumerable
7
+
8
+ # @return [Array<Integer>] numbers in range
7
9
  attr_reader :numbers
8
10
 
9
11
  # Initialize numbers range
@@ -23,5 +25,14 @@ module Blackcal
23
25
 
24
26
  numbers.include?(timestamp.day)
25
27
  end
28
+
29
+ # @return [Array<Integer>] numbers in range
30
+ alias_method :to_a, :numbers
31
+
32
+ # Iterate over range
33
+ # @see #to_a
34
+ def each(&block)
35
+ to_a.each(&block)
36
+ end
26
37
  end
27
38
  end
@@ -3,6 +3,9 @@
3
3
  module Blackcal
4
4
  # Month range
5
5
  class MonthRange
6
+ include Enumerable
7
+
8
+ # Map month name to number
6
9
  MONTH_MAP = {
7
10
  january: 1,
8
11
  february: 2,
@@ -40,5 +43,14 @@ module Blackcal
40
43
  MONTH_MAP.fetch(month) == timestamp.month
41
44
  end
42
45
  end
46
+
47
+ # @return [Array<Symbol>] months in range
48
+ alias_method :to_a, :months
49
+
50
+ # Iterate over range
51
+ # @see #to_a
52
+ def each(&block)
53
+ to_a.each(&block)
54
+ end
43
55
  end
44
56
  end
@@ -1,26 +1,94 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'blackcal/time_of_day'
4
+
3
5
  module Blackcal
4
6
  # Time of day range
5
7
  class TimeOfDayRange
8
+ include Enumerable
9
+
6
10
  # Initialize time of day range
11
+ # @param [TimeOfDay, Time, Integer, nil] start
12
+ # @param [TimeOfDay, Time, Integer, nil] finish
7
13
  def initialize(start, finish = nil)
8
- @start = start || 0
9
- @finish = finish || 0
10
-
11
- @disallowed_hours = if @finish < @start
12
- (@start..23).to_a + (0..@finish).to_a
13
- else
14
- (@start..@finish).to_a
15
- end
14
+ @start = start
15
+ @finish = finish
16
16
  end
17
17
 
18
18
  # Returns true if it covers timestamp
19
19
  # @return [Boolean]
20
20
  def cover?(timestamp)
21
- hour = timestamp.hour
22
- # min = timestamp.min # TODO: Support minutes
23
- @disallowed_hours.include?(hour)
21
+ return false if @start.nil? && @finish.nil?
22
+
23
+ t1 = TimeOfDay.new(timestamp.hour, timestamp.min)
24
+ if start == finish
25
+ t1 == start
26
+ elsif start > finish
27
+ t1 <= finish || t1 >= start
28
+ else
29
+ t1 >= start && t1 <= finish
30
+ end
31
+ end
32
+
33
+ # Return start hour
34
+ # @return [TimeOfDay]
35
+ def start
36
+ @start_time ||= to_time_of_day(@start || 0)
37
+ end
38
+
39
+ # Return finish hour
40
+ # @return [TimeOfDay]
41
+ def finish
42
+ @finish_time ||= to_time_of_day(@finish || 0)
43
+ end
44
+
45
+ # Returns range as an array
46
+ # @param resolution [Symbol] :hour our :min
47
+ # @return [Array<Array<Integer>>] times
48
+ def to_a(resolution: :hour)
49
+ return [] if @start.nil? && @finish.nil?
50
+
51
+ if finish < start
52
+ to_time_array(start, TimeOfDay.new(23), resolution) +
53
+ to_time_array(TimeOfDay.new(0), finish, resolution)
54
+ else
55
+ to_time_array(start, finish, resolution)
56
+ end
57
+ end
58
+
59
+ # Iterate over range
60
+ # @param resolution [Symbol] :hour our :min
61
+ def each(resolution: :hour, &block)
62
+ to_a(resolution: resolution).each(&block)
63
+ end
64
+
65
+ private
66
+
67
+ def to_time_array(start, finish, resolution)
68
+ if resolution == :hour
69
+ return (start.hour..finish.hour).map { |hour| [hour, 0] }
70
+ end
71
+
72
+ # minute resolution
73
+ times = []
74
+ (start.hour..finish.hour).each do |hour|
75
+ finish_min = if hour == finish.hour && finish.min != 0
76
+ finish.min
77
+ else
78
+ 59
79
+ end
80
+ (start.min..finish_min).each { |min| times << [hour, min] }
81
+ end
82
+ times
83
+ end
84
+
85
+ def to_time_of_day(number_or_day)
86
+ return number_or_day if number_or_day.is_a?(TimeOfDay)
87
+ if number_or_day.is_a?(Time)
88
+ return TimeOfDay.new(number_or_day.hour, number_or_day.min)
89
+ end
90
+
91
+ TimeOfDay.new(number_or_day)
24
92
  end
25
93
  end
26
94
  end
@@ -3,9 +3,13 @@
3
3
  module Blackcal
4
4
  # Time range
5
5
  class TimeRange
6
+ include Enumerable
7
+
6
8
  attr_reader :start, :finish
7
9
 
8
10
  # Initialize time range
11
+ # @param [Time, nil] start_time
12
+ # @param [Time, nil] finish_time optional
9
13
  def initialize(start_time, finish_time = nil)
10
14
  @start = start_time
11
15
  @finish = finish_time
@@ -23,5 +27,23 @@ module Blackcal
23
27
 
24
28
  false
25
29
  end
30
+
31
+ # Returns range as array
32
+ # @param resolution [Symbol] :hour our :min
33
+ # @return [Array<Array<Integer>>] times
34
+ def to_a(resolution: :hour)
35
+ resolution_multiplier = resolution == :hour ? 60 * 60 : 60
36
+ time_units = ((start - finish) / resolution_multiplier).abs.to_i
37
+
38
+ time_units.times.map do |time_unit|
39
+ start + (time_unit * resolution_multiplier)
40
+ end
41
+ end
42
+
43
+ # Iterate over range
44
+ # @param resolution [Symbol] :hour our :min
45
+ def each(resolution: :hour, &block)
46
+ to_a(resolution: resolution).each(&block)
47
+ end
26
48
  end
27
49
  end
@@ -3,6 +3,9 @@
3
3
  module Blackcal
4
4
  # Weekday range
5
5
  class WeekdayRange
6
+ include Enumerable
7
+
8
+ # Map weekday name to number
6
9
  WEEKDAY_MAP = {
7
10
  sunday: 0,
8
11
  monday: 1,
@@ -35,5 +38,14 @@ module Blackcal
35
38
  WEEKDAY_MAP.fetch(weekday) == timestamp.wday
36
39
  end
37
40
  end
41
+
42
+ # @return [Array<Symbol>] weekdays in range
43
+ alias_method :to_a, :weekdays
44
+
45
+ # Iterate over range
46
+ # @see #to_a
47
+ def each(&block)
48
+ to_a.each(&block)
49
+ end
38
50
  end
39
51
  end
@@ -15,11 +15,16 @@ module Blackcal
15
15
  # Initialize rule
16
16
  # @param start_time [Time, Date, String, nil]
17
17
  # @param finish_time [Time, Date, String, nil]
18
- # @param start_hour [Integer, nil]
19
- # @param finish_hour [Integer, nil]
18
+ # @param start_hour [TimeOfDay, Time, Integer, nil]
19
+ # @param finish_hour [TimeOfDay, Time, Integer, nil]
20
20
  # @param months [Array<String>, Array<Symbol>, String, Symbol, nil]
21
21
  # @param weekdays [Array<String>, Array<Symbol>, String, Symbol, nil]
22
22
  # @param days [Array<Integer>, Integer, nil]
23
+ # @see TimeRange#initialize
24
+ # @see TimeOfDayRange#initialize
25
+ # @see MonthRange#initialize
26
+ # @see WeekdayRange#initialize
27
+ # @see DayRange#initialize
23
28
  def initialize(
24
29
  start_time: nil,
25
30
  finish_time: nil,
@@ -30,11 +35,25 @@ module Blackcal
30
35
  # weeks_of_month: nil, # TODO
31
36
  days: nil
32
37
  )
33
- @rule_range = TimeRange.new(parse_time(start_time), parse_time(finish_time)) if start_time || finish_time # rubocop:disable Metrics/LineLength
34
- @time_of_day = TimeOfDayRange.new(start_hour, finish_hour) if start_hour || finish_hour # rubocop:disable Metrics/LineLength
35
- @months = MonthRange.new(months) if months
36
- @weekdays = WeekdayRange.new(weekdays) if weekdays
37
- @days = DayRange.new(days) if days
38
+ if start_time || finish_time
39
+ @rule_range = TimeRange.new(parse_time(start_time), parse_time(finish_time))
40
+ end
41
+
42
+ if start_hour || finish_hour
43
+ @time_of_day = TimeOfDayRange.new(start_hour, finish_hour)
44
+ end
45
+
46
+ if months
47
+ @months = MonthRange.new(months)
48
+ end
49
+
50
+ if weekdays
51
+ @weekdays = WeekdayRange.new(weekdays)
52
+ end
53
+
54
+ if days
55
+ @days = DayRange.new(days)
56
+ end
38
57
  end
39
58
 
40
59
  # Returns true if calendar is open for timestamp
@@ -42,28 +61,29 @@ module Blackcal
42
61
  # @return [Boolean]
43
62
  def cover?(timestamp)
44
63
  timestamp = parse_time(timestamp)
45
- return true if @rule_range && !@rule_range.cover?(timestamp)
64
+ return false if @rule_range && !@rule_range.cover?(timestamp)
46
65
 
47
- [@months, @weekdays, @days, @time_of_day].each do |range|
48
- return true if range && !range.cover?(timestamp)
49
- end
66
+ ranges = [@months, @weekdays, @days, @time_of_day].compact
67
+ return false if ranges.empty?
50
68
 
51
- false
69
+ ranges.all? { |range| range.cover?(timestamp) }
52
70
  end
53
71
 
54
72
  # Returns schedule represented as a matrix
55
73
  # @param start_date [Date]
56
74
  # @param finish_date [Date]
57
75
  # @return [Array<Array<Boolean>>]
58
- def to_matrix(start_date:, finish_date:)
76
+ def to_matrix(start_date:, finish_date:, resolution: :hour)
59
77
  start_time = parse_time(start_date).to_time
60
78
  finish_time = parse_time(finish_date).to_time
61
- matrix = SlotMatrix.new(24)
79
+ slots = resolution == :hour ? 24 : 24 * 60
80
+ matrix = SlotMatrix.new(slots)
62
81
 
63
82
  # TODO: This is needlessly inefficient..
64
- seconds = (start_time - finish_time).abs
65
- hours = (seconds / (60 * 60)).to_i
66
- hours.times { |hour| matrix << cover?(start_time + (hour * 60 * 60)) }
83
+ time_range = TimeRange.new(start_time, finish_time)
84
+ time_range.each(resolution: resolution) do |time|
85
+ matrix << cover?(time)
86
+ end
67
87
 
68
88
  matrix.data
69
89
  end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blackcal
4
+ # Slot matrix
4
5
  class SlotMatrix
6
+ # Initialize slot matrix
7
+ # @param [Integer] slots max elements in each slot
5
8
  def initialize(slots)
6
9
  @matrix = [[]]
7
10
  @slots = slots
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blackcal
4
+ # Represents a time of day (hour and min)
5
+ class TimeOfDay
6
+ include Comparable
7
+
8
+ # @return [Integer] hour
9
+ attr_reader :hour
10
+
11
+ # @return [Integer] minutes defaults to 0
12
+ attr_reader :min
13
+
14
+ # Initialize time of day
15
+ # @param [Integer] hour
16
+ # @param [Integer, nil] min optional argument
17
+ def initialize(hour, min = nil)
18
+ @hour = hour
19
+ @min = min || 0
20
+ end
21
+
22
+ # Compares two time of days
23
+ # @param [TimeOfDay, Integer] other if a number is passed it will be used as the hour
24
+ # @return [Integer] 1 if greater than, 0 if equal, -1 if less than
25
+ def <=>(other)
26
+ other_seconds = if other.is_a?(self.class)
27
+ (other.hour * 60 * 60) + (other.min * 60)
28
+ else
29
+ other * 60 * 60
30
+ end
31
+ seconds = (hour * 60 * 60) + (min * 60)
32
+
33
+ seconds <=> other_seconds
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blackcal
4
- VERSION = '0.2.0'.freeze
4
+ # Gem version
5
+ VERSION = '0.3.0'.freeze
5
6
  end
metadata CHANGED
@@ -1,15 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blackcal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Burenstam
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-15 00:00:00.000000000 Z
11
+ date: 2018-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: github-markup
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redcarpet
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.16'
13
55
  - !ruby/object:Gem::Dependency
14
56
  name: bundler
15
57
  requirement: !ruby/object:Gem::Requirement
@@ -79,6 +121,7 @@ files:
79
121
  - ".rubocop.yml"
80
122
  - ".ruby-style-guide.yml"
81
123
  - ".travis.yml"
124
+ - ".yardopts"
82
125
  - CHANGELOG.md
83
126
  - Gemfile
84
127
  - LICENSE.txt
@@ -95,6 +138,7 @@ files:
95
138
  - lib/blackcal/range/weekday_range.rb
96
139
  - lib/blackcal/schedule.rb
97
140
  - lib/blackcal/slot_matrix.rb
141
+ - lib/blackcal/time_of_day.rb
98
142
  - lib/blackcal/version.rb
99
143
  homepage: https://github.com/buren/blackcal
100
144
  licenses: