ice_cube 0.16.2 → 0.16.3

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ice_cube.rb +1 -3
  3. data/lib/ice_cube/i18n.rb +10 -7
  4. data/lib/ice_cube/input_alignment.rb +89 -0
  5. data/lib/ice_cube/null_i18n.rb +12 -6
  6. data/lib/ice_cube/occurrence.rb +25 -23
  7. data/lib/ice_cube/parsers/ical_parser.rb +7 -4
  8. data/lib/ice_cube/rule.rb +4 -11
  9. data/lib/ice_cube/rules/daily_rule.rb +9 -0
  10. data/lib/ice_cube/rules/hourly_rule.rb +9 -0
  11. data/lib/ice_cube/rules/minutely_rule.rb +9 -0
  12. data/lib/ice_cube/rules/monthly_rule.rb +9 -0
  13. data/lib/ice_cube/rules/secondly_rule.rb +9 -0
  14. data/lib/ice_cube/rules/weekly_rule.rb +10 -1
  15. data/lib/ice_cube/rules/yearly_rule.rb +9 -0
  16. data/lib/ice_cube/schedule.rb +10 -9
  17. data/lib/ice_cube/single_occurrence_rule.rb +4 -0
  18. data/lib/ice_cube/time_util.rb +26 -16
  19. data/lib/ice_cube/validated_rule.rb +10 -19
  20. data/lib/ice_cube/validations/count.rb +1 -2
  21. data/lib/ice_cube/validations/daily_interval.rb +5 -1
  22. data/lib/ice_cube/validations/day.rb +6 -2
  23. data/lib/ice_cube/validations/day_of_month.rb +5 -0
  24. data/lib/ice_cube/validations/hour_of_day.rb +23 -0
  25. data/lib/ice_cube/validations/hourly_interval.rb +2 -0
  26. data/lib/ice_cube/validations/minute_of_hour.rb +16 -0
  27. data/lib/ice_cube/validations/minutely_interval.rb +2 -0
  28. data/lib/ice_cube/validations/month_of_year.rb +5 -0
  29. data/lib/ice_cube/validations/monthly_interval.rb +4 -1
  30. data/lib/ice_cube/validations/schedule_lock.rb +4 -0
  31. data/lib/ice_cube/validations/second_of_minute.rb +19 -3
  32. data/lib/ice_cube/validations/secondly_interval.rb +2 -0
  33. data/lib/ice_cube/validations/until.rb +1 -2
  34. data/lib/ice_cube/validations/weekly_interval.rb +0 -2
  35. data/lib/ice_cube/version.rb +1 -1
  36. data/spec/spec_helper.rb +32 -9
  37. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 51dd94d70b0d51847c678abf736d395ddbee5e2e
4
- data.tar.gz: 2b69372e7e4e67487347cab5a8a773da40a87e91
3
+ metadata.gz: afc29d6bebdb8a25e7926863560b6b73214887d7
4
+ data.tar.gz: 850cad27d4f06dd30052222e4c30f6fa8b3b50e5
5
5
  SHA512:
6
- metadata.gz: d20e08c68105003f8a6c3f192c679d185c07e43681543844f890d00947d94d6c42d5e82a350e83cab506c74183bbd42eef2cb8d5deeab8f7979a819884221dff
7
- data.tar.gz: f1ab8c1b534dd70e185da9b272a973202cb071f29bd90cb2a31fa7d83d1b3f0e61c321d3d402e79e0059cf698374a0465dc954218de77afe0ac459cc0f990f47
6
+ metadata.gz: 351c83281b34657ff2a64c8fc03e023265803ba8776d6f6a98c90fbbf04f14c0e0c9a7d7ee40f5a8c739cea31283b60c429b37521f7fe4e7ea61bc2175123c46
7
+ data.tar.gz: d02fba63d82b7a26077cadf400a0bd45172d6d047c26533164f786654b925e0b6e28902cefec8ada0a2df10f08596237c848271ffbb85094323ec515a71f2431
@@ -1,8 +1,5 @@
1
1
  require 'date'
2
2
  require 'ice_cube/deprecated'
3
- require 'ice_cube/i18n'
4
-
5
- IceCube::I18n.detect_backend!
6
3
 
7
4
  module IceCube
8
5
 
@@ -10,6 +7,7 @@ module IceCube
10
7
 
11
8
  autoload :TimeUtil, 'ice_cube/time_util'
12
9
  autoload :FlexibleHash, 'ice_cube/flexible_hash'
10
+ autoload :I18n, 'ice_cube/i18n'
13
11
 
14
12
  autoload :Rule, 'ice_cube/rule'
15
13
  autoload :Schedule, 'ice_cube/schedule'
@@ -1,5 +1,10 @@
1
+ require 'ice_cube/null_i18n'
2
+
1
3
  module IceCube
2
4
  module I18n
5
+
6
+ LOCALES_PATH = File.expand_path(File.join('..', '..', '..', 'config', 'locales'), __FILE__)
7
+
3
8
  def self.t(*args)
4
9
  backend.t(*args)
5
10
  end
@@ -9,16 +14,14 @@ module IceCube
9
14
  end
10
15
 
11
16
  def self.backend
12
- @backend
17
+ @backend ||= detect_backend!
13
18
  end
14
19
 
15
20
  def self.detect_backend!
16
- require 'i18n'
17
- ::I18n.load_path += Dir[File.expand_path('../../../config/locales/*{rb,yml}', __FILE__)]
18
- @backend = ::I18n
19
- rescue LoadError
20
- require 'ice_cube/null_i18n'
21
- @backend = NullI18n
21
+ ::I18n.load_path += Dir[File.join(LOCALES_PATH, '*.yml')]
22
+ ::I18n
23
+ rescue NameError
24
+ NullI18n
22
25
  end
23
26
  end
24
27
  end
@@ -0,0 +1,89 @@
1
+ module IceCube
2
+ class InputAlignment
3
+
4
+ def initialize(rule, value, rule_part)
5
+ @rule = rule
6
+ @value = value
7
+ @rule_part = rule_part
8
+ end
9
+
10
+ attr_reader :rule, :value, :rule_part
11
+
12
+ def verify(freq, options={}, &block)
13
+ @rule.validations[:interval] or return
14
+
15
+ case @rule
16
+ when DailyRule
17
+ verify_wday_alignment(freq, &block)
18
+ when MonthlyRule
19
+ verify_month_alignment(freq, &block)
20
+ else
21
+ verify_freq_alignment(freq, &block)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def interval_validation
28
+ @interval_validation ||= @rule.validations[:interval].first
29
+ end
30
+
31
+ def interval_value
32
+ @interval_value ||= (rule_part == :interval) ? value : interval_validation.interval
33
+ end
34
+
35
+ def fixed_validations
36
+ @fixed_validations ||= @rule.validations.values.flatten.select { |v|
37
+ interval_type = (v.type == :wday ? :day : v.type)
38
+ v.class < Validations::FixedValue &&
39
+ interval_type == rule.base_interval_validation.type
40
+ }
41
+ end
42
+
43
+ def verify_freq_alignment(freq)
44
+ interval_validation.type == freq or return
45
+ (last_validation = fixed_validations.min_by(&:value)) or return
46
+
47
+ alignment = (value - last_validation.value) % interval_validation.interval
48
+ return if alignment.zero?
49
+
50
+ validation_values = fixed_validations.map(&:value).join(', ')
51
+ if rule_part == :interval
52
+ message = "interval(#{value}) " \
53
+ "must be a multiple of " \
54
+ "intervals in #{last_validation.key}(#{validation_values})"
55
+ else
56
+ message = "intervals in #{last_validation.key}(#{validation_values}, #{value}) " \
57
+ "must be multiples of " \
58
+ "interval(#{interval_validation.interval})"
59
+ end
60
+
61
+ yield ArgumentError.new(message)
62
+ end
63
+
64
+ def verify_month_alignment(_freq)
65
+ return if interval_value == 1 || (interval_value % 12).zero?
66
+ return if fixed_validations.empty?
67
+
68
+ message = "month_of_year can only be used with interval(1) or multiples of interval(12)"
69
+
70
+ yield ArgumentError.new(message)
71
+ end
72
+
73
+ def verify_wday_alignment(freq)
74
+ return if interval_value == 1
75
+
76
+ if freq == :wday
77
+ return if (interval_value % 7).zero?
78
+ return if Array(@rule.validations[:day]).empty?
79
+ message = "day can only be used with multiples of interval(7)"
80
+ else
81
+ (fixed_validation = fixed_validations.first) or return
82
+ message = "#{fixed_validation.key} can only be used with interval(1)"
83
+ end
84
+
85
+ yield ArgumentError.new(message)
86
+ end
87
+
88
+ end
89
+ end
@@ -7,13 +7,19 @@ module IceCube
7
7
 
8
8
  base = base[options[:count] == 1 ? "one" : "other"] if options[:count]
9
9
 
10
- if base.is_a?(Hash)
11
- return base.each_with_object({}) do |(key, value), hash|
12
- hash[key.is_a?(String) ? key.to_sym : key] = value
10
+ case base
11
+ when Hash
12
+ base.each_with_object({}) do |(k, v), hash|
13
+ hash[k.is_a?(String) ? k.to_sym : k] = v
13
14
  end
15
+ when Array
16
+ base.each_with_index.each_with_object({}) do |(v, k), hash|
17
+ hash[k] = v
18
+ end
19
+ else
20
+ return base unless base.include?('%{')
21
+ base % options
14
22
  end
15
-
16
- options.reduce(base) { |result, (find, replace)| result.gsub("%{#{find}}", "#{replace}") }
17
23
  end
18
24
 
19
25
  def self.l(date_or_time, options = {})
@@ -22,7 +28,7 @@ module IceCube
22
28
  end
23
29
 
24
30
  def self.config
25
- @config ||= YAML.load(File.read(File.join(File.dirname(__FILE__), '..', '..', 'config', 'locales', 'en.yml')))['en']
31
+ @config ||= YAML.load_file(File.join(IceCube::I18n::LOCALES_PATH, 'en.yml'))['en']
26
32
  end
27
33
  end
28
34
  end
@@ -1,4 +1,3 @@
1
- require 'forwardable'
2
1
  require 'delegate'
3
2
 
4
3
  module IceCube
@@ -20,18 +19,16 @@ module IceCube
20
19
  # Time.now - Occurrence.new(start_time) # => 3600
21
20
  #
22
21
  class Occurrence < SimpleDelegator
22
+ include Comparable
23
23
 
24
24
  # Report class name as 'Time' to thwart type checking.
25
25
  def self.name
26
26
  'Time'
27
27
  end
28
28
 
29
- # Optimize for common methods to avoid method_missing
30
- extend Forwardable
31
- def_delegators :start_time, :to_i, :<=>, :==
32
- def_delegators :to_range, :cover?, :include?, :each, :first, :last
33
-
34
29
  attr_reader :start_time, :end_time
30
+ alias first start_time
31
+ alias last end_time
35
32
 
36
33
  def initialize(start_time, end_time=nil)
37
34
  @start_time = start_time
@@ -39,29 +36,34 @@ module IceCube
39
36
  __setobj__ @start_time
40
37
  end
41
38
 
39
+ def to_i
40
+ @start_time.to_i
41
+ end
42
+
43
+ def <=>(other)
44
+ @start_time <=> other
45
+ end
46
+
42
47
  def is_a?(klass)
43
48
  klass == ::Time || super
44
49
  end
45
50
  alias_method :kind_of?, :is_a?
46
51
 
47
- def intersects? other
48
- if other.is_a?(Occurrence) || other.is_a?(Range)
49
- lower_bound_1 = first + 1
50
- upper_bound_1 = last # exclude end
51
- lower_bound_2 = other.first + 1
52
- upper_bound_2 = other.last + 1
53
- if (lower_bound_2 <=> upper_bound_2) > 0
54
- false
55
- elsif (lower_bound_1 <=> upper_bound_1) > 0
56
- false
57
- else
58
- (upper_bound_1 <=> lower_bound_2) >= 0 and
59
- (upper_bound_2 <=> lower_bound_1) >= 0
60
- end
61
- else
62
- cover? other
63
- end
52
+ def intersects?(other)
53
+ return cover?(other) unless other.is_a?(Occurrence) || other.is_a?(Range)
54
+
55
+ this_start = first + 1
56
+ this_end = last # exclude end boundary
57
+ other_start = other.first + 1
58
+ other_end = other.last + 1
59
+
60
+ !(this_end < other_start || this_start > other_end)
61
+ end
62
+
63
+ def cover?(other)
64
+ to_range.cover?(other)
64
65
  end
66
+ alias_method :include?, :cover?
65
67
 
66
68
  def comparable_time
67
69
  start_time
@@ -7,12 +7,15 @@ module IceCube
7
7
  (property, tzid) = property.split(';')
8
8
  case property
9
9
  when 'DTSTART'
10
- data[:start_time] = Time.parse(value)
10
+ data[:start_time] = TimeUtil.deserialize_time(value)
11
11
  when 'DTEND'
12
- data[:end_time] = Time.parse(value)
12
+ data[:end_time] = TimeUtil.deserialize_time(value)
13
+ when 'RDATE'
14
+ data[:rtimes] ||= []
15
+ data[:rtimes] += value.split(',').map { |v| TimeUtil.deserialize_time(v) }
13
16
  when 'EXDATE'
14
17
  data[:extimes] ||= []
15
- data[:extimes] += value.split(',').map{|v| Time.parse(v)}
18
+ data[:extimes] += value.split(',').map { |v| TimeUtil.deserialize_time(v) }
16
19
  when 'DURATION'
17
20
  data[:duration] # FIXME
18
21
  when 'RRULE'
@@ -41,7 +44,7 @@ module IceCube
41
44
  when 'COUNT'
42
45
  params[:count] = value.to_i
43
46
  when 'UNTIL'
44
- params[:until] = Time.parse(value).utc
47
+ params[:until] = TimeUtil.deserialize_time(value).utc
45
48
  when 'WKST'
46
49
  params[:week_start] = TimeUtil.ical_day_to_symbol(value)
47
50
  when 'BYSECOND'
@@ -19,11 +19,9 @@ module IceCube
19
19
  until_time || occurrence_count
20
20
  end
21
21
 
22
- def ==(rule)
23
- if rule.is_a? Rule
24
- hash = to_hash
25
- hash && hash == rule.to_hash
26
- end
22
+ def ==(other)
23
+ return false unless other.is_a? Rule
24
+ hash == other.hash
27
25
  end
28
26
 
29
27
  def hash
@@ -31,7 +29,7 @@ module IceCube
31
29
  end
32
30
 
33
31
  def to_ical
34
- raise MethodNotImplemented, "Expected to be overrridden by subclasses"
32
+ raise MethodNotImplemented, "Expected to be overridden by subclasses"
35
33
  end
36
34
 
37
35
  # Convert from ical string and create a rule
@@ -60,11 +58,6 @@ module IceCube
60
58
  next_time(time, schedule, time).to_i == time.to_i
61
59
  end
62
60
 
63
- # Whether this rule requires a full run
64
- def full_required?
65
- !@count.nil?
66
- end
67
-
68
61
  class << self
69
62
 
70
63
  # Convert from a hash and create a rule
@@ -2,6 +2,15 @@ module IceCube
2
2
 
3
3
  class DailyRule < ValidatedRule
4
4
 
5
+ include Validations::HourOfDay
6
+ include Validations::MinuteOfHour
7
+ include Validations::SecondOfMinute
8
+ include Validations::DayOfMonth
9
+ include Validations::DayOfWeek
10
+ include Validations::Day
11
+ include Validations::MonthOfYear
12
+ # include Validations::DayOfYear # n/a
13
+
5
14
  include Validations::DailyInterval
6
15
 
7
16
  def initialize(interval = 1)
@@ -2,6 +2,15 @@ module IceCube
2
2
 
3
3
  class HourlyRule < ValidatedRule
4
4
 
5
+ include Validations::HourOfDay
6
+ include Validations::MinuteOfHour
7
+ include Validations::SecondOfMinute
8
+ include Validations::DayOfMonth
9
+ include Validations::DayOfWeek
10
+ include Validations::Day
11
+ include Validations::MonthOfYear
12
+ include Validations::DayOfYear
13
+
5
14
  include Validations::HourlyInterval
6
15
 
7
16
  def initialize(interval = 1)
@@ -2,6 +2,15 @@ module IceCube
2
2
 
3
3
  class MinutelyRule < ValidatedRule
4
4
 
5
+ include Validations::HourOfDay
6
+ include Validations::MinuteOfHour
7
+ include Validations::SecondOfMinute
8
+ include Validations::DayOfMonth
9
+ include Validations::DayOfWeek
10
+ include Validations::Day
11
+ include Validations::MonthOfYear
12
+ include Validations::DayOfYear
13
+
5
14
  include Validations::MinutelyInterval
6
15
 
7
16
  def initialize(interval = 1)
@@ -2,6 +2,15 @@ module IceCube
2
2
 
3
3
  class MonthlyRule < ValidatedRule
4
4
 
5
+ include Validations::HourOfDay
6
+ include Validations::MinuteOfHour
7
+ include Validations::SecondOfMinute
8
+ include Validations::DayOfMonth
9
+ include Validations::DayOfWeek
10
+ include Validations::Day
11
+ include Validations::MonthOfYear
12
+ # include Validations::DayOfYear # n/a
13
+
5
14
  include Validations::MonthlyInterval
6
15
 
7
16
  def initialize(interval = 1)
@@ -2,6 +2,15 @@ module IceCube
2
2
 
3
3
  class SecondlyRule < ValidatedRule
4
4
 
5
+ include Validations::HourOfDay
6
+ include Validations::MinuteOfHour
7
+ include Validations::SecondOfMinute
8
+ include Validations::DayOfMonth
9
+ include Validations::DayOfWeek
10
+ include Validations::Day
11
+ include Validations::MonthOfYear
12
+ include Validations::DayOfYear
13
+
5
14
  include Validations::SecondlyInterval
6
15
 
7
16
  def initialize(interval = 1)
@@ -2,6 +2,15 @@ module IceCube
2
2
 
3
3
  class WeeklyRule < ValidatedRule
4
4
 
5
+ include Validations::HourOfDay
6
+ include Validations::MinuteOfHour
7
+ include Validations::SecondOfMinute
8
+ # include Validations::DayOfMonth # n/a
9
+ include Validations::DayOfWeek
10
+ include Validations::Day
11
+ include Validations::MonthOfYear
12
+ # include Validations::DayOfYear # n/a
13
+
5
14
  include Validations::WeeklyInterval
6
15
 
7
16
  attr_reader :week_start
@@ -28,7 +37,7 @@ module IceCube
28
37
  time = TimeUtil::TimeWrapper.new(start_time)
29
38
  offset = wday_offset(step_time, start_time)
30
39
  time.add(:day, offset)
31
- time.to_time
40
+ super step_time, time.to_time
32
41
  end
33
42
 
34
43
  # Calculate how many days to the first wday validation in the correct
@@ -2,6 +2,15 @@ module IceCube
2
2
 
3
3
  class YearlyRule < ValidatedRule
4
4
 
5
+ include Validations::HourOfDay
6
+ include Validations::MinuteOfHour
7
+ include Validations::SecondOfMinute
8
+ include Validations::DayOfMonth
9
+ include Validations::DayOfWeek
10
+ include Validations::Day
11
+ include Validations::MonthOfYear
12
+ include Validations::DayOfYear
13
+
5
14
  include Validations::YearlyInterval
6
15
 
7
16
  def initialize(interval = 1)
@@ -340,9 +340,9 @@ module IceCube
340
340
  IcalParser.schedule_from_ical(ical, options)
341
341
  end
342
342
 
343
- # Convert the schedule to yaml
344
- def to_yaml(*args)
345
- YAML::dump(to_hash, *args)
343
+ # Hook for YAML.dump, enables to_yaml
344
+ def encode_with(coder)
345
+ coder.represent_object nil, to_hash
346
346
  end
347
347
 
348
348
  # Load the schedule from yaml
@@ -371,6 +371,7 @@ module IceCube
371
371
  end
372
372
  data
373
373
  end
374
+ alias_method :to_h, :to_hash
374
375
 
375
376
  # Load the schedule from a hash
376
377
  def self.from_hash(original_hash, options = {})
@@ -383,7 +384,7 @@ module IceCube
383
384
  # Determine if the schedule will end
384
385
  # @return [Boolean] true if ending, false if repeating forever
385
386
  def terminating?
386
- recurrence_rules.empty? || recurrence_rules.all?(&:terminating?)
387
+ @all_recurrence_rules.all?(&:terminating?)
387
388
  end
388
389
 
389
390
  def hash
@@ -423,7 +424,7 @@ module IceCube
423
424
  def enumerate_occurrences(opening_time, closing_time = nil, options = {})
424
425
  opening_time = TimeUtil.match_zone(opening_time, start_time)
425
426
  closing_time = TimeUtil.match_zone(closing_time, start_time)
426
- opening_time += start_time.subsec - opening_time.subsec rescue 0
427
+ opening_time += TimeUtil.subsec(start_time) - TimeUtil.subsec(opening_time)
427
428
  opening_time = start_time if opening_time < start_time
428
429
  spans = options[:spans] == true && duration != 0
429
430
  Enumerator.new do |yielder|
@@ -445,12 +446,12 @@ module IceCube
445
446
  # Get the next time after (or including) a specific time
446
447
  def next_time(time, closing_time)
447
448
  loop do
448
- min_time = recurrence_rules_with_implicit_start_occurrence.reduce(nil) do |min_time, rule|
449
+ min_time = recurrence_rules_with_implicit_start_occurrence.reduce(nil) do |best_time, rule|
449
450
  begin
450
- new_time = rule.next_time(time, start_time, min_time || closing_time)
451
- [min_time, new_time].compact.min
451
+ new_time = rule.next_time(time, start_time, best_time || closing_time)
452
+ [best_time, new_time].compact.min
452
453
  rescue StopIteration
453
- min_time
454
+ best_time
454
455
  end
455
456
  end
456
457
  break unless min_time
@@ -23,6 +23,10 @@ module IceCube
23
23
  { :time => time }
24
24
  end
25
25
 
26
+ def full_required?
27
+ false
28
+ end
29
+
26
30
  end
27
31
 
28
32
  end
@@ -170,20 +170,13 @@ module IceCube
170
170
 
171
171
  # Convert wday number to day symbol
172
172
  def self.wday_to_sym(wday)
173
- return sym = wday if DAYS.keys.include? wday
173
+ return wday if DAYS.keys.include? wday
174
174
  DAYS.invert.fetch(wday) do |i|
175
175
  raise ArgumentError, "Expecting Integer value for weekday. " \
176
176
  "No such wday number: #{i.inspect}"
177
177
  end
178
178
  end
179
179
 
180
- # Convert a symbol to an ical day (SU, MO)
181
- def self.week_start(sym)
182
- raise ArgumentError, "Invalid day: #{str}" unless DAYS.keys.include?(sym)
183
- day = sym.to_s.upcase[0..1]
184
- day
185
- end
186
-
187
180
  # Convert weekday from base sunday to the schedule's week start.
188
181
  def self.normalize_wday(wday, week_start)
189
182
  (wday - sym_to_wday(week_start)) % 7
@@ -260,8 +253,19 @@ module IceCube
260
253
  end
261
254
  end
262
255
 
263
- def self.same_clock?(t1, t2)
264
- CLOCK_VALUES.all? { |i| t1.send(i) == t2.send(i) }
256
+ # Handle discrepancies between various time types
257
+ # - Time has subsec
258
+ # - DateTime does not
259
+ # - ActiveSupport::TimeWithZone can wrap either type, depending on version
260
+ # or if `parse` or `now`/`local` was used to build it.
261
+ def self.subsec(time)
262
+ if time.respond_to?(:subsec)
263
+ time.subsec
264
+ elsif time.respond_to?(:sec_fraction)
265
+ time.sec_fraction
266
+ else
267
+ 0.0
268
+ end
265
269
  end
266
270
 
267
271
  # A utility class for safely moving time around
@@ -271,7 +275,7 @@ module IceCube
271
275
  @dst_adjust = dst_adjust
272
276
  @base = time
273
277
  if dst_adjust
274
- @time = Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec)
278
+ @time = Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + TimeUtil.subsec(time))
275
279
  else
276
280
  @time = time
277
281
  end
@@ -307,7 +311,17 @@ module IceCube
307
311
  end
308
312
  end
309
313
 
310
- private
314
+ def hour=(value)
315
+ @time += (value * ONE_HOUR) - (@time.hour * ONE_HOUR)
316
+ end
317
+
318
+ def min=(value)
319
+ @time += (value * ONE_MINUTE) - (@time.min * ONE_MINUTE)
320
+ end
321
+
322
+ def sec=(value)
323
+ @time += (value) - (@time.sec)
324
+ end
311
325
 
312
326
  def clear_sec
313
327
  @time.sec > 0 ? @time -= @time.sec : @time
@@ -335,10 +349,6 @@ module IceCube
335
349
  @time += ONE_DAY
336
350
  end
337
351
 
338
- def clear_year
339
- @time
340
- end
341
-
342
352
  end
343
353
 
344
354
  end
@@ -1,18 +1,11 @@
1
+ require 'ice_cube/input_alignment'
2
+
1
3
  module IceCube
2
4
 
3
5
  class ValidatedRule < Rule
4
6
 
5
7
  include Validations::ScheduleLock
6
8
 
7
- include Validations::HourOfDay
8
- include Validations::MinuteOfHour
9
- include Validations::SecondOfMinute
10
- include Validations::DayOfMonth
11
- include Validations::DayOfWeek
12
- include Validations::Day
13
- include Validations::MonthOfYear
14
- include Validations::DayOfYear
15
-
16
9
  include Validations::Count
17
10
  include Validations::Until
18
11
 
@@ -51,10 +44,6 @@ module IceCube
51
44
  Array(@validations[base_interval_validation.type])
52
45
  end
53
46
 
54
- def base_interval_type
55
- base_interval_validation.type
56
- end
57
-
58
47
  # Compute the next time after (or including) the specified time in respect
59
48
  # to the given start time
60
49
  def next_time(time, start_time, closing_time)
@@ -74,12 +63,8 @@ module IceCube
74
63
  start_time
75
64
  end
76
65
 
77
- def skipped_for_dst
78
- @uses -= 1 if @uses > 0
79
- end
80
-
81
- def dst_adjust?
82
- @validations[:interval].any?(&:dst_adjust?)
66
+ def full_required?
67
+ !occurrence_count.nil?
83
68
  end
84
69
 
85
70
  def to_s
@@ -193,6 +178,12 @@ module IceCube
193
178
  VALIDATION_ORDER & @validations.keys
194
179
  end
195
180
 
181
+ def verify_alignment(value, freq, rule_part)
182
+ InputAlignment.new(self, value, rule_part).verify(freq) do |error|
183
+ yield error
184
+ end
185
+ end
186
+
196
187
  end
197
188
 
198
189
  end
@@ -4,14 +4,13 @@ module IceCube
4
4
 
5
5
  # Value reader for limit
6
6
  def occurrence_count
7
- @count
7
+ (arr = @validations[:count]) && (val = arr[0]) && val.count
8
8
  end
9
9
 
10
10
  def count(max)
11
11
  unless max.nil? || max.is_a?(Integer)
12
12
  raise ArgumentError, "Expecting Integer or nil value for count, got #{max.inspect}"
13
13
  end
14
- @count = max
15
14
  replace_validations_for(:count, max && [Validation.new(max, self)])
16
15
  self
17
16
  end
@@ -4,7 +4,11 @@ module IceCube
4
4
 
5
5
  # Add a new interval validation
6
6
  def interval(interval)
7
- @interval = normalized_interval(interval)
7
+ interval = normalized_interval(interval)
8
+ verify_alignment(interval, :wday, :interval) { |error| raise error }
9
+ verify_alignment(interval, :day, :interval) { |error| raise error }
10
+
11
+ @interval = interval
8
12
  replace_validations_for(:interval, [Validation.new(@interval)])
9
13
  clobber_base_validations(:wday, :day)
10
14
  self
@@ -1,5 +1,3 @@
1
- require 'date'
2
-
3
1
  module IceCube
4
2
 
5
3
  module Validations::Day
@@ -12,6 +10,8 @@ module IceCube
12
10
  raise ArgumentError, "expecting Integer or Symbol value for day, got #{day.inspect}"
13
11
  end
14
12
  day = TimeUtil.sym_to_wday(day)
13
+ verify_alignment(day, :wday, :day) { |error| raise error }
14
+
15
15
  validations_for(:day) << Validation.new(day)
16
16
  end
17
17
  clobber_base_validations(:wday, :day)
@@ -27,6 +27,10 @@ module IceCube
27
27
  @day = day
28
28
  end
29
29
 
30
+ def key
31
+ :day
32
+ end
33
+
30
34
  def type
31
35
  :wday
32
36
  end
@@ -7,6 +7,7 @@ module IceCube
7
7
  unless day.is_a?(Integer)
8
8
  raise ArgumentError, "expecting Integer value for day, got #{day.inspect}"
9
9
  end
10
+ verify_alignment(day, :day, :day_of_month) { |error| raise error }
10
11
  validations_for(:day_of_month) << Validation.new(day)
11
12
  end
12
13
  clobber_base_validations(:day, :wday)
@@ -22,6 +23,10 @@ module IceCube
22
23
  @day = day
23
24
  end
24
25
 
26
+ def key
27
+ :day_of_month
28
+ end
29
+
25
30
  def type
26
31
  :day
27
32
  end
@@ -8,12 +8,31 @@ module IceCube
8
8
  unless hour.is_a?(Integer)
9
9
  raise ArgumentError, "expecting Integer value for hour, got #{hour.inspect}"
10
10
  end
11
+
12
+ verify_alignment(hour, :hour, :hour_of_day) { |error| raise error }
13
+
11
14
  validations_for(:hour_of_day) << Validation.new(hour)
12
15
  end
13
16
  clobber_base_validations(:hour)
14
17
  self
15
18
  end
16
19
 
20
+ def realign(opening_time, start_time)
21
+ return super unless validations[:hour_of_day]
22
+ freq = base_interval_validation.interval
23
+
24
+ first_hour = Array(validations[:hour_of_day]).min_by(&:value)
25
+ time = TimeUtil::TimeWrapper.new(start_time, false)
26
+ if freq > 1
27
+ offset = first_hour.validate(opening_time, start_time)
28
+ time.add(:hour, offset - freq)
29
+ else
30
+ time.hour = first_hour.value
31
+ end
32
+
33
+ super opening_time, time.to_time
34
+ end
35
+
17
36
  class Validation < Validations::FixedValue
18
37
 
19
38
  attr_reader :hour
@@ -23,6 +42,10 @@ module IceCube
23
42
  @hour = hour
24
43
  end
25
44
 
45
+ def key
46
+ :hour_of_day
47
+ end
48
+
26
49
  def type
27
50
  :hour
28
51
  end
@@ -3,6 +3,8 @@ module IceCube
3
3
  module Validations::HourlyInterval
4
4
 
5
5
  def interval(interval)
6
+ verify_alignment(interval, :hour, :interval) { |error| raise error }
7
+
6
8
  @interval = normalized_interval(interval)
7
9
  replace_validations_for(:interval, [Validation.new(@interval)])
8
10
  clobber_base_validations(:hour)
@@ -7,12 +7,24 @@ module IceCube
7
7
  unless minute.is_a?(Integer)
8
8
  raise ArgumentError, "expecting Integer value for minute, got #{minute.inspect}"
9
9
  end
10
+
11
+ verify_alignment(minute, :min, :minute_of_hour) { |error| raise error }
12
+
10
13
  validations_for(:minute_of_hour) << Validation.new(minute)
11
14
  end
12
15
  clobber_base_validations(:min)
13
16
  self
14
17
  end
15
18
 
19
+ def realign(opening_time, start_time)
20
+ return super unless validations[:minute_of_hour]
21
+
22
+ first_minute = validations[:minute_of_hour].min_by(&:value)
23
+ time = TimeUtil::TimeWrapper.new(start_time, false)
24
+ time.min = first_minute.value
25
+ super opening_time, time.to_time
26
+ end
27
+
16
28
  class Validation < Validations::FixedValue
17
29
 
18
30
  attr_reader :minute
@@ -22,6 +34,10 @@ module IceCube
22
34
  @minute = minute
23
35
  end
24
36
 
37
+ def key
38
+ :minute_of_hour
39
+ end
40
+
25
41
  def type
26
42
  :min
27
43
  end
@@ -3,6 +3,8 @@ module IceCube
3
3
  module Validations::MinutelyInterval
4
4
 
5
5
  def interval(interval)
6
+ verify_alignment(interval, :min, :interval) { |error| raise error }
7
+
6
8
  @interval = normalized_interval(interval)
7
9
  replace_validations_for(:interval, [Validation.new(@interval)])
8
10
  clobber_base_validations(:min)
@@ -8,6 +8,7 @@ module IceCube
8
8
  raise ArgumentError, "expecting Integer or Symbol value for month, got #{month.inspect}"
9
9
  end
10
10
  month = TimeUtil.sym_to_month(month)
11
+ verify_alignment(month, :month, :month_of_year) { |error| raise error }
11
12
  validations_for(:month_of_year) << Validation.new(month)
12
13
  end
13
14
  clobber_base_validations :month
@@ -23,6 +24,10 @@ module IceCube
23
24
  @month = month
24
25
  end
25
26
 
27
+ def key
28
+ :month_of_year
29
+ end
30
+
26
31
  def type
27
32
  :month
28
33
  end
@@ -3,7 +3,10 @@ module IceCube
3
3
  module Validations::MonthlyInterval
4
4
 
5
5
  def interval(interval)
6
- @interval = normalized_interval(interval)
6
+ interval = normalized_interval(interval)
7
+ verify_alignment(interval, :month, :interval) { |error| raise error }
8
+
9
+ @interval = interval
7
10
  replace_validations_for(:interval, [Validation.new(@interval)])
8
11
  clobber_base_validations(:month)
9
12
  self
@@ -20,6 +20,10 @@ module IceCube
20
20
  @type = type
21
21
  end
22
22
 
23
+ def key
24
+ :base
25
+ end
26
+
23
27
  def dst_adjust?
24
28
  case @type
25
29
  when :sec, :min then false
@@ -4,15 +4,27 @@ module IceCube
4
4
 
5
5
  def second_of_minute(*seconds)
6
6
  seconds.flatten.each do |second|
7
- unless second.is_a?(Integer)
8
- raise ArgumentError, "Expecting Integer value for second, got #{second.inspect}"
9
- end
7
+ unless second.is_a?(Integer)
8
+ raise ArgumentError, "Expecting Integer value for second, got #{second.inspect}"
9
+ end
10
+
11
+ verify_alignment(second, :sec, :second_of_minute) { |error| raise error }
12
+
10
13
  validations_for(:second_of_minute) << Validation.new(second)
11
14
  end
12
15
  clobber_base_validations :sec
13
16
  self
14
17
  end
15
18
 
19
+ def realign(opening_time, start_time)
20
+ return super unless validations[:second_of_minute]
21
+
22
+ first_second = Array(validations[:second_of_minute]).min_by(&:value)
23
+ time = TimeUtil::TimeWrapper.new(start_time, false)
24
+ time.sec = first_second.value
25
+ super opening_time, time.to_time
26
+ end
27
+
16
28
  class Validation < Validations::FixedValue
17
29
 
18
30
  attr_reader :second
@@ -22,6 +34,10 @@ module IceCube
22
34
  @second = second
23
35
  end
24
36
 
37
+ def key
38
+ :second_of_minute
39
+ end
40
+
25
41
  def type
26
42
  :sec
27
43
  end
@@ -3,6 +3,8 @@ module IceCube
3
3
  module Validations::SecondlyInterval
4
4
 
5
5
  def interval(interval)
6
+ verify_alignment(interval, :sec, :interval) { |error| raise error }
7
+
6
8
  @interval = normalized_interval(interval)
7
9
  replace_validations_for(:interval, [Validation.new(@interval)])
8
10
  clobber_base_validations(:sec)
@@ -6,12 +6,11 @@ module IceCube
6
6
 
7
7
  # Value reader for limit
8
8
  def until_time
9
- @until
9
+ (arr = @validations[:until]) && (val = arr[0]) && val.time
10
10
  end
11
11
  deprecated_alias :until_date, :until_time
12
12
 
13
13
  def until(time)
14
- @until = time
15
14
  replace_validations_for(:until, time.nil? ? nil : [Validation.new(time)])
16
15
  self
17
16
  end
@@ -1,5 +1,3 @@
1
- require 'date'
2
-
3
1
  module IceCube
4
2
 
5
3
  module Validations::WeeklyInterval
@@ -1,5 +1,5 @@
1
1
  module IceCube
2
2
 
3
- VERSION = '0.16.2'
3
+ VERSION = '0.16.3'
4
4
 
5
5
  end
@@ -1,5 +1,6 @@
1
1
  require "bundler/setup"
2
2
  require 'ice_cube'
3
+ require 'timeout'
3
4
 
4
5
  begin
5
6
  require 'simplecov'
@@ -19,6 +20,17 @@ WORLD_TIME_ZONES = [
19
20
  'Pacific/Auckland', # +1200 / +1300
20
21
  ]
21
22
 
23
+ # TODO: enable warnings here and update specs to call IceCube objects correctly
24
+ def Object.const_missing(sym)
25
+ case sym
26
+ when :Schedule, :Rule, :Occurrence, :TimeUtil, :ONE_DAY, :ONE_HOUR, :ONE_MINUTE
27
+ # warn "Use IceCube::#{sym}", caller[0]
28
+ IceCube.const_get(sym)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
22
34
  RSpec.configure do |config|
23
35
  # Enable flags like --only-failures and --next-failure
24
36
  config.example_status_persistence_file_path = ".rspec_status"
@@ -29,6 +41,8 @@ RSpec.configure do |config|
29
41
 
30
42
  Dir[File.dirname(__FILE__) + '/support/**/*'].each { |f| require f }
31
43
 
44
+ config.warnings = true
45
+
32
46
  config.include WarningHelpers
33
47
 
34
48
  config.before :each do |example|
@@ -37,15 +51,18 @@ RSpec.configure do |config|
37
51
  end
38
52
  end
39
53
 
40
- config.around :each do |example|
41
- if zone = example.metadata[:system_time_zone]
42
- orig_zone = ENV['TZ']
43
- ENV['TZ'] = zone
44
- example.run
45
- ENV['TZ'] = orig_zone
46
- else
47
- example.run
48
- end
54
+ config.around :each, system_time_zone: true do |example|
55
+ orig_zone = ENV['TZ']
56
+ ENV['TZ'] = example.metadata[:system_time_zone]
57
+ example.run
58
+ ENV['TZ'] = orig_zone
59
+ end
60
+
61
+ config.around :each, locale: true do |example|
62
+ orig_locale = I18n.locale
63
+ I18n.locale = example.metadata[:locale]
64
+ example.run
65
+ I18n.locale = orig_locale
49
66
  end
50
67
 
51
68
  config.around :each, expect_warnings: true do |example|
@@ -53,4 +70,10 @@ RSpec.configure do |config|
53
70
  example.run
54
71
  end
55
72
  end
73
+
74
+ config.around :each do |example|
75
+ Timeout.timeout(example.metadata.fetch(:timeout, 1)) do
76
+ example.run
77
+ end
78
+ end
56
79
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ice_cube
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.2
4
+ version: 0.16.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Crepezzi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-10 00:00:00.000000000 Z
11
+ date: 2018-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -62,6 +62,7 @@ files:
62
62
  - lib/ice_cube/errors/until_exceeded.rb
63
63
  - lib/ice_cube/flexible_hash.rb
64
64
  - lib/ice_cube/i18n.rb
65
+ - lib/ice_cube/input_alignment.rb
65
66
  - lib/ice_cube/null_i18n.rb
66
67
  - lib/ice_cube/occurrence.rb
67
68
  - lib/ice_cube/parsers/hash_parser.rb
@@ -120,8 +121,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
121
  - !ruby/object:Gem::Version
121
122
  version: '0'
122
123
  requirements: []
123
- rubyforge_project: ice-cube
124
- rubygems_version: 2.6.11
124
+ rubyforge_project:
125
+ rubygems_version: 2.6.14
125
126
  signing_key:
126
127
  specification_version: 4
127
128
  summary: Ruby Date Recurrence Library