uv-rays 2.0.4 → 2.1.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: fb2605b3097dbf2b0ef3c8c17df39580ce036185
4
- data.tar.gz: 6983fdeea7d991cda8740e3076a98fc07d479630
3
+ metadata.gz: 90bfee69384f25d0c1ce287d91028996305ee511
4
+ data.tar.gz: a5d3f82c72d591fb56f54b0d92eade0768d042bb
5
5
  SHA512:
6
- metadata.gz: 40556df7599e68cdbed13ffe47bddf74c5fee91fdb296b8f5468d6f881e44b0e5db7490b5a6f150ab90f2025beb2ccc1c754df281a2c58d5af54ecb09077f75b
7
- data.tar.gz: 990adf1a8c5282b80ab3c93a19ea8785519e436add68136a68b8891bfe0e6e9cd7676bfe243df9429b47711bb44109c37ed4f8bbcb17bcb849a5ef02f2065dc7
6
+ metadata.gz: 6ce208199ef4a990628aa1d82b1b2f64186fe56aedb46c84991edba1b773cc95dce94086b12fd7ef058a432daf3b127ddec6b56361cbd3649b06af5f262e3717
7
+ data.tar.gz: 9851c7e265d84351307d60c29ba1e86f1dae9e0b36f4c44bc655e7afc15af74774f0f42b73decba188e9520f4fac4cbc6d15b69ceec00cd11ce575f5b9095a12
data/lib/uv-rays.rb CHANGED
@@ -7,7 +7,6 @@ require 'libuv'
7
7
  require 'set' # ruby std lib
8
8
  require 'bisect' # insert into a sorted array
9
9
  require 'tzinfo' # timezone information
10
- require 'uv-rays/scheduler/cron'
11
10
  require 'uv-rays/scheduler/time'
12
11
  require 'uv-rays/scheduler'
13
12
 
@@ -32,15 +32,23 @@ module UV
32
32
 
33
33
  # Provide relevant inspect information
34
34
  def inspect
35
- insp = String.new("#<#{self.class}:0x#{self.__id__.to_s(16)} ")
35
+ insp = String.new("#<#{self.class}:#{"0x00%x" % (self.__id__ << 1)} ")
36
36
  insp << "trigger_count=#{@trigger_count} "
37
37
  insp << "config=#{info} " if self.respond_to?(:info, true)
38
- insp << "next_scheduled=#{@next_scheduled} "
39
- insp << "last_scheduled=#{@last_scheduled} created=#{@created}>"
38
+ insp << "next_scheduled=#{to_time(@next_scheduled)} "
39
+ insp << "last_scheduled=#{to_time(@last_scheduled)} created=#{to_time(@created)}>"
40
40
  insp
41
41
  end
42
42
  alias_method :to_s, :inspect
43
43
 
44
+ def to_time(internal_time)
45
+ if internal_time
46
+ ((internal_time + @scheduler.time_diff) / 1000).to_i
47
+ else
48
+ internal_time
49
+ end
50
+ end
51
+
44
52
 
45
53
  # required for comparable
46
54
  def <=>(anOther)
@@ -100,8 +108,8 @@ module UV
100
108
  # Update the time period of the repeating event
101
109
  #
102
110
  # @param schedule [String] a standard CRON job line or a human readable string representing a time period.
103
- def update(every)
104
- time = Scheduler.parse_in(every, :quiet) || Scheduler.parse_cron(every, :quiet)
111
+ def update(every, timezone: nil)
112
+ time = Scheduler.parse_in(every, :quiet) || Scheduler.parse_cron(every, :quiet, timezone: timezone)
105
113
  raise ArgumentError.new("couldn't parse \"#{o}\"") if time.nil?
106
114
 
107
115
  @every = time
@@ -142,7 +150,7 @@ module UV
142
150
  @next_scheduled = @last_scheduled + @every
143
151
  else
144
152
  # must be a cron
145
- @next_scheduled = (@every.next_time.to_f * 1000).to_i - @scheduler.time_diff
153
+ @next_scheduled = (@every.next.to_f * 1000).to_i - @scheduler.time_diff
146
154
  end
147
155
  end
148
156
 
@@ -154,7 +162,7 @@ module UV
154
162
  end
155
163
 
156
164
  def info
157
- "repeat:#{@every}"
165
+ "repeat:#{@every.inspect}"
158
166
  end
159
167
  end
160
168
 
@@ -176,13 +184,19 @@ module UV
176
184
  # Not really required when used correctly
177
185
  @critical = Mutex.new
178
186
 
179
- # as the libuv time is taken from an arbitrary point in time we
180
- # need to roughly synchronize between it and ruby's Time.now
187
+ # Every hour we should re-calibrate this (just in case)
188
+ calibrate_time
189
+ every(3600000) { calibrate_time }
190
+ end
191
+
192
+
193
+ # As the libuv time is taken from an arbitrary point in time we
194
+ # need to roughly synchronize between it and ruby's Time.now
195
+ def calibrate_time
181
196
  @reactor.update_time
182
197
  @time_diff = (Time.now.to_f * 1000).to_i - @reactor.now
183
198
  end
184
199
 
185
-
186
200
  # Create a repeating event that occurs each time period
187
201
  #
188
202
  # @param time [String] a human readable string representing the time period. 3w2d4h1m2s for example.
@@ -239,9 +253,9 @@ module UV
239
253
  # @param schedule [String] a standard CRON job line.
240
254
  # @param callback [Proc] a block or method to execute when the event triggers
241
255
  # @return [::UV::Repeat]
242
- def cron(schedule, callback = nil, &block)
256
+ def cron(schedule, callback = nil, timezone: nil , &block)
243
257
  callback ||= block
244
- ms = Scheduler.parse_cron(schedule)
258
+ ms = Scheduler.parse_cron(schedule, timezone: timezone)
245
259
  event = Repeat.new(self, ms)
246
260
 
247
261
  if callback.respond_to? :call
@@ -24,10 +24,36 @@
24
24
  # Made in Japan.
25
25
  #++
26
26
 
27
+ require 'parse-cron'
28
+
27
29
 
28
30
  module UV
29
31
  class Scheduler
30
32
 
33
+ # Thread safe timezone time class for use with parse-cron
34
+ class TimeInZone
35
+ def initialize(timezone)
36
+ @timezone = timezone
37
+ end
38
+
39
+ def now
40
+ time = nil
41
+ Time.use_zone(@timezone) do
42
+ time = Time.now
43
+ end
44
+ time
45
+ end
46
+
47
+ def local(*args)
48
+ result = nil
49
+ Time.use_zone(@timezone) do
50
+ result = Time.local(*args)
51
+ end
52
+ result
53
+ end
54
+ end
55
+
56
+
31
57
  def self.parse_in(o, quiet = false)
32
58
  # if o is an integer we are looking at ms
33
59
  o.is_a?(String) ? parse_duration(o, quiet) : o
@@ -60,8 +86,13 @@ module UV
60
86
  raise se
61
87
  end
62
88
 
63
- def self.parse_cron(o, quiet = false)
64
- CronLine.new(o)
89
+ def self.parse_cron(o, quiet = false, timezone: nil)
90
+ if timezone
91
+ tz = TimeInZone.new(timezone)
92
+ CronParser.new(o, tz)
93
+ else
94
+ CronParser.new(o)
95
+ end
65
96
 
66
97
  rescue ArgumentError => ae
67
98
  return nil if quiet
@@ -81,13 +112,13 @@ module UV
81
112
  end
82
113
 
83
114
  DURATIONS2M = [
84
- [ 'y', 365 * 24 * 3600 * 1000 ],
85
- [ 'M', 30 * 24 * 3600 * 1000 ],
86
- [ 'w', 7 * 24 * 3600 * 1000 ],
87
- [ 'd', 24 * 3600 * 1000 ],
88
- [ 'h', 3600 * 1000 ],
89
- [ 'm', 60 * 1000 ],
90
- [ 's', 1000 ]
115
+ [ 'y', 365 * 24 * 3600 * 1000 ],
116
+ [ 'M', 30 * 24 * 3600 * 1000 ],
117
+ [ 'w', 7 * 24 * 3600 * 1000 ],
118
+ [ 'd', 24 * 3600 * 1000 ],
119
+ [ 'h', 3600 * 1000 ],
120
+ [ 'm', 60 * 1000 ],
121
+ [ 's', 1000 ]
91
122
  ]
92
123
  DURATIONS2 = DURATIONS2M.dup
93
124
  DURATIONS2.delete_at(1)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UV
4
- VERSION = '2.0.4'
4
+ VERSION = '2.1.0'
5
5
  end
data/spec/zen_spec.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'libuv'
2
+ require 'set'
3
+
4
+
5
+ describe Libuv::Listener do
6
+ it "should ensure there are no remaining object references in callbacks", network: true do
7
+ require 'objspace'
8
+
9
+ checked = Set.new
10
+
11
+ # These are created by loop objects and are never cleaned up
12
+ # This is OK as the loops are expected to execute for the life of the application
13
+ except = []
14
+
15
+ ObjectSpace.each_object(Class) do |cls|
16
+ next unless cls.ancestors.include? ::UV::Connection
17
+ next if checked.include? cls
18
+ checked << cls
19
+ end
20
+
21
+ if checked.length > 0
22
+ puts "\nMemory Leak in #{checked.inspect}"
23
+ end
24
+
25
+ expect(checked.length).to be(0)
26
+ end
27
+ end
data/uv-rays.gemspec CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.add_runtime_dependency 'tzinfo', '~> 1.2' # Ruby timezones info
19
19
  gem.add_runtime_dependency 'cookiejar', '~> 0.3' # HTTP cookies
20
20
  gem.add_runtime_dependency 'ipaddress', '~> 0.8' # IP address validation
21
+ gem.add_runtime_dependency 'parse-cron', '~> 0.1' # CRON calculations
21
22
  gem.add_runtime_dependency 'addressable', '~> 2.4' # URI parser
22
23
  gem.add_runtime_dependency 'http-parser', '~> 1.2' # HTTP tokeniser
23
24
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uv-rays
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen von Takach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-15 00:00:00.000000000 Z
11
+ date: 2017-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: libuv
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: parse-cron
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.1'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.1'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: addressable
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -199,7 +213,6 @@ files:
199
213
  - lib/uv-rays/http/request.rb
200
214
  - lib/uv-rays/http_endpoint.rb
201
215
  - lib/uv-rays/scheduler.rb
202
- - lib/uv-rays/scheduler/cron.rb
203
216
  - lib/uv-rays/scheduler/time.rb
204
217
  - lib/uv-rays/tcp_server.rb
205
218
  - lib/uv-rays/version.rb
@@ -207,9 +220,9 @@ files:
207
220
  - spec/buffered_tokenizer_spec.rb
208
221
  - spec/connection_spec.rb
209
222
  - spec/http_endpoint_spec.rb
210
- - spec/scheduler_cron_spec.rb
211
223
  - spec/scheduler_spec.rb
212
224
  - spec/scheduler_time_spec.rb
225
+ - spec/zen_spec.rb
213
226
  - uv-rays.gemspec
214
227
  homepage: https://github.com/cotag/uv-rays
215
228
  licenses:
@@ -240,6 +253,6 @@ test_files:
240
253
  - spec/buffered_tokenizer_spec.rb
241
254
  - spec/connection_spec.rb
242
255
  - spec/http_endpoint_spec.rb
243
- - spec/scheduler_cron_spec.rb
244
256
  - spec/scheduler_spec.rb
245
257
  - spec/scheduler_time_spec.rb
258
+ - spec/zen_spec.rb
@@ -1,389 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # Copyright (c) 2006-2013, John Mettraux, jmettraux@gmail.com
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in
14
- # all copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- # THE SOFTWARE.
23
- #
24
- # Made in Japan.
25
- #++
26
-
27
-
28
- module UV
29
- class Scheduler
30
- #
31
- # A 'cron line' is a line in the sense of a crontab
32
- # (man 5 crontab) file line.
33
- #
34
- class CronLine
35
-
36
- # The string used for creating this cronline instance.
37
- #
38
- attr_reader :original
39
-
40
- attr_reader :seconds
41
- attr_reader :minutes
42
- attr_reader :hours
43
- attr_reader :days
44
- attr_reader :months
45
- attr_reader :weekdays
46
- attr_reader :monthdays
47
- attr_reader :timezone
48
-
49
- def initialize(line)
50
-
51
- raise ArgumentError.new(
52
- "not a string: #{line.inspect}"
53
- ) unless line.is_a?(String)
54
-
55
- @original = line
56
-
57
- items = line.split
58
-
59
- @timezone = (TZInfo::Timezone.get(items.last) rescue nil)
60
- items.pop if @timezone
61
-
62
- raise ArgumentError.new(
63
- "not a valid cronline : '#{line}'"
64
- ) unless items.length == 5 or items.length == 6
65
-
66
- offset = items.length - 5
67
-
68
- @seconds = offset == 1 ? parse_item(items[0], 0, 59) : [ 0 ]
69
- @minutes = parse_item(items[0 + offset], 0, 59)
70
- @hours = parse_item(items[1 + offset], 0, 24)
71
- @days = parse_item(items[2 + offset], 1, 31)
72
- @months = parse_item(items[3 + offset], 1, 12)
73
- @weekdays, @monthdays = parse_weekdays(items[4 + offset])
74
-
75
- [ @seconds, @minutes, @hours, @months ].each do |es|
76
-
77
- raise ArgumentError.new(
78
- "invalid cronline: '#{line}'"
79
- ) if es && es.find { |e| ! e.is_a?(Integer) }
80
- end
81
- end
82
-
83
- # Returns true if the given time matches this cron line.
84
- #
85
- def matches?(time)
86
-
87
- time = Time.at(time) unless time.kind_of?(Time)
88
-
89
- time = @timezone.utc_to_local(time.getutc) if @timezone
90
-
91
- return false unless sub_match?(time, :sec, @seconds)
92
- return false unless sub_match?(time, :min, @minutes)
93
- return false unless sub_match?(time, :hour, @hours)
94
- return false unless date_match?(time)
95
- true
96
- end
97
-
98
- # Returns the next time that this cron line is supposed to 'fire'
99
- #
100
- # This is raw, 3 secs to iterate over 1 year on my macbook :( brutal.
101
- # (Well, I was wrong, takes 0.001 sec on 1.8.7 and 1.9.1)
102
- #
103
- # This method accepts an optional Time parameter. It's the starting point
104
- # for the 'search'. By default, it's Time.now
105
- #
106
- # Note that the time instance returned will be in the same time zone that
107
- # the given start point Time (thus a result in the local time zone will
108
- # be passed if no start time is specified (search start time set to
109
- # Time.now))
110
- #
111
- # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
112
- # Time.mktime(2008, 10, 24, 7, 29))
113
- # #=> Fri Oct 24 07:30:00 -0500 2008
114
- #
115
- # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
116
- # Time.utc(2008, 10, 24, 7, 29))
117
- # #=> Fri Oct 24 07:30:00 UTC 2008
118
- #
119
- # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
120
- # Time.utc(2008, 10, 24, 7, 29)).localtime
121
- # #=> Fri Oct 24 02:30:00 -0500 2008
122
- #
123
- # (Thanks to K Liu for the note and the examples)
124
- #
125
- def next_time(from=Time.now)
126
-
127
- time = @timezone ? @timezone.utc_to_local(from.getutc) : from
128
-
129
- time = time.round
130
- # chop off subseconds
131
-
132
- time = time + 1
133
- # start at the next second
134
-
135
- loop do
136
-
137
- unless date_match?(time)
138
- time += (24 - time.hour) * 3600 - time.min * 60 - time.sec; next
139
- end
140
- unless sub_match?(time, :hour, @hours)
141
- time += (60 - time.min) * 60 - time.sec; next
142
- end
143
- unless sub_match?(time, :min, @minutes)
144
- time += 60 - time.sec; next
145
- end
146
- unless sub_match?(time, :sec, @seconds)
147
- time += 1; next
148
- end
149
-
150
- break
151
- end
152
-
153
- if @timezone
154
- time = @timezone.local_to_utc(time)
155
- time = time.getlocal unless from.utc?
156
- end
157
-
158
- time
159
- end
160
-
161
- # Returns the previous the cronline matched. It's like next_time, but
162
- # for the past.
163
- #
164
- def previous_time(from=Time.now)
165
-
166
- # looks back by slices of two hours,
167
- #
168
- # finds for '* * * * sun', '* * 13 * *' and '0 12 13 * *'
169
- # starting 1970, 1, 1 in 1.8 to 2 seconds (says Rspec)
170
-
171
- start = current = from - 2 * 3600
172
- result = nil
173
-
174
- loop do
175
- nex = next_time(current)
176
- return (result ? result : previous_time(start)) if nex > from
177
- result = current = nex
178
- end
179
-
180
- # never reached
181
- end
182
-
183
- # Returns an array of 6 arrays (seconds, minutes, hours, days,
184
- # months, weekdays).
185
- # This method is used by the cronline unit tests.
186
- #
187
- def to_array
188
-
189
- [
190
- @seconds,
191
- @minutes,
192
- @hours,
193
- @days,
194
- @months,
195
- @weekdays,
196
- @monthdays,
197
- @timezone ? @timezone.name : nil
198
- ]
199
- end
200
-
201
- # Returns the shortest delta between two potential occurrences of the
202
- # schedule described by this cronline.
203
- #
204
- def frequency
205
-
206
- delta = 366 * DAY_S
207
-
208
- t0 = previous_time(Time.local(2000, 1, 1))
209
-
210
- loop do
211
-
212
- break if delta <= 1
213
- break if delta <= 60 && @seconds && @seconds.size == 1
214
-
215
- t1 = next_time(t0)
216
- d = t1 - t0
217
- delta = d if d < delta
218
-
219
- break if @months == nil && t1.month == 2
220
- break if t1.year == 2001
221
-
222
- t0 = t1
223
- end
224
-
225
- delta
226
- end
227
-
228
- protected
229
-
230
- WEEKDAYS = %w[ sun mon tue wed thu fri sat ]
231
- DAY_S = 24 * 3600
232
- WEEK_S = 7 * DAY_S
233
-
234
- def parse_weekdays(item)
235
-
236
- return nil if item == '*'
237
-
238
- items = item.downcase.split(',')
239
-
240
- weekdays = nil
241
- monthdays = nil
242
-
243
- items.each do |it|
244
-
245
- if m = it.match(/^(.+)#(l|-?[12345])$/)
246
-
247
- raise ArgumentError.new(
248
- "ranges are not supported for monthdays (#{it})"
249
- ) if m[1].index('-')
250
-
251
- expr = it.gsub(/#l/, '#-1')
252
-
253
- (monthdays ||= []) << expr
254
-
255
- else
256
-
257
- expr = it.dup
258
- WEEKDAYS.each_with_index { |a, i| expr.gsub!(/#{a}/, i.to_s) }
259
-
260
- raise ArgumentError.new(
261
- "invalid weekday expression (#{it})"
262
- ) if expr !~ /^0*[0-7](-0*[0-7])?$/
263
-
264
- its = expr.index('-') ? parse_range(expr, 0, 7) : [ Integer(expr) ]
265
- its = its.collect { |i| i == 7 ? 0 : i }
266
-
267
- (weekdays ||= []).concat(its)
268
- end
269
- end
270
-
271
- weekdays = weekdays.uniq.sort if weekdays
272
-
273
- [ weekdays, monthdays ]
274
- end
275
-
276
- def parse_item(item, min, max)
277
-
278
- return nil if item == '*'
279
-
280
- r = item.split(',').map { |i| parse_range(i.strip, min, max) }.flatten
281
-
282
- raise ArgumentError.new(
283
- "found duplicates in #{item.inspect}"
284
- ) if r.uniq.size < r.size
285
-
286
- r = r.sort_by { |e| e.is_a?(String) ? 61 : e.to_i }
287
- r
288
- end
289
-
290
- RANGE_REGEX = /^(\*|\d{1,2})(?:-(\d{1,2}))?(?:\/(\d{1,2}))?$/
291
-
292
- def parse_range(item, min, max)
293
-
294
- return %w[ L ] if item == 'L'
295
-
296
- item = '*' + item if item.match(/^\//)
297
-
298
- m = item.match(RANGE_REGEX)
299
-
300
- raise ArgumentError.new(
301
- "cannot parse #{item.inspect}"
302
- ) unless m
303
-
304
- sta = m[1]
305
- sta = sta == '*' ? min : sta.to_i
306
-
307
- edn = m[2]
308
- edn = edn ? edn.to_i : sta
309
- edn = max if m[1] == '*'
310
-
311
- inc = m[3]
312
- inc = inc ? inc.to_i : 1
313
-
314
- raise ArgumentError.new(
315
- "#{item.inspect} is not in range #{min}..#{max}"
316
- ) if sta < min || edn > max
317
-
318
- r = []
319
- val = sta
320
-
321
- loop do
322
- v = val
323
- v = 0 if max == 24 && v == 24
324
- r << v
325
- break if inc == 1 && val == edn
326
- val += inc
327
- break if inc > 1 && val > edn
328
- val = min if val > max
329
- end
330
-
331
- r.uniq
332
- end
333
-
334
- def sub_match?(time, accessor, values)
335
-
336
- value = time.send(accessor)
337
-
338
- return true if values.nil?
339
- return true if values.include?('L') && (time + DAY_S).day == 1
340
-
341
- return true if value == 0 && accessor == :hour && values.include?(24)
342
-
343
- values.include?(value)
344
- end
345
-
346
- def monthday_match?(date, values)
347
-
348
- return true if values.nil?
349
-
350
- today_values = monthdays(date)
351
-
352
- (today_values & values).any?
353
- end
354
-
355
- def date_match?(date)
356
-
357
- return false unless sub_match?(date, :day, @days)
358
- return false unless sub_match?(date, :month, @months)
359
- return false unless sub_match?(date, :wday, @weekdays)
360
- return false unless monthday_match?(date, @monthdays)
361
- true
362
- end
363
-
364
- def monthdays(date)
365
-
366
- pos = 1
367
- d = date.dup
368
-
369
- loop do
370
- d = d - WEEK_S
371
- break if d.month != date.month
372
- pos = pos + 1
373
- end
374
-
375
- neg = -1
376
- d = date.dup
377
-
378
- loop do
379
- d = d + WEEK_S
380
- break if d.month != date.month
381
- neg = neg - 1
382
- end
383
-
384
- [ "#{WEEKDAYS[date.wday]}##{pos}", "#{WEEKDAYS[date.wday]}##{neg}" ]
385
- end
386
- end
387
- end
388
- end
389
-
@@ -1,440 +0,0 @@
1
- require 'uv-rays'
2
-
3
- describe UV::Scheduler::CronLine do
4
-
5
- def cl(cronline_string)
6
- UV::Scheduler::CronLine.new(cronline_string)
7
- end
8
-
9
- def match(line, time)
10
- expect(cl(line).matches?(time)).to eq(true)
11
- end
12
- def no_match(line, time)
13
- expect(cl(line).matches?(time)).to eq(false)
14
- end
15
- def to_a(line, array)
16
- expect(cl(line).to_array).to eq(array)
17
- end
18
- def local(*args)
19
- Time.local(*args)
20
- end
21
- alias lo local
22
-
23
- def utc(*args)
24
- Time.utc(*args)
25
- end
26
-
27
- describe '.new' do
28
-
29
- it 'interprets cron strings correctly' do
30
-
31
- to_a '* * * * *', [ [0], nil, nil, nil, nil, nil, nil, nil ]
32
- to_a '10-12 * * * *', [ [0], [10, 11, 12], nil, nil, nil, nil, nil, nil ]
33
- to_a '* * * * sun,mon', [ [0], nil, nil, nil, nil, [0, 1], nil, nil ]
34
- to_a '* * * * mon-wed', [ [0], nil, nil, nil, nil, [1, 2, 3], nil, nil ]
35
- to_a '* * * * 7', [ [0], nil, nil, nil, nil, [0], nil, nil ]
36
- to_a '* * * * 0', [ [0], nil, nil, nil, nil, [0], nil, nil ]
37
- to_a '* * * * 0,1', [ [0], nil, nil, nil, nil, [0,1], nil, nil ]
38
- to_a '* * * * 7,1', [ [0], nil, nil, nil, nil, [0,1], nil, nil ]
39
- to_a '* * * * 7,0', [ [0], nil, nil, nil, nil, [0], nil, nil ]
40
- to_a '* * * * sun,2-4', [ [0], nil, nil, nil, nil, [0, 2, 3, 4], nil, nil ]
41
-
42
- to_a '* * * * sun,mon-tue', [ [0], nil, nil, nil, nil, [0, 1, 2], nil, nil ]
43
- to_a '0 0 * * mon#1,tue', [[0], [0], [0], nil, nil, [2], ["mon#1"], nil]
44
-
45
- to_a '* * * * * *', [ nil, nil, nil, nil, nil, nil, nil, nil ]
46
- to_a '1 * * * * *', [ [1], nil, nil, nil, nil, nil, nil, nil ]
47
- to_a '7 10-12 * * * *', [ [7], [10, 11, 12], nil, nil, nil, nil, nil, nil ]
48
- to_a '1-5 * * * * *', [ [1,2,3,4,5], nil, nil, nil, nil, nil, nil, nil ]
49
-
50
- to_a '0 0 1 1 *', [ [0], [0], [0], [1], [1], nil, nil, nil ]
51
-
52
- to_a '52 0 * * *', [ [0], [52], [0], nil, nil, nil, nil, nil ]
53
-
54
- to_a '0 16 * * *', [ [0], [0], [16], nil, nil, nil, nil, nil ]
55
-
56
- to_a '0 23-24 * * *', [ [0], [0], [0, 23], nil, nil, nil, nil, nil ]
57
-
58
- to_a '0 23-2 * * *', [ [0], [0], [0, 1, 2, 23], nil, nil, nil, nil, nil ]
59
-
60
- # modulo forms work for five-field forms
61
- to_a '*/17 * * * *', [[0], [0, 17, 34, 51], nil, nil, nil, nil, nil, nil]
62
- to_a '13 */17 * * *', [[0], [13], [0, 17], nil, nil, nil, nil, nil]
63
-
64
- # modulo forms work for six-field forms
65
- to_a '*/17 * * * * *', [[0, 17, 34, 51], nil, nil, nil, nil, nil, nil, nil]
66
- to_a '13 */17 * * * *', [[13], [0, 17, 34, 51], nil, nil, nil, nil, nil, nil]
67
-
68
- end
69
-
70
- it 'rejects invalid weekday expressions' do
71
-
72
- expect { cl '0 17 * * MON_FRI' }.to raise_error(ArgumentError)
73
- # underline instead of dash
74
-
75
- expect { cl '* * * * 9' }.to raise_error(ArgumentError)
76
- expect { cl '* * * * 0-12' }.to raise_error(ArgumentError)
77
- expect { cl '* * * * BLABLA' }.to raise_error(ArgumentError)
78
- end
79
-
80
- it 'rejects invalid cronlines' do
81
-
82
- expect { cl '* nada * * 9' }.to raise_error(ArgumentError)
83
- end
84
-
85
- it 'interprets cron strings with TZ correctly' do
86
-
87
- to_a('* * * * * EST', [ [0], nil, nil, nil, nil, nil, nil, 'EST' ])
88
- to_a('* * * * * * EST', [ nil, nil, nil, nil, nil, nil, nil, 'EST' ])
89
-
90
- to_a(
91
- '* * * * * * America/Chicago',
92
- [ nil, nil, nil, nil, nil, nil, nil, 'America/Chicago' ])
93
- to_a(
94
- '* * * * * * America/New_York',
95
- [ nil, nil, nil, nil, nil, nil, nil, 'America/New_York' ])
96
-
97
- expect { cl '* * * * * NotATimeZone' }.to raise_error(ArgumentError)
98
- expect { cl '* * * * * * NotATimeZone' }.to raise_error(ArgumentError)
99
- end
100
-
101
- it 'interprets cron strings with / (slashes) correctly' do
102
-
103
- to_a(
104
- '0 */2 * * *',
105
- [ [0], [0], (0..11).collect { |e| e * 2 }, nil, nil, nil, nil, nil ])
106
- to_a(
107
- '0 7-23/2 * * *',
108
- [ [0], [0], (7..23).select { |e| e.odd? }, nil, nil, nil, nil, nil ])
109
- to_a(
110
- '*/10 * * * *',
111
- [ [0], [0, 10, 20, 30, 40, 50], nil, nil, nil, nil, nil, nil ])
112
-
113
- # fighting https://github.com/jmettraux/rufus-scheduler/issues/65
114
- #
115
- to_a(
116
- '*/10 * * * * Europe/Berlin',
117
- [ [0], [ 0, 10, 20, 30, 40, 50], nil, nil, nil, nil, nil, 'Europe/Berlin' ])
118
- end
119
-
120
- it 'accepts lonely / (slashes) (like <= 2.0.19 did)' do
121
-
122
- # fighting https://github.com/jmettraux/rufus-scheduler/issues/65
123
-
124
- to_a(
125
- '/10 * * * *',
126
- [ [0], [ 0, 10, 20, 30, 40, 50], nil, nil, nil, nil, nil, nil ])
127
- end
128
-
129
- it 'does not support ranges for monthdays (sun#1-sun#2)' do
130
-
131
- expect {
132
- UV::Scheduler::CronLine.new('* * * * sun#1-sun#2')
133
- }.to raise_error(ArgumentError)
134
- end
135
-
136
- it 'accepts items with initial 0' do
137
-
138
- to_a '09 * * * *', [ [0], [9], nil, nil, nil, nil, nil, nil ]
139
- to_a '09-12 * * * *', [ [0], [9, 10, 11, 12], nil, nil, nil, nil, nil, nil ]
140
- to_a '07-08 * * * *', [ [0], [7, 8], nil, nil, nil, nil, nil, nil ]
141
- to_a '* */08 * * *', [ [0], nil, [0, 8, 16], nil, nil, nil, nil, nil ]
142
- to_a '* */07 * * *', [ [0], nil, [0, 7, 14, 21], nil, nil, nil, nil, nil ]
143
- to_a '* 01-09/04 * * *', [ [0], nil, [1, 5, 9], nil, nil, nil, nil, nil ]
144
- to_a '* * * * 06', [ [0], nil, nil, nil, nil, [6], nil, nil ]
145
- end
146
-
147
- it 'interprets cron strings with L correctly' do
148
-
149
- to_a '* * L * *', [[0], nil, nil, ['L'], nil, nil, nil, nil ]
150
- to_a '* * 2-5,L * *', [[0], nil, nil, [2,3,4,5,'L'], nil, nil, nil, nil ]
151
- to_a '* * */8,L * *', [[0], nil, nil, [1,9,17,25,'L'], nil, nil, nil, nil ]
152
- end
153
-
154
- it 'does not support ranges for L' do
155
-
156
- expect { cl '* * 15-L * *'}.to raise_error(ArgumentError)
157
- expect { cl '* * L/4 * *'}.to raise_error(ArgumentError)
158
- end
159
-
160
- it 'does not support multiple Ls' do
161
-
162
- expect { cl '* * L,L * *'}.to raise_error(ArgumentError)
163
- end
164
-
165
- it 'raises if L is used for something else than days' do
166
-
167
- expect { cl '* L * * *'}.to raise_error(ArgumentError)
168
- end
169
-
170
- it 'raises for out of range input' do
171
-
172
- expect { cl '60-62 * * * *'}.to raise_error(ArgumentError)
173
- expect { cl '62 * * * *'}.to raise_error(ArgumentError)
174
- expect { cl '60 * * * *'}.to raise_error(ArgumentError)
175
- expect { cl '* 25-26 * * *'}.to raise_error(ArgumentError)
176
- expect { cl '* 25 * * *'}.to raise_error(ArgumentError)
177
- #
178
- # as reported by Aimee Rose in
179
- # https://github.com/jmettraux/rufus-scheduler/pull/58
180
- end
181
- end
182
-
183
- describe '#next_time' do
184
-
185
- def nt(cronline, now)
186
- UV::Scheduler::CronLine.new(cronline).next_time(now)
187
- end
188
-
189
- it 'computes the next occurence correctly' do
190
-
191
- now = Time.at(0).getutc # Thu Jan 01 00:00:00 UTC 1970
192
-
193
- expect(nt('* * * * *', now)).to eq(now + 60)
194
- expect(nt('* * * * sun', now)).to eq(now + 259200)
195
- expect(nt('* * * * * *', now)).to eq(now + 1)
196
- expect(nt('* * 13 * fri', now)).to eq(now + 3715200)
197
-
198
- expect(nt('10 12 13 12 *', now)).to eq(now + 29938200)
199
- # this one is slow (1 year == 3 seconds)
200
- #
201
- # historical note:
202
- # (comment made in 2006 or 2007, the underlying libs got better and
203
- # that slowness is gone)
204
-
205
- expect(nt('0 0 * * thu', now)).to eq(now + 604800)
206
- expect(nt('00 0 * * thu', now)).to eq(now + 604800)
207
-
208
- expect(nt('0 0 * * *', now)).to eq(now + 24 * 3600)
209
- expect(nt('0 24 * * *', now)).to eq(now + 24 * 3600)
210
-
211
- now = local(2008, 12, 31, 23, 59, 59, 0)
212
-
213
- expect(nt('* * * * *', now)).to eq(now + 1)
214
- end
215
-
216
- it 'computes the next occurence correctly in UTC (TZ not specified)' do
217
-
218
- now = utc(1970, 1, 1)
219
-
220
- expect(nt('* * * * *', now)).to eq(utc(1970, 1, 1, 0, 1))
221
- expect(nt('* * * * sun', now)).to eq(utc(1970, 1, 4))
222
- expect(nt('* * * * * *', now)).to eq(utc(1970, 1, 1, 0, 0, 1))
223
- expect(nt('* * 13 * fri', now)).to eq(utc(1970, 2, 13))
224
-
225
- expect(nt('10 12 13 12 *', now)).to eq(utc(1970, 12, 13, 12, 10))
226
- # this one is slow (1 year == 3 seconds)
227
- expect(nt('* * 1 6 *', now)).to eq(utc(1970, 6, 1))
228
-
229
- expect(nt('0 0 * * thu', now)).to eq(utc(1970, 1, 8))
230
- end
231
-
232
- it 'computes the next occurence correctly in local TZ (TZ not specified)' do
233
-
234
- now = local(1970, 1, 1)
235
-
236
- expect(nt('* * * * *', now)).to eq(local(1970, 1, 1, 0, 1))
237
- expect(nt('* * * * sun', now)).to eq(local(1970, 1, 4))
238
- expect(nt('* * * * * *', now)).to eq(local(1970, 1, 1, 0, 0, 1))
239
- expect(nt('* * 13 * fri', now)).to eq(local(1970, 2, 13))
240
-
241
- expect(nt('10 12 13 12 *', now)).to eq(local(1970, 12, 13, 12, 10))
242
- # this one is slow (1 year == 3 seconds)
243
- expect(nt('* * 1 6 *', now)).to eq(local(1970, 6, 1))
244
-
245
- expect(nt('0 0 * * thu', now)).to eq(local(1970, 1, 8))
246
- end
247
-
248
- it 'computes the next occurence correctly in UTC (TZ specified)' do
249
-
250
- zone = 'Europe/Stockholm'
251
- tz = TZInfo::Timezone.get(zone)
252
- now = tz.local_to_utc(local(1970, 1, 1))
253
- # Midnight in zone, UTC
254
-
255
- expect(nt("* * * * * #{zone}", now)).to eq(utc(1969, 12, 31, 23, 1))
256
- expect(nt("* * * * sun #{zone}", now)).to eq(utc(1970, 1, 3, 23))
257
- expect(nt("* * * * * * #{zone}", now)).to eq(utc(1969, 12, 31, 23, 0, 1))
258
- expect(nt("* * 13 * fri #{zone}", now)).to eq(utc(1970, 2, 12, 23))
259
-
260
- expect(nt("10 12 13 12 * #{zone}", now)).to eq(utc(1970, 12, 13, 11, 10))
261
- expect(nt("* * 1 6 * #{zone}", now)).to eq(utc(1970, 5, 31, 23))
262
-
263
- expect(nt("0 0 * * thu #{zone}", now)).to eq(utc(1970, 1, 7, 23))
264
- end
265
-
266
- #it 'computes the next occurence correctly in local TZ (TZ specified)' do
267
- # zone = 'Europe/Stockholm'
268
- # tz = TZInfo::Timezone.get(zone)
269
- # now = tz.local_to_utc(utc(1970, 1, 1)).localtime
270
- # # Midnight in zone, local time
271
- # expect(nt("* * * * * #{zone}", now)).to eq(local(1969, 12, 31, 18, 1))
272
- # expect(nt("* * * * sun #{zone}", now)).to eq(local(1970, 1, 3, 18))
273
- # expect(nt("* * * * * * #{zone}", now)).to eq(local(1969, 12, 31, 18, 0, 1))
274
- # expect(nt("* * 13 * fri #{zone}", now)).to eq(local(1970, 2, 12, 18))
275
- # expect(nt("10 12 13 12 * #{zone}", now)).to eq(local(1970, 12, 13, 6, 10))
276
- # expect(nt("* * 1 6 * #{zone}", now)).to eq(local(1970, 5, 31, 19))
277
- # expect(nt("0 0 * * thu #{zone}", now)).to eq(local(1970, 1, 7, 18))
278
- #end
279
-
280
- it 'computes the next time correctly when there is a sun#2 involved' do
281
-
282
- expect(nt('* * * * sun#1', local(1970, 1, 1))).to eq(local(1970, 1, 4))
283
- expect(nt('* * * * sun#2', local(1970, 1, 1))).to eq(local(1970, 1, 11))
284
-
285
- expect(nt('* * * * sun#2', local(1970, 1, 12))).to eq(local(1970, 2, 8))
286
- end
287
-
288
- it 'computes the next time correctly when there is a sun#2,sun#3 involved' do
289
-
290
- expect(nt('* * * * sun#2,sun#3', local(1970, 1, 1))).to eq(local(1970, 1, 11))
291
- expect(nt('* * * * sun#2,sun#3', local(1970, 1, 12))).to eq(local(1970, 1, 18))
292
- end
293
-
294
- it 'understands sun#L' do
295
-
296
- expect(nt('* * * * sun#L', local(1970, 1, 1))).to eq(local(1970, 1, 25))
297
- end
298
-
299
- it 'understands sun#-1' do
300
-
301
- expect(nt('* * * * sun#-1', local(1970, 1, 1))).to eq(local(1970, 1, 25))
302
- end
303
-
304
- it 'understands sun#-2' do
305
-
306
- expect(nt('* * * * sun#-2', local(1970, 1, 1))).to eq(local(1970, 1, 18))
307
- end
308
-
309
- it 'computes the next time correctly when "L" (last day of month)' do
310
-
311
- expect(nt('* * L * *', lo(1970, 1, 1))).to eq(lo(1970, 1, 31))
312
- expect(nt('* * L * *', lo(1970, 2, 1))).to eq(lo(1970, 2, 28))
313
- expect(nt('* * L * *', lo(1972, 2, 1))).to eq(lo(1972, 2, 29))
314
- expect(nt('* * L * *', lo(1970, 4, 1))).to eq(lo(1970, 4, 30))
315
- end
316
-
317
- it 'returns a time with subseconds chopped off' do
318
-
319
- expect(nt('* * * * *', Time.now).usec).to eq(0)
320
- expect(nt('* * * * *', Time.now).iso8601(10).match(/\.0+[^\d]/)).not_to eq(nil)
321
- end
322
- end
323
-
324
- describe '#previous_time' do
325
-
326
- def pt(cronline, now)
327
- UV::Scheduler::CronLine.new(cronline).previous_time(now)
328
- end
329
-
330
- it 'returns the previous time the cron should have triggered' do
331
-
332
- expect(pt('* * * * sun', lo(1970, 1, 1))).to eq(lo(1969, 12, 28, 23, 59, 00))
333
- expect(pt('* * 13 * *', lo(1970, 1, 1))).to eq(lo(1969, 12, 13, 23, 59, 00))
334
- expect(pt('0 12 13 * *', lo(1970, 1, 1))).to eq(lo(1969, 12, 13, 12, 00))
335
-
336
- expect(pt('* * * * * sun', lo(1970, 1, 1))).to eq(lo(1969, 12, 28, 23, 59, 59))
337
- end
338
- end
339
-
340
- describe '#matches?' do
341
-
342
- it 'matches correctly in UTC (TZ not specified)' do
343
-
344
- match '* * * * *', utc(1970, 1, 1, 0, 1)
345
- match '* * * * sun', utc(1970, 1, 4)
346
- match '* * * * * *', utc(1970, 1, 1, 0, 0, 1)
347
- match '* * 13 * fri', utc(1970, 2, 13)
348
-
349
- match '10 12 13 12 *', utc(1970, 12, 13, 12, 10)
350
- match '* * 1 6 *', utc(1970, 6, 1)
351
-
352
- match '0 0 * * thu', utc(1970, 1, 8)
353
-
354
- match '0 0 1 1 *', utc(2012, 1, 1)
355
- no_match '0 0 1 1 *', utc(2012, 1, 1, 1, 0)
356
- end
357
-
358
- it 'matches correctly in local TZ (TZ not specified)' do
359
-
360
- match '* * * * *', local(1970, 1, 1, 0, 1)
361
- match '* * * * sun', local(1970, 1, 4)
362
- match '* * * * * *', local(1970, 1, 1, 0, 0, 1)
363
- match '* * 13 * fri', local(1970, 2, 13)
364
-
365
- match '10 12 13 12 *', local(1970, 12, 13, 12, 10)
366
- match '* * 1 6 *', local(1970, 6, 1)
367
-
368
- match '0 0 * * thu', local(1970, 1, 8)
369
-
370
- match '0 0 1 1 *', local(2012, 1, 1)
371
- no_match '0 0 1 1 *', local(2012, 1, 1, 1, 0)
372
- end
373
-
374
- it 'matches correctly in UTC (TZ specified)' do
375
-
376
- zone = 'Europe/Stockholm'
377
-
378
- match "* * * * * #{zone}", utc(1969, 12, 31, 23, 1)
379
- match "* * * * sun #{zone}", utc(1970, 1, 3, 23)
380
- match "* * * * * * #{zone}", utc(1969, 12, 31, 23, 0, 1)
381
- match "* * 13 * fri #{zone}", utc(1970, 2, 12, 23)
382
-
383
- match "10 12 13 12 * #{zone}", utc(1970, 12, 13, 11, 10)
384
- match "* * 1 6 * #{zone}", utc(1970, 5, 31, 23)
385
-
386
- match "0 0 * * thu #{zone}", utc(1970, 1, 7, 23)
387
- end
388
-
389
- it 'matches correctly when there is a sun#2 involved' do
390
-
391
- match '* * 13 * fri#2', utc(1970, 2, 13)
392
- no_match '* * 13 * fri#2', utc(1970, 2, 20)
393
- end
394
-
395
- it 'matches correctly when there is a L involved' do
396
-
397
- match '* * L * *', utc(1970, 1, 31)
398
- no_match '* * L * *', utc(1970, 1, 30)
399
- end
400
-
401
- it 'matches correctly when there is a sun#2,sun#3 involved' do
402
-
403
- no_match '* * * * sun#2,sun#3', local(1970, 1, 4)
404
- match '* * * * sun#2,sun#3', local(1970, 1, 11)
405
- match '* * * * sun#2,sun#3', local(1970, 1, 18)
406
- no_match '* * * * sun#2,sun#3', local(1970, 1, 25)
407
- end
408
- end
409
-
410
- describe '#monthdays' do
411
-
412
- it 'returns the appropriate "sun#2"-like string' do
413
-
414
- class UV::Scheduler::CronLine
415
- public :monthdays
416
- end
417
-
418
- cl = UV::Scheduler::CronLine.new('* * * * *')
419
-
420
- expect(cl.monthdays(local(1970, 1, 1))).to eq(%w[ thu#1 thu#-5 ])
421
- expect(cl.monthdays(local(1970, 1, 7))).to eq(%w[ wed#1 wed#-4 ])
422
- expect(cl.monthdays(local(1970, 1, 14))).to eq(%w[ wed#2 wed#-3 ])
423
-
424
- expect(cl.monthdays(local(2011, 3, 11))).to eq(%w[ fri#2 fri#-3 ])
425
- end
426
- end
427
-
428
- describe '#frequency' do
429
-
430
- it 'returns the shortest delta between two occurrences' do
431
-
432
- expect(UV::Scheduler::CronLine.new('* * * * *').frequency).to eq(60)
433
- expect(UV::Scheduler::CronLine.new('* * * * * *').frequency).to eq(1)
434
-
435
- expect(UV::Scheduler::CronLine.new('5 23 * * *').frequency).to eq(24 * 3600)
436
- expect(UV::Scheduler::CronLine.new('5 * * * *').frequency).to eq(3600)
437
- expect(UV::Scheduler::CronLine.new('10,20,30 * * * *').frequency).to eq(600)
438
- end
439
- end
440
- end