uv-rays 2.0.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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