ice_cube 0.11.3 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec72afe66473b31f58d02177fce834635b8d6f6d
4
- data.tar.gz: f2b199ad1c5cddfbdc4f0b435f7caa594e8e0f3b
3
+ metadata.gz: f8d575d65663825eb9be2a82f9e931537009d485
4
+ data.tar.gz: 2e292c2f229a8a7b7aaaf8f8dcaf009412741745
5
5
  SHA512:
6
- metadata.gz: 21eba9fafa80dd036ca6e24f118b4638c46c68000a1a77758467d8cc1a27b9cb0c287980fe2d23fbaeb0b18f35d7ac9f8c6850bb5ff5a5cd2c019dace00d9f43
7
- data.tar.gz: 2d842dc82d6f195642c2ecfd0bb4198dd6d9dced764da87bba2001309d2d4de4672f581c8e3ddc062223c87e9c32c1cb32aa2874cb4a1e4893ba98f578ce73fc
6
+ metadata.gz: 98e4ffa4ddc22fb26da6eb632203e389361149216ad62193f64c773fe1a68da07dff80ab3f0470cbc1d0dabfc049780b86eb4f270469b2e47539e46016709c1e
7
+ data.tar.gz: 2882dc6f6af47db013a1ee079c2fc06ecf254274b0d39b00f24bc6a8e72021b4935e27463a65ab7c01740180bcab4236bfc15507825b3db910b4bf16f5ab6ce3
data/lib/ice_cube.rb CHANGED
@@ -16,6 +16,9 @@ module IceCube
16
16
  autoload :HashBuilder, 'ice_cube/builders/hash_builder'
17
17
  autoload :StringBuilder, 'ice_cube/builders/string_builder'
18
18
 
19
+ autoload :HashParser, 'ice_cube/parsers/hash_parser'
20
+ autoload :YamlParser, 'ice_cube/parsers/yaml_parser'
21
+
19
22
  autoload :CountExceeded, 'ice_cube/errors/count_exceeded'
20
23
  autoload :UntilExceeded, 'ice_cube/errors/until_exceeded'
21
24
 
@@ -74,4 +77,14 @@ module IceCube
74
77
  def self.to_s_time_format=(format)
75
78
  @to_s_time_format = format
76
79
  end
80
+
81
+ # Retain backwards compatibility for schedules exported from older versions
82
+ # This represents the version number, 11 = 0.11, 1.0 will be 100
83
+ def self.compatibility
84
+ @compatibility ||= 11
85
+ end
86
+
87
+ def self.compatibility=(version)
88
+ @compatibility = version
89
+ end
77
90
  end
@@ -26,5 +26,13 @@ module IceCube
26
26
  end
27
27
  end
28
28
 
29
+ def self.schedule_options(schedule, options)
30
+ if options[:start_date_override]
31
+ warn "IceCube: :start_date_override option deprecated. " \
32
+ "(please use a block { |s| s.start_time = override })"
33
+ schedule.start_time = options[:start_date_override]
34
+ end
35
+ end
36
+
29
37
  end
30
38
  end
@@ -0,0 +1,63 @@
1
+ module IceCube
2
+ class Enumerator < ::Enumerator
3
+
4
+ def initialize(schedule, from_time, to_time)
5
+ @schedule = schedule
6
+ @from_time = TimeUtil.ensure_time(from_time)
7
+ @to_time = TimeUtil.ensure_time(to_time)
8
+ align_start_time
9
+ @cursor = @from_time
10
+ end
11
+
12
+ def each
13
+ while res = self.find_next && @to_time.nil? || res <= @to_time
14
+ yield Occurrence.new(res, res + schedule.duration)
15
+ end
16
+ raise StopIteration
17
+ end
18
+
19
+ def find_next
20
+ loop do
21
+ min_time = recurrence_rules.reduce(nil) do |min_time, rule|
22
+ catch :limit do
23
+ new_time = rule.next_time(time, schedule, min_time || @to_time)
24
+ [min_time, new_time].compact.min
25
+ end
26
+ end
27
+ break nil unless min_time
28
+ @cursor = min_time + 1
29
+ next if exception_time?(min_time)
30
+ break min_time
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def align_start_time
37
+ if @from_time <= schedule.start_time || full_required?
38
+ @from_time = schedule.start_time
39
+ else
40
+ @from_time += @schedule.start_time.subsec - @from_time.subsec rescue 0
41
+ end
42
+ end
43
+
44
+ # Return a boolean indicating if any rule needs to be run from the start of time
45
+ def full_required?
46
+ recurrence_rules.any?(&:full_required?) ||
47
+ exception_rules.any?(&:full_required?)
48
+ end
49
+
50
+ def exception_rules
51
+ schedule.instance_variable_get(:@all_exception_rules)
52
+ end
53
+
54
+ def recurrence_rules
55
+ @recurrence_rules ||= if recurrence_rules.empty?
56
+ [SingleOccurrenceRule.new(schedule.start_time)].concat schedule.instance_variable_get(:@all_recurrence_rules)
57
+ else
58
+ schedule.instance_variable_get(:@all_recurrence_rules)
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -1,7 +1,7 @@
1
1
  module IceCube
2
2
 
3
3
  # An exception for when a count on a Rule is passed
4
- class CountExceeded < Exception
4
+ class CountExceeded < StopIteration
5
5
  end
6
6
 
7
7
  end
@@ -1,7 +1,7 @@
1
1
  module IceCube
2
2
 
3
3
  # An exception for when an until date on a Rule is passed
4
- class UntilExceeded < Exception
4
+ class UntilExceeded < StopIteration
5
5
  end
6
6
 
7
7
  end
@@ -2,21 +2,37 @@ require 'delegate'
2
2
 
3
3
  module IceCube
4
4
 
5
- # A way to symbolize what's necessary on the fly
5
+ # Find keys by symbol or string without symbolizing user input
6
6
  # Due to the serialization format of ice_cube, this limited implementation
7
7
  # is entirely sufficient
8
8
 
9
9
  class FlexibleHash < SimpleDelegator
10
10
 
11
- def initialize(hash)
12
- @underlying = hash
11
+ def [](key)
12
+ key = _match_key(key)
13
+ super
13
14
  end
14
15
 
15
- def [](key)
16
- case key
17
- when String then @underlying[key] || @underlying[key.to_sym]
18
- else @underlying[key] || @underlying[key.to_s]
16
+ def fetch(key)
17
+ key = _match_key(key)
18
+ super
19
+ end
20
+
21
+ def delete(key)
22
+ key = _match_key(key)
23
+ super
24
+ end
25
+
26
+ private
27
+
28
+ def _match_key(key)
29
+ return key if __getobj__.has_key? key
30
+ if Symbol == key.class
31
+ __getobj__.keys.detect { |k| return k if k == key.to_s }
32
+ elsif String == key.class
33
+ __getobj__.keys.detect { |k| return k if k.to_s == key }
19
34
  end
35
+ key
20
36
  end
21
37
 
22
38
  end
@@ -0,0 +1,155 @@
1
+ class IceCube::StringBuilder
2
+
3
+ format :day do |entries|
4
+ case entries = entries.sort
5
+ when [0, 6] then 'on Weekends'
6
+ when [1, 2, 3, 4, 5] then 'on Weekdays'
7
+ else
8
+ "on " << build(:sentence, build(:daynames, entries))
9
+ end
10
+ end
11
+
12
+ format :dayname do |number|
13
+ Date::DAYNAMES[number]
14
+ end
15
+
16
+ format :daynames do |entries|
17
+ entries.map { |wday| build(:dayname, wday) + "s" }
18
+ end
19
+
20
+ format :monthnames do |entries|
21
+ entries.map { |m| Date::MONTHNAMES[m] }
22
+ end
23
+
24
+ format :count do |number|
25
+ times = number == 1 ? "time" : "times"
26
+ "#{number} #{times}"
27
+ end
28
+
29
+ format :day_of_month do |entries|
30
+ numbered = build(:sentence, build(:ordinals, entries))
31
+ days = entries.size == 1 ? "day" : "days"
32
+ "on the #{numbered} #{days} of the month"
33
+ end
34
+
35
+ format :day_of_week do |entries|
36
+ numbered_weekdays = build(:daynames_of_week, entries).join(" and ")
37
+ "on the #{numbered_weekdays}"
38
+ end
39
+
40
+ format :daynames_of_week do |entries|
41
+ entries.map do |occurrence, wday|
42
+ numbered = build(:ordinal, occurrence)
43
+ weekday = build(:dayname, wday)
44
+ "#{numbered} #{weekday}"
45
+ end
46
+ end
47
+
48
+ format :day_of_year do |entries|
49
+ numbered = build(:sentence, build(:ordinals, entries))
50
+ days = entries.size == 1 ? "day" : "days"
51
+ "on the #{numbered} #{days} of the year"
52
+ end
53
+
54
+ format :hour_of_day do |entries|
55
+ numbered = build(:sentence, build(:ordinals, entries))
56
+ hours = entries.size == 1 ? "hour" : "hours"
57
+ "on the #{numbered} #{hours} of the day"
58
+ end
59
+
60
+ format :minute_of_hour do |entries|
61
+ numbered = build(:sentence, build(:ordinals, entries))
62
+ minutes = entries.size == 1 ? "minute" : "minutes"
63
+ "on the #{numbered} #{minutes} of the hour"
64
+ end
65
+
66
+ format :month_of_year do |entries|
67
+ "in " << build(:sentence, build(:monthnames, entries))
68
+ end
69
+
70
+ format :second_of_minute do |entries|
71
+ numbered = build(:sentence, build(:ordinals, entries))
72
+ seconds = entries.size == 1 ? "second" : "seconds"
73
+ "on the #{numbered} #{seconds} of the minute"
74
+ end
75
+
76
+ format :time do |time|
77
+ time.strftime(IceCube.to_s_time_format)
78
+ end
79
+
80
+ format :until do |time|
81
+ "until " << build(:time, time)
82
+ end
83
+
84
+ format :sentence do |entries|
85
+ case entries.length
86
+ when 0 then ''
87
+ when 1 then entries[0].to_s
88
+ when 2 then "#{entries[0]} and #{entries[1]}"
89
+ else "#{entries[0...-1].join(', ')}, and #{entries[-1]}"
90
+ end
91
+ end
92
+
93
+ format :ordinals do |entries|
94
+ entries = entries.sort
95
+ entries.rotate! while entries[0] < 0 if entries.last > 0
96
+ entries.map { |number| build(:ordinal, number) }
97
+ end
98
+
99
+ format :ordinal do |number|
100
+ next "last" if number == -1
101
+ suffix = SPECIAL_SUFFIX[number] || NUMBER_SUFFIX[number.abs % 10]
102
+ if number < -1
103
+ number.abs.to_s << suffix << " to last"
104
+ else
105
+ number.to_s << suffix
106
+ end
107
+ end
108
+
109
+ format :daily_interval do |i|
110
+ i == 1 ? "Daily" : "Every #{i} days"
111
+ end
112
+
113
+ format :hourly_interval do |i|
114
+ i == 1 ? "Hourly" : "Every #{i} hours"
115
+ end
116
+
117
+ format :minutely_interval do |i|
118
+ i == 1 ? "Minutely" : "Every #{i} minutes"
119
+ end
120
+
121
+ format :monthly_interval do |i|
122
+ i == 1 ? "Monthly" : "Every #{i} months"
123
+ end
124
+
125
+ format :secondly_interval do |i|
126
+ i == 1 ? "Secondly" : "Every #{i} seconds"
127
+ end
128
+
129
+ format :weekly_interval do |i|
130
+ i == 1 ? "Weekly" : "Every #{i} weeks"
131
+ end
132
+
133
+ format :yearly_interval do |i|
134
+ i == 1 ? "Yearly" : "Every #{i} years"
135
+ end
136
+
137
+ format :exrule do |rule|
138
+ "not #{rule}"
139
+ end
140
+
141
+ format :extime do |time|
142
+ "not on #{build(:time, time)}"
143
+ end
144
+
145
+ format :schedule do |s|
146
+ times = s.recurrence_times_with_start_time - s.extimes
147
+ pieces = []
148
+ pieces.concat times.uniq.sort.map { |t| build(:time, t) }
149
+ pieces.concat s.rrules.map { |t| t.to_s }
150
+ pieces.concat s.exrules.map { |t| build(:exrule, t) }
151
+ pieces.concat s.extimes.uniq.sort.map { |t| build(:extime, t) }
152
+ pieces.join(' / ')
153
+ end
154
+
155
+ end
@@ -0,0 +1,71 @@
1
+ module IceCube
2
+ class HashInput
3
+
4
+ class Mash
5
+ def initialize(hash)
6
+ @hash = hash
7
+ end
8
+
9
+ # Fetch values indifferently by symbol or string key without symbolizing
10
+ # arbitrary string input
11
+ #
12
+ def [](key)
13
+ @hash.fetch(key) do |key|
14
+ str_key = key.to_s
15
+ @hash.each_pair.detect do |sym_key, value|
16
+ return value if sym_key.to_s == str_key
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def initialize(hash)
23
+ @input = Mash.new(hash)
24
+ end
25
+
26
+ def [](key)
27
+ @input[key]
28
+ end
29
+
30
+ def to_rule
31
+ return nil unless rule_class
32
+ rule = rule_class.new(interval, week_start)
33
+ rule.until(limit_time) if limit_time
34
+ rule.count(limit_count) if limit_count
35
+ validations.each do |validation, args|
36
+ rule.send(validation, *args)
37
+ end
38
+ end
39
+
40
+ def rule_class
41
+ return @rule_class if @rule_class
42
+ match = @input[:rule_type].match(/::(\w+Rule)$/)
43
+ @rule_class = IceCube.const_get(match[1]) if match
44
+ end
45
+
46
+ def interval
47
+ @input[:interval] || 1
48
+ end
49
+
50
+ def week_start
51
+ @input[:week_start] || :sunday
52
+ end
53
+
54
+ def limit_time
55
+ @limit_time ||= TimeUtil.deserialize_time(@input[:until])
56
+ end
57
+
58
+ def limit_count
59
+ @input[:count]
60
+ end
61
+
62
+ def validations
63
+ input_validations = Mash.new(@input[:validations] || {})
64
+ ValidatedRule::VALIDATION_ORDER.each_with_object({}) do |key, output_validations|
65
+ args = input_validations[key]
66
+ output_validations[key] = Array(args) if args
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -93,7 +93,8 @@ module IceCube
93
93
  end
94
94
 
95
95
  def overnight?
96
- midnight = Time.new(start_time.year, start_time.month, start_time.day + 1)
96
+ offset = start_time + 3600 * 24
97
+ midnight = Time.new(offset.year, offset.month, offset.day)
97
98
  midnight < end_time
98
99
  end
99
100
  end
@@ -0,0 +1,87 @@
1
+ module IceCube
2
+ class HashParser
3
+
4
+ attr_reader :hash
5
+
6
+ def initialize(original_hash)
7
+ @hash = original_hash
8
+ end
9
+
10
+ def to_schedule
11
+ data = normalize_keys(hash)
12
+ schedule = IceCube::Schedule.new parse_time(data[:start_time])
13
+ apply_duration schedule, data
14
+ apply_end_time schedule, data
15
+ apply_rrules schedule, data
16
+ apply_exrules schedule, data
17
+ apply_rtimes schedule, data
18
+ apply_extimes schedule, data
19
+ yield schedule if block_given?
20
+ schedule
21
+ end
22
+
23
+ private
24
+
25
+ def normalize_keys(hash)
26
+ data = IceCube::FlexibleHash.new(hash.dup)
27
+
28
+ if (start_date = data.delete(:start_date))
29
+ warn "IceCube: :start_date deprecated. (please use :start_time)"
30
+ data[:start_time] = start_date
31
+ end
32
+
33
+ {:rdates => :rtimes, :exdates => :extimes}.each do |old_key, new_key|
34
+ if (times = data.delete(old_key))
35
+ warn "IceCube: :#{old_key} deprecated. (please use :#{new_key})"
36
+ (data[new_key] ||= []).concat times
37
+ end
38
+ end
39
+
40
+ data
41
+ end
42
+
43
+ def apply_duration(schedule, data)
44
+ return unless data[:duration]
45
+ schedule.duration = data[:duration].to_i
46
+ end
47
+
48
+ def apply_end_time(schedule, data)
49
+ return unless data[:end_time]
50
+ schedule.end_time = parse_time(data[:end_time])
51
+ end
52
+
53
+ def apply_rrules(schedule, data)
54
+ return unless data[:rrules]
55
+ data[:rrules].each do |h|
56
+ schedule.rrule(IceCube::Rule.from_hash(h))
57
+ end
58
+ end
59
+
60
+ def apply_exrules(schedule, data)
61
+ return unless data[:exrules]
62
+ warn "IceCube: :exrules deprecated. (This will be going away)"
63
+ data[:exrules].each do |h|
64
+ schedule.exrule(IceCube::Rule.from_hash(h))
65
+ end
66
+ end
67
+
68
+ def apply_rtimes(schedule, data)
69
+ return unless data[:rtimes]
70
+ data[:rtimes].each do |t|
71
+ schedule.add_recurrence_time TimeUtil.deserialize_time(t)
72
+ end
73
+ end
74
+
75
+ def apply_extimes(schedule, data)
76
+ return unless data[:extimes]
77
+ data[:extimes].each do |t|
78
+ schedule.add_exception_time TimeUtil.deserialize_time(t)
79
+ end
80
+ end
81
+
82
+ def parse_time(time)
83
+ TimeUtil.deserialize_time(time)
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,21 @@
1
+ module IceCube
2
+ class YamlParser < HashParser
3
+
4
+ SERIALIZED_START = /start_(?:time|date): .+(?<tz>(?:-|\+)\d{2}:\d{2})$/
5
+
6
+ attr_reader :hash
7
+
8
+ def initialize(yaml)
9
+ @hash = YAML::load(yaml)
10
+ yaml.match(SERIALIZED_START) do |match|
11
+ start_time = hash[:start_time] || hash[:start_date]
12
+ hash = FlexibleHash.new(@hash)
13
+ TimeUtil.restore_deserialized_offset start_time, match[:tz]
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+
20
+ end
21
+ end
@@ -153,7 +153,7 @@ module IceCube
153
153
  require_terminating_rules
154
154
  enumerate_occurrences(start_time).to_a
155
155
  end
156
-
156
+
157
157
  # Emit an enumerator based on the start time
158
158
  def all_occurrences_enumerator
159
159
  enumerate_occurrences(start_time)
@@ -340,20 +340,22 @@ module IceCube
340
340
 
341
341
  # Load the schedule from yaml
342
342
  def self.from_yaml(yaml, options = {})
343
- hash = YAML::load(yaml)
344
- if match = yaml.match(/start_date: .+((?:-|\+)\d{2}:\d{2})$/)
345
- TimeUtil.restore_deserialized_offset(hash[:start_date], match[1])
343
+ YamlParser.new(yaml).to_schedule do |schedule|
344
+ Deprecated.schedule_options(schedule, options)
345
+ yield schedule if block_given?
346
346
  end
347
- from_hash hash, options
348
347
  end
349
348
 
350
349
  # Convert the schedule to a hash
351
350
  def to_hash
352
351
  data = {}
353
- data[:start_date] = TimeUtil.serialize_time(start_time)
352
+ data[:start_time] = TimeUtil.serialize_time(start_time)
353
+ data[:start_date] = data[:start_time] if IceCube.compatibility <= 11
354
354
  data[:end_time] = TimeUtil.serialize_time(end_time) if end_time
355
355
  data[:rrules] = recurrence_rules.map(&:to_hash)
356
- data[:exrules] = exception_rules.map(&:to_hash)
356
+ if IceCube.compatibility <= 11 && exception_rules.any?
357
+ data[:exrules] = exception_rules.map(&:to_hash)
358
+ end
357
359
  data[:rtimes] = recurrence_times.map do |rt|
358
360
  TimeUtil.serialize_time(rt)
359
361
  end
@@ -365,28 +367,10 @@ module IceCube
365
367
 
366
368
  # Load the schedule from a hash
367
369
  def self.from_hash(original_hash, options = {})
368
- original_hash[:start_date] = options[:start_date_override] if options[:start_date_override]
369
- # And then deserialize
370
- data = IceCube::FlexibleHash.new(original_hash)
371
- schedule = IceCube::Schedule.new TimeUtil.deserialize_time(data[:start_date])
372
- schedule.end_time = schedule.start_time + data[:duration] if data[:duration]
373
- schedule.end_time = TimeUtil.deserialize_time(data[:end_time]) if data[:end_time]
374
- data[:rrules] && data[:rrules].each { |h| schedule.rrule(IceCube::Rule.from_hash(h)) }
375
- data[:exrules] && data[:exrules].each { |h| schedule.exrule(IceCube::Rule.from_hash(h)) }
376
- data[:rtimes] && data[:rtimes].each do |t|
377
- schedule.add_recurrence_time TimeUtil.deserialize_time(t)
378
- end
379
- data[:extimes] && data[:extimes].each do |t|
380
- schedule.add_exception_time TimeUtil.deserialize_time(t)
381
- end
382
- # Also serialize old format for backward compat
383
- data[:rdates] && data[:rdates].each do |t|
384
- schedule.add_recurrence_time TimeUtil.deserialize_time(t)
385
- end
386
- data[:exdates] && data[:exdates].each do |t|
387
- schedule.add_exception_time TimeUtil.deserialize_time(t)
370
+ HashParser.new(original_hash).to_schedule do |schedule|
371
+ Deprecated.schedule_options(schedule, options)
372
+ yield schedule if block_given?
388
373
  end
389
- schedule
390
374
  end
391
375
 
392
376
  # Determine if the schedule will end
@@ -413,25 +397,27 @@ module IceCube
413
397
 
414
398
  # Find all of the occurrences for the schedule between opening_time
415
399
  # and closing_time
400
+ # Iteration is unrolled in pairs to skip duplicate times in end of DST
416
401
  def enumerate_occurrences(opening_time, closing_time = nil, &block)
417
- opening_time = TimeUtil.ensure_time opening_time
418
- closing_time = TimeUtil.ensure_time closing_time
402
+ opening_time = TimeUtil.match_zone(opening_time, start_time)
403
+ closing_time = TimeUtil.match_zone(closing_time, start_time)
419
404
  opening_time += start_time.subsec - opening_time.subsec rescue 0
420
405
  reset
421
406
  opening_time = start_time if opening_time < start_time
422
- # walk up to the opening time - and off we go
423
- # If we have rules with counts, we need to walk from the beginning of time,
424
- # otherwise opening_time
425
- time = full_required? ? start_time : opening_time
407
+ t1 = full_required? ? start_time : opening_time
426
408
  e = Enumerator.new do |yielder|
427
409
  loop do
428
- res = next_time(time, closing_time)
429
- break unless res
430
- break if closing_time && res > closing_time
431
- if res >= opening_time
432
- yielder.yield (block_given? ? block.call(res) : res)
410
+ break unless (t0 = next_time(t1, closing_time))
411
+ break if closing_time && t0 > closing_time
412
+ yielder << (block_given? ? block.call(t0) : t0) if t0 >= opening_time
413
+ break unless (t1 = next_time(t0 + 1, closing_time))
414
+ break if closing_time && t1 > closing_time
415
+ if TimeUtil.same_clock?(t0, t1) && recurrence_rules.any?(&:dst_adjust?)
416
+ wind_back_dst
417
+ next (t1 += 1)
433
418
  end
434
- time = res + 1
419
+ yielder << (block_given? ? block.call(t1) : t1) if t1 >= opening_time
420
+ next (t1 += 1)
435
421
  end
436
422
  end
437
423
  end
@@ -443,17 +429,18 @@ module IceCube
443
429
  begin
444
430
  new_time = rule.next_time(time, self, min_time || closing_time)
445
431
  [min_time, new_time].compact.min
446
- rescue CountExceeded, UntilExceeded
432
+ rescue StopIteration
447
433
  min_time
448
434
  end
449
435
  end
450
436
  break nil unless min_time
451
- next(time = min_time + 1) if exception_time?(min_time)
437
+ next (time = min_time + 1) if exception_time?(min_time)
452
438
  break Occurrence.new(min_time, min_time + duration)
453
439
  end
454
440
  end
455
441
 
456
- # Return a boolean indicating if any rule needs to be run from the start of time
442
+ # Indicate if any rule needs to be run from the start of time
443
+ # If we have rules with counts, we need to walk from the beginning of time
457
444
  def full_required?
458
445
  @all_recurrence_rules.any?(&:full_required?) ||
459
446
  @all_exception_rules.any?(&:full_required?)
@@ -497,6 +484,12 @@ module IceCube
497
484
  end
498
485
  end
499
486
 
487
+ def wind_back_dst
488
+ recurrence_rules.each do |rule|
489
+ rule.skipped_for_dst
490
+ end
491
+ end
492
+
500
493
  end
501
494
 
502
495
  end
@@ -0,0 +1,10 @@
1
+ module IceCube
2
+ module StringHelpers
3
+
4
+ module_function
5
+
6
+ def nice_number(number)
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ module IceCube
2
+ class TimeStep
3
+ SECS = 1
4
+ MINS = 60
5
+ HOURS = MINS * 60
6
+ DAYS = HOURS * 24
7
+ WEEKS = DAYS * 7
8
+ MONTHS = {
9
+ 1 => [ 28, 29, 30, 31].map { |m| m * DAYS },
10
+ 2 => [ 59, 60, 61, 62].map { |m| m * DAYS },
11
+ 3 => [ 89, 90, 91, 92].map { |m| m * DAYS },
12
+ 4 => [120, 121, 122, 123].map { |m| m * DAYS },
13
+ 5 => [150, 151, 152, 153].map { |m| m * DAYS },
14
+ 6 => [181, 182, 183, 184].map { |m| m * DAYS },
15
+ 7 => [212, 213, 214, 215].map { |m| m * DAYS },
16
+ 8 => [242, 243, 244, 245].map { |m| m * DAYS },
17
+ 9 => [273, 274, 275, 276].map { |m| m * DAYS },
18
+ 10 => [303, 304, 305, 306].map { |m| m * DAYS },
19
+ 11 => [334, 335, 336, 337].map { |m| m * DAYS },
20
+ 12 => [365, 366] .map { |m| m * DAYS }
21
+ }
22
+ YEARS = [365, 366]
23
+
24
+ def next(base, validations)
25
+ end
26
+
27
+ def prev(base, validations)
28
+ end
29
+ end
30
+ end
@@ -16,6 +16,8 @@ module IceCube
16
16
  :november => 11, :december => 12
17
17
  }
18
18
 
19
+ CLOCK_VALUES = [:year, :month, :day, :hour, :min, :sec]
20
+
19
21
  # Provides a Time.now without the usec, in the reference zone or utc offset
20
22
  def self.now(reference=Time.now)
21
23
  match_zone(Time.at(Time.now.to_i), reference)
@@ -72,7 +74,8 @@ module IceCube
72
74
  if time_or_hash.is_a?(Time)
73
75
  time_or_hash
74
76
  elsif time_or_hash.is_a?(Hash)
75
- time_or_hash[:time].in_time_zone(time_or_hash[:zone])
77
+ hash = FlexibleHash.new(time_or_hash)
78
+ hash[:time].in_time_zone(hash[:zone])
76
79
  end
77
80
  end
78
81
 
@@ -112,8 +115,8 @@ module IceCube
112
115
 
113
116
  # Convert a symbol to a numeric month
114
117
  def self.sym_to_month(sym)
115
- return wday = sym if (1..12).include? sym
116
118
  MONTHS.fetch(sym) do |k|
119
+ return wday = sym.to_i if MONTHS.values.any? { |i| i.to_s == sym.to_s }
117
120
  raise ArgumentError, "Expecting Fixnum or Symbol value for month. " \
118
121
  "No such month: #{k.inspect}"
119
122
  end
@@ -122,8 +125,8 @@ module IceCube
122
125
 
123
126
  # Convert a symbol to a wday number
124
127
  def self.sym_to_wday(sym)
125
- return sym if (0..6).include? sym
126
128
  DAYS.fetch(sym) do |k|
129
+ return sym.to_i if DAYS.values.any? { |i| i.to_s == sym.to_s }
127
130
  raise ArgumentError, "Expecting Fixnum or Symbol value for weekday. " \
128
131
  "No such weekday: #{k.inspect}"
129
132
  end
@@ -209,6 +212,10 @@ module IceCube
209
212
  end
210
213
  end
211
214
 
215
+ def self.same_clock?(t1, t2)
216
+ CLOCK_VALUES.all? { |i| t1.send(i) == t2.send(i) }
217
+ end
218
+
212
219
  # A utility class for safely moving time around
213
220
  class TimeWrapper
214
221
 
@@ -46,6 +46,14 @@ module IceCube
46
46
  @time
47
47
  end
48
48
 
49
+ def skipped_for_dst
50
+ @uses -= 1 if @uses > 0
51
+ end
52
+
53
+ def dst_adjust?
54
+ @validations[:interval].any? &:dst_adjust?
55
+ end
56
+
49
57
  def to_s
50
58
  builder = StringBuilder.new
51
59
  @validations.each do |name, validations|
@@ -132,12 +140,11 @@ module IceCube
132
140
  end
133
141
 
134
142
  def shift_time_by_validation(res, vals)
135
- return unless res.min
136
- type = vals.first.type # get the jump type
137
- dst_adjust = !vals.first.respond_to?(:dst_adjust?) || vals.first.dst_adjust?
138
- wrapper = TimeUtil::TimeWrapper.new(@time, dst_adjust)
139
- wrapper.add(type, res.min)
140
- wrapper.clear_below(type)
143
+ return unless (interval = res.min)
144
+ validation = vals.first
145
+ wrapper = TimeUtil::TimeWrapper.new(@time, validation.dst_adjust?)
146
+ wrapper.add(validation.type, interval)
147
+ wrapper.clear_below(validation.type)
141
148
 
142
149
  # Move over DST if blocked, no adjustments
143
150
  if wrapper.to_time <= @time
@@ -29,6 +29,10 @@ module IceCube
29
29
  :limit
30
30
  end
31
31
 
32
+ def dst_adjust?
33
+ false
34
+ end
35
+
32
36
  def validate(time, schedule)
33
37
  raise CountExceeded if rule.uses && rule.uses >= count
34
38
  end
@@ -22,6 +22,10 @@ module IceCube
22
22
  :day
23
23
  end
24
24
 
25
+ def dst_adjust?
26
+ true
27
+ end
28
+
25
29
  def validate(step_time, schedule)
26
30
  t0, t1 = schedule.start_time, step_time
27
31
  days = Date.new(t1.year, t1.month, t1.day) -
@@ -31,6 +31,10 @@ module IceCube
31
31
  :wday
32
32
  end
33
33
 
34
+ def dst_adjust?
35
+ true
36
+ end
37
+
34
38
  def build_s(builder)
35
39
  builder.piece(:day) << day
36
40
  end
@@ -28,6 +28,10 @@ module IceCube
28
28
  :day
29
29
  end
30
30
 
31
+ def dst_adjust?
32
+ true
33
+ end
34
+
31
35
  def build_s(builder)
32
36
  builder.piece(:day_of_month) << StringBuilder.nice_number(day)
33
37
  end
@@ -26,6 +26,10 @@ module IceCube
26
26
  :day
27
27
  end
28
28
 
29
+ def dst_adjust?
30
+ true
31
+ end
32
+
29
33
  def validate(step_time, schedule)
30
34
  wday = step_time.wday
31
35
  offset = (day < wday) ? (7 - wday + day) : (day - wday)
@@ -25,6 +25,10 @@ module IceCube
25
25
  :day
26
26
  end
27
27
 
28
+ def dst_adjust?
29
+ true
30
+ end
31
+
28
32
  def validate(step_time, schedule)
29
33
  days_in_year = TimeUtil.days_in_year(step_time)
30
34
  yday = day < 0 ? day + days_in_year : day
@@ -29,6 +29,10 @@ module IceCube
29
29
  :hour
30
30
  end
31
31
 
32
+ def dst_adjust?
33
+ true
34
+ end
35
+
32
36
  def build_s(builder)
33
37
  builder.piece(:hour_of_day) << StringBuilder.nice_number(hour)
34
38
  end
@@ -28,6 +28,10 @@ module IceCube
28
28
  :min
29
29
  end
30
30
 
31
+ def dst_adjust?
32
+ false
33
+ end
34
+
31
35
  def build_s(builder)
32
36
  builder.piece(:minute_of_hour) << StringBuilder.nice_number(minute)
33
37
  end
@@ -29,6 +29,10 @@ module IceCube
29
29
  :month
30
30
  end
31
31
 
32
+ def dst_adjust?
33
+ true
34
+ end
35
+
32
36
  def build_s(builder)
33
37
  builder.piece(:month_of_year) << Date::MONTHNAMES[month]
34
38
  end
@@ -21,6 +21,10 @@ module IceCube
21
21
  :month
22
22
  end
23
23
 
24
+ def dst_adjust?
25
+ true
26
+ end
27
+
24
28
  def validate(step_time, schedule)
25
29
  t0, t1 = schedule.start_time, step_time
26
30
  months = (t1.month - t0.month) +
@@ -28,6 +28,10 @@ module IceCube
28
28
  :sec
29
29
  end
30
30
 
31
+ def dst_adjust?
32
+ false
33
+ end
34
+
31
35
  def build_s(builder)
32
36
  builder.piece(:second_of_minute) << StringBuilder.nice_number(second)
33
37
  end
@@ -29,6 +29,10 @@ module IceCube
29
29
  :limit
30
30
  end
31
31
 
32
+ def dst_adjust?
33
+ false
34
+ end
35
+
32
36
  def validate(step_time, schedule)
33
37
  raise UntilExceeded if step_time > time
34
38
  end
@@ -29,6 +29,10 @@ module IceCube
29
29
  :day
30
30
  end
31
31
 
32
+ def dst_adjust?
33
+ true
34
+ end
35
+
32
36
  def validate(step_time, schedule)
33
37
  t0, t1 = schedule.start_time, step_time
34
38
  d0 = Date.new(t0.year, t0.month, t0.day)
@@ -20,6 +20,10 @@ module IceCube
20
20
  :year
21
21
  end
22
22
 
23
+ def dst_adjust?
24
+ true
25
+ end
26
+
23
27
  def validate(step_time, schedule)
24
28
  years = step_time.year - schedule.start_time.year
25
29
  offset = (years % interval).nonzero?
@@ -1,5 +1,5 @@
1
1
  module IceCube
2
2
 
3
- VERSION = '0.11.3'
3
+ VERSION = '0.12.0'
4
4
 
5
5
  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.11.3
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Crepezzi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-07 00:00:00.000000000 Z
11
+ date: 2014-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -78,10 +78,15 @@ files:
78
78
  - lib/ice_cube/builders/ical_builder.rb
79
79
  - lib/ice_cube/builders/string_builder.rb
80
80
  - lib/ice_cube/deprecated.rb
81
+ - lib/ice_cube/enumerator.rb
81
82
  - lib/ice_cube/errors/count_exceeded.rb
82
83
  - lib/ice_cube/errors/until_exceeded.rb
83
84
  - lib/ice_cube/flexible_hash.rb
85
+ - lib/ice_cube/formatters/string.rb
86
+ - lib/ice_cube/hash_input.rb
84
87
  - lib/ice_cube/occurrence.rb
88
+ - lib/ice_cube/parsers/hash_parser.rb
89
+ - lib/ice_cube/parsers/yaml_parser.rb
85
90
  - lib/ice_cube/rule.rb
86
91
  - lib/ice_cube/rules/daily_rule.rb
87
92
  - lib/ice_cube/rules/hourly_rule.rb
@@ -92,6 +97,8 @@ files:
92
97
  - lib/ice_cube/rules/yearly_rule.rb
93
98
  - lib/ice_cube/schedule.rb
94
99
  - lib/ice_cube/single_occurrence_rule.rb
100
+ - lib/ice_cube/string_helpers.rb
101
+ - lib/ice_cube/time_step.rb
95
102
  - lib/ice_cube/time_util.rb
96
103
  - lib/ice_cube/validated_rule.rb
97
104
  - lib/ice_cube/validations/count.rb
@@ -135,9 +142,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
142
  version: '0'
136
143
  requirements: []
137
144
  rubyforge_project: ice-cube
138
- rubygems_version: 2.2.0
145
+ rubygems_version: 2.2.2
139
146
  signing_key:
140
147
  specification_version: 4
141
148
  summary: Ruby Date Recurrence Library
142
149
  test_files:
143
150
  - spec/spec_helper.rb
151
+ has_rdoc: true