ice_cube 0.16.2 → 0.16.3

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