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 +4 -4
- data/lib/uv-rays.rb +0 -1
- data/lib/uv-rays/scheduler.rb +26 -12
- data/lib/uv-rays/scheduler/time.rb +40 -9
- data/lib/uv-rays/version.rb +1 -1
- data/spec/zen_spec.rb +27 -0
- data/uv-rays.gemspec +1 -0
- metadata +18 -5
- data/lib/uv-rays/scheduler/cron.rb +0 -389
- data/spec/scheduler_cron_spec.rb +0 -440
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90bfee69384f25d0c1ce287d91028996305ee511
|
4
|
+
data.tar.gz: a5d3f82c72d591fb56f54b0d92eade0768d042bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ce208199ef4a990628aa1d82b1b2f64186fe56aedb46c84991edba1b773cc95dce94086b12fd7ef058a432daf3b127ddec6b56361cbd3649b06af5f262e3717
|
7
|
+
data.tar.gz: 9851c7e265d84351307d60c29ba1e86f1dae9e0b36f4c44bc655e7afc15af74774f0f42b73decba188e9520f4fac4cbc6d15b69ceec00cd11ce575f5b9095a12
|
data/lib/uv-rays.rb
CHANGED
data/lib/uv-rays/scheduler.rb
CHANGED
@@ -32,15 +32,23 @@ module UV
|
|
32
32
|
|
33
33
|
# Provide relevant inspect information
|
34
34
|
def inspect
|
35
|
-
insp = String.new("#<#{self.class}
|
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.
|
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
|
-
#
|
180
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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)
|
data/lib/uv-rays/version.rb
CHANGED
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
|
+
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-
|
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
|
-
|
data/spec/scheduler_cron_spec.rb
DELETED
@@ -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
|