rocketjob 5.3.0 → 5.3.1

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
  SHA256:
3
- metadata.gz: fe39da29017eca601c104ca9d7c4fa90f903c32cbf3822e064cbd4a7f0780272
4
- data.tar.gz: ce81cfb0d3a0ac4dbfe4ebe7c549077e56ca52d19ca41a2fda150561ecfeedd7
3
+ metadata.gz: 42be7df83c1d38b5ef3a41751e3cc6afd6fb885638a7d144bfd1f71ff5de441e
4
+ data.tar.gz: fd3942b7fe1aa2d76bda3395561bea56122b9eab58c0ae65d99fbea76e3d771c
5
5
  SHA512:
6
- metadata.gz: fa49046d738a5064da99363e0d854253d17c375ab1e75b095c118139d9081781b9e4177bc03cf70abd4edb906b43cdc033b96234bd5864b5f9959e81f1421074
7
- data.tar.gz: 5e67742618a4ec874a1c43649d8619cec007380f8db84a3a81f584ec6f9896be0e14e062e69955158732b32e419a8ac7bfab9eef394c983a9f484a9c5c04c934
6
+ metadata.gz: 324641130fd1bb0724058d81cdf33dad02bbdeef2047413a479dcb0bb0782be37be3e0aad9b5c57d9ea200e6cc30889ce06ce8130716f91399472bf317ed4089
7
+ data.tar.gz: dfe9e7f121e7fee9c4713d5fc819e3889d1e0d1d208ac14cd4678f83ce0f802f51e2067cd10d687301a9f7470a609eab6259036e191ae8e6198f7a7830cbbd82
@@ -1,4 +1,5 @@
1
1
  require "active_support/concern"
2
+ require "fugit"
2
3
 
3
4
  module RocketJob
4
5
  module Batch
@@ -43,6 +44,10 @@ module RocketJob
43
44
  field :secondary_duration, type: Integer, class_attribute: true, user_editable: true, copy_on_restart: true
44
45
 
45
46
  define_batch_throttle :throttle_windows_exceeded?, filter: :throttle_filter_id
47
+
48
+ validates_each :primary_schedule, :secondary_schedule do |record, attr, value|
49
+ record.errors.add(attr, "Invalid #{attr}: #{value.inspect}") if value && !Fugit::Cron.new(value)
50
+ end
46
51
  end
47
52
 
48
53
  private
@@ -56,10 +61,10 @@ module RocketJob
56
61
  end
57
62
 
58
63
  def throttle_outside_window?(schedule, duration)
59
- cron = Plugins::Rufus::CronLine.new(schedule)
60
- time = Time.now + 1
64
+ cron = Fugit::Cron.new(schedule)
65
+ time = Time.now.utc + 1
61
66
  # Add 1 second since right now could be the very beginning of the processing window.
62
- previous_time = cron.previous_time(time).to_time
67
+ previous_time = cron.previous_time(time).to_utc_time
63
68
  previous_time + duration < time
64
69
  end
65
70
  end
@@ -110,8 +110,6 @@ module RocketJob
110
110
  servers
111
111
  end
112
112
 
113
- private
114
-
115
113
  def rocket_job_batch_throttled?(slice, worker)
116
114
  filter = self.class.rocket_job_batch_throttles.matching_filter(self, slice)
117
115
  return false unless filter
@@ -150,7 +148,7 @@ module RocketJob
150
148
  records = slice.records
151
149
 
152
150
  # Skip records already processed, if any.
153
- #slice.processing_record_number ||= 0
151
+ # slice.processing_record_number ||= 0
154
152
  # TODO: Must append to existing output slices before this can be enabled.
155
153
  # if !collect_output && (slice.processing_record_number > 1)
156
154
  # records = records[slice.processing_record_number - 1..-1]
@@ -104,8 +104,6 @@ module RocketJob
104
104
  end
105
105
  end
106
106
 
107
- private
108
-
109
107
  @load_time = Time.now.utc
110
108
  @subscribers = Concurrent::Map.new { Concurrent::Array.new }
111
109
 
@@ -22,8 +22,6 @@ module RocketJob
22
22
  @collection_name = collection_name&.to_sym
23
23
  end
24
24
 
25
- private
26
-
27
25
  module ClassMethods
28
26
  def with_collection(collection_name)
29
27
  all.with_collection(collection_name)
@@ -1,4 +1,5 @@
1
1
  require "active_support/concern"
2
+ require "fugit"
2
3
 
3
4
  module RocketJob
4
5
  module Plugins
@@ -17,7 +18,9 @@ module RocketJob
17
18
 
18
19
  field :cron_schedule, type: String, class_attribute: true, user_editable: true, copy_on_restart: true
19
20
 
20
- validate :rocket_job_cron_valid
21
+ validates_each :cron_schedule do |record, attr, value|
22
+ record.errors.add(attr, "Invalid cron_schedule: #{value.inspect}") if value && !Fugit::Cron.new(value)
23
+ end
21
24
  before_save :rocket_job_cron_set_run_at
22
25
 
23
26
  private
@@ -49,24 +52,14 @@ module RocketJob
49
52
  # The next time as of this time.
50
53
  # Default: Time.now
51
54
  def rocket_job_cron_next_time(time = Time.now)
52
- RocketJob::Plugins::Rufus::CronLine.new(cron_schedule).next_time(time)
55
+ Fugit::Cron.new(cron_schedule).next_time.to_utc_time
53
56
  end
54
57
 
55
- private
56
-
57
58
  def rocket_job_cron_set_run_at
58
59
  return unless cron_schedule
59
60
 
60
61
  self.run_at = rocket_job_cron_next_time if cron_schedule_changed? && !run_at_changed?
61
62
  end
62
-
63
- def rocket_job_cron_valid
64
- return unless cron_schedule
65
-
66
- RocketJob::Plugins::Rufus::CronLine.new(cron_schedule)
67
- rescue ArgumentError => e
68
- errors.add(:cron_schedule, e.message)
69
- end
70
63
  end
71
64
  end
72
65
  end
@@ -1,4 +1,5 @@
1
1
  require "active_support/concern"
2
+ require "fugit"
2
3
 
3
4
  module RocketJob
4
5
  module Plugins
@@ -47,18 +48,14 @@ module RocketJob
47
48
 
48
49
  validates_presence_of :processing_schedule, :processing_duration
49
50
  validates_each :processing_schedule do |record, attr, value|
50
- begin
51
- RocketJob::Plugins::Rufus::CronLine.new(value)
52
- rescue ArgumentError => e
53
- record.errors.add(attr, e.message)
54
- end
51
+ record.errors.add(attr, "Invalid schedule: #{value.inspect}") unless Fugit::Cron.new(value)
55
52
  end
56
53
  end
57
54
 
58
55
  # Returns [true|false] whether this job is currently inside its processing window
59
56
  def rocket_job_processing_window_active?
60
- time = Time.now
61
- previous_time = rocket_job_processing_schedule.previous_time(time)
57
+ time = Time.now.utc
58
+ previous_time = Fugit::Cron.new(processing_schedule).previous_time(time).to_utc_time
62
59
  # Inside previous processing window?
63
60
  previous_time + processing_duration > time
64
61
  end
@@ -69,17 +66,14 @@ module RocketJob
69
66
  def rocket_job_processing_window_check
70
67
  return if rocket_job_processing_window_active?
71
68
 
72
- logger.warn("Processing window closed before job was processed. Job is re-scheduled to run at: #{rocket_job_processing_schedule.next_time}")
69
+ next_time = Fugit::Cron.new(processing_schedule).next_time.to_utc_time
70
+ logger.warn("Processing window closed before job was processed. Job is re-scheduled to run at: #{next_time}")
73
71
  self.worker_name ||= "inline"
74
72
  requeue!(worker_name)
75
73
  end
76
74
 
77
75
  def rocket_job_processing_window_set_run_at
78
- self.run_at = rocket_job_processing_schedule.next_time unless rocket_job_processing_window_active?
79
- end
80
-
81
- def rocket_job_processing_schedule
82
- RocketJob::Plugins::Rufus::CronLine.new(processing_schedule)
76
+ self.run_at = Fugit::Cron.new(processing_schedule).next_time.to_utc_time unless rocket_job_processing_window_active?
83
77
  end
84
78
  end
85
79
  end
@@ -1,3 +1,3 @@
1
1
  module RocketJob
2
- VERSION = "5.3.0".freeze
2
+ VERSION = "5.3.1".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rocketjob
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.0
4
+ version: 5.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-14 00:00:00.000000000 Z
11
+ date: 2020-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aasm
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '4.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: fugit
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.3'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.3'
97
111
  description:
98
112
  email:
99
113
  executables:
@@ -163,8 +177,6 @@ files:
163
177
  - lib/rocket_job/plugins/processing_window.rb
164
178
  - lib/rocket_job/plugins/restart.rb
165
179
  - lib/rocket_job/plugins/retry.rb
166
- - lib/rocket_job/plugins/rufus/cron_line.rb
167
- - lib/rocket_job/plugins/rufus/zo_time.rb
168
180
  - lib/rocket_job/plugins/singleton.rb
169
181
  - lib/rocket_job/plugins/state_machine.rb
170
182
  - lib/rocket_job/plugins/transaction.rb
@@ -212,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
224
  - !ruby/object:Gem::Version
213
225
  version: '0'
214
226
  requirements: []
215
- rubygems_version: 3.1.2
227
+ rubygems_version: 3.0.8
216
228
  signing_key:
217
229
  specification_version: 4
218
230
  summary: Ruby's missing batch processing system.
@@ -1,520 +0,0 @@
1
- #--
2
- # Copyright (c) 2006-2017, John Mettraux, jmettraux@gmail.com
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
- #
22
- # Made in Japan.
23
- #++
24
- #@formatter:off
25
-
26
- require 'set'
27
-
28
- module RocketJob::Plugins::Rufus
29
-
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 max number of years in the future or the past before giving up
37
- # searching for #next_time or #previous_time respectively
38
- #
39
- NEXT_TIME_MAX_YEARS = 14
40
-
41
- # The string used for creating this cronline instance.
42
- #
43
- attr_reader :original
44
- attr_reader :original_timezone
45
-
46
- attr_reader :seconds
47
- attr_reader :minutes
48
- attr_reader :hours
49
- attr_reader :days
50
- attr_reader :months
51
- #attr_reader :monthdays # reader defined below
52
- attr_reader :weekdays
53
- attr_reader :timezone
54
-
55
- def initialize(line)
56
-
57
- fail ArgumentError.new(
58
- "not a string: #{line.inspect}"
59
- ) unless line.is_a?(String)
60
-
61
- @original = line
62
- @original_timezone = nil
63
-
64
- items = line.split
65
-
66
- if @timezone = RocketJob::Plugins::Rufus::ZoTime.get_tzone(items.last)
67
- @original_timezone = items.pop
68
- else
69
- @timezone = RocketJob::Plugins::Rufus::ZoTime.get_tzone(:current)
70
- end
71
-
72
- fail ArgumentError.new(
73
- "not a valid cronline : '#{line}'"
74
- ) unless items.length == 5 or items.length == 6
75
-
76
- offset = items.length - 5
77
-
78
- @seconds = offset == 1 ? parse_item(items[0], 0, 59) : [ 0 ]
79
- @minutes = parse_item(items[0 + offset], 0, 59)
80
- @hours = parse_item(items[1 + offset], 0, 24)
81
- @days = parse_item(items[2 + offset], -30, 31)
82
- @months = parse_item(items[3 + offset], 1, 12)
83
- @weekdays, @monthdays = parse_weekdays(items[4 + offset])
84
-
85
- [ @seconds, @minutes, @hours, @months ].each do |es|
86
-
87
- fail ArgumentError.new(
88
- "invalid cronline: '#{line}'"
89
- ) if es && es.find { |e| ! e.is_a?(Integer) }
90
- end
91
-
92
- if @days && @days.include?(0) # gh-221
93
-
94
- fail ArgumentError.new('invalid day 0 in cronline')
95
- end
96
- end
97
-
98
- # Returns true if the given time matches this cron line.
99
- #
100
- def matches?(time)
101
-
102
- # FIXME Don't create a new ZoTime if time is already a ZoTime in same
103
- # zone ...
104
- # Wait, this seems only used in specs...
105
- t = ZoTime.new(time.to_f, @timezone)
106
-
107
- return false unless sub_match?(t, :sec, @seconds)
108
- return false unless sub_match?(t, :min, @minutes)
109
- return false unless sub_match?(t, :hour, @hours)
110
- return false unless date_match?(t)
111
- true
112
- end
113
-
114
- # Returns the next time that this cron line is supposed to 'fire'
115
- #
116
- # This is raw, 3 secs to iterate over 1 year on my macbook :( brutal.
117
- # (Well, I was wrong, takes 0.001 sec on 1.8.7 and 1.9.1)
118
- #
119
- # This method accepts an optional Time parameter. It's the starting point
120
- # for the 'search'. By default, it's Time.now
121
- #
122
- # Note that the time instance returned will be in the same time zone that
123
- # the given start point Time (thus a result in the local time zone will
124
- # be passed if no start time is specified (search start time set to
125
- # Time.now))
126
- #
127
- # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
128
- # Time.mktime(2008, 10, 24, 7, 29))
129
- # #=> Fri Oct 24 07:30:00 -0500 2008
130
- #
131
- # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
132
- # Time.utc(2008, 10, 24, 7, 29))
133
- # #=> Fri Oct 24 07:30:00 UTC 2008
134
- #
135
- # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
136
- # Time.utc(2008, 10, 24, 7, 29)).localtime
137
- # #=> Fri Oct 24 02:30:00 -0500 2008
138
- #
139
- # (Thanks to K Liu for the note and the examples)
140
- #
141
- def next_time(from=ZoTime.now)
142
-
143
- nt = nil
144
- zt = ZoTime.new(from.to_i + 1, @timezone)
145
- maxy = from.year + NEXT_TIME_MAX_YEARS
146
-
147
- loop do
148
-
149
- nt = zt.dup
150
-
151
- fail RangeError.new(
152
- "failed to reach occurrence within " +
153
- "#{NEXT_TIME_MAX_YEARS} years for '#{original}'"
154
- ) if nt.year > maxy
155
-
156
- unless date_match?(nt)
157
- zt.add((24 - nt.hour) * 3600 - nt.min * 60 - nt.sec)
158
- next
159
- end
160
- unless sub_match?(nt, :hour, @hours)
161
- zt.add((60 - nt.min) * 60 - nt.sec)
162
- next
163
- end
164
- unless sub_match?(nt, :min, @minutes)
165
- zt.add(60 - nt.sec)
166
- next
167
- end
168
- unless sub_match?(nt, :sec, @seconds)
169
- zt.add(next_second(nt))
170
- next
171
- end
172
-
173
- break
174
- end
175
-
176
- nt
177
- end
178
-
179
- # Returns the previous time the cronline matched. It's like next_time, but
180
- # for the past.
181
- #
182
- def previous_time(from=ZoTime.now)
183
-
184
- pt = nil
185
- zt = ZoTime.new(from.to_i - 1, @timezone)
186
- miny = from.year - NEXT_TIME_MAX_YEARS
187
-
188
- loop do
189
-
190
- pt = zt.dup
191
-
192
- fail RangeError.new(
193
- "failed to reach occurrence within " +
194
- "#{NEXT_TIME_MAX_YEARS} years for '#{original}'"
195
- ) if pt.year < miny
196
-
197
- unless date_match?(pt)
198
- zt.substract(pt.hour * 3600 + pt.min * 60 + pt.sec + 1)
199
- next
200
- end
201
- unless sub_match?(pt, :hour, @hours)
202
- zt.substract(pt.min * 60 + pt.sec + 1)
203
- next
204
- end
205
- unless sub_match?(pt, :min, @minutes)
206
- zt.substract(pt.sec + 1)
207
- next
208
- end
209
- unless sub_match?(pt, :sec, @seconds)
210
- zt.substract(prev_second(pt))
211
- next
212
- end
213
-
214
- break
215
- end
216
-
217
- pt
218
- end
219
-
220
- # Returns an array of 6 arrays (seconds, minutes, hours, days,
221
- # months, weekdays).
222
- # This method is mostly used by the cronline specs.
223
- #
224
- def to_a
225
-
226
- [
227
- toa(@seconds),
228
- toa(@minutes),
229
- toa(@hours),
230
- toa(@days),
231
- toa(@months),
232
- toa(@weekdays),
233
- toa(@monthdays),
234
- @timezone.name
235
- ]
236
- end
237
- alias to_array to_a
238
-
239
- # Returns a quickly computed approximation of the frequency for this
240
- # cron line.
241
- #
242
- # #brute_frequency, on the other hand, will compute the frequency by
243
- # examining a whole year, that can take more than seconds for a seconds
244
- # level cron...
245
- #
246
- def frequency
247
-
248
- return brute_frequency unless @seconds && @seconds.length > 1
249
-
250
- secs = toa(@seconds)
251
-
252
- secs[1..-1].inject([ secs[0], 60 ]) { |(prev, delta), sec|
253
- d = sec - prev
254
- [ sec, d < delta ? d : delta ]
255
- }[1]
256
- end
257
-
258
- # Caching facility. Currently only used for brute frequencies.
259
- #
260
- @cache = {}; class << self; attr_reader :cache; end
261
-
262
- # Returns the shortest delta between two potential occurences of the
263
- # schedule described by this cronline.
264
- #
265
- # .
266
- #
267
- # For a simple cronline like "*/5 * * * *", obviously the frequency is
268
- # five minutes. Why does this method look at a whole year of #next_time ?
269
- #
270
- # Consider "* * * * sun#2,sun#3", the computed frequency is 1 week
271
- # (the shortest delta is the one between the second sunday and the third
272
- # sunday). This method takes no chance and runs next_time for the span
273
- # of a whole year and keeps the shortest.
274
- #
275
- # Of course, this method can get VERY slow if you call on it a second-
276
- # based cronline...
277
- #
278
- def brute_frequency
279
-
280
- key = "brute_frequency:#{@original}"
281
-
282
- delta = self.class.cache[key]
283
- return delta if delta
284
-
285
- delta = 366 * DAY_S
286
-
287
- t0 = previous_time(Time.local(2000, 1, 1))
288
-
289
- loop do
290
-
291
- break if delta <= 1
292
- break if delta <= 60 && @seconds && @seconds.size == 1
293
-
294
- t1 = next_time(t0)
295
- d = t1 - t0
296
- delta = d if d < delta
297
- break if @months.nil? && t1.month == 2
298
- break if @months.nil? && @days.nil? && t1.day == 2
299
- break if @months.nil? && @days.nil? && @hours.nil? && t1.hour == 1
300
- break if @months.nil? && @days.nil? && @hours.nil? && @minutes.nil? && t1.min == 1
301
- break if t1.year >= 2001
302
-
303
- t0 = t1
304
- end
305
-
306
- self.class.cache[key] = delta
307
- end
308
-
309
- def next_second(time)
310
-
311
- secs = toa(@seconds)
312
-
313
- return secs.first + 60 - time.sec if time.sec > secs.last
314
-
315
- secs.shift while secs.first < time.sec
316
-
317
- secs.first - time.sec
318
- end
319
-
320
- def prev_second(time)
321
-
322
- secs = toa(@seconds)
323
-
324
- return time.sec + 60 - secs.last if time.sec < secs.first
325
-
326
- secs.pop while time.sec < secs.last
327
-
328
- time.sec - secs.last
329
- end
330
-
331
- protected
332
-
333
- def sc_sort(a)
334
-
335
- a.sort_by { |e| e.is_a?(String) ? 61 : e.to_i }
336
- end
337
-
338
- if RUBY_VERSION >= '1.9'
339
- def toa(item)
340
- item == nil ? nil : item.to_a
341
- end
342
- else
343
- def toa(item)
344
- item.is_a?(Set) ? sc_sort(item.to_a) : item
345
- end
346
- end
347
-
348
- WEEKDAYS = %w[ sun mon tue wed thu fri sat ]
349
- DAY_S = 24 * 3600
350
-
351
- def parse_weekdays(item)
352
-
353
- return nil if item == '*'
354
-
355
- weekdays = nil
356
- monthdays = nil
357
-
358
- item.downcase.split(',').each do |it|
359
-
360
- WEEKDAYS.each_with_index { |a, i| it.gsub!(/#{a}/, i.to_s) }
361
-
362
- it = it.gsub(/([^#])l/, '\1#-1')
363
- # "5L" == "5#-1" == the last Friday
364
-
365
- if m = it.match(/\A(.+)#(l|-?[12345])\z/)
366
-
367
- fail ArgumentError.new(
368
- "ranges are not supported for monthdays (#{it})"
369
- ) if m[1].index('-')
370
-
371
- it = it.gsub(/#l/, '#-1')
372
-
373
- (monthdays ||= []) << it
374
-
375
- else
376
-
377
- fail ArgumentError.new(
378
- "invalid weekday expression (#{item})"
379
- ) if it !~ /\A0*[0-7](-0*[0-7])?\z/
380
-
381
- its = it.index('-') ? parse_range(it, 0, 7) : [ Integer(it) ]
382
- its = its.collect { |i| i == 7 ? 0 : i }
383
-
384
- (weekdays ||= []).concat(its)
385
- end
386
- end
387
-
388
- weekdays = weekdays.uniq.sort if weekdays
389
-
390
- [ weekdays, monthdays ]
391
- end
392
-
393
- def parse_item(item, min, max)
394
-
395
- return nil if item == '*'
396
-
397
- r = item.split(',').map { |i| parse_range(i.strip, min, max) }.flatten
398
-
399
- fail ArgumentError.new(
400
- "found duplicates in #{item.inspect}"
401
- ) if r.uniq.size < r.size
402
-
403
- r = sc_sort(r)
404
-
405
- Set.new(r)
406
- end
407
-
408
- RANGE_REGEX = /\A(\*|-?\d{1,2})(?:-(-?\d{1,2}))?(?:\/(\d{1,2}))?\z/
409
-
410
- def parse_range(item, min, max)
411
-
412
- return %w[ L ] if item == 'L'
413
-
414
- item = '*' + item if item[0, 1] == '/'
415
-
416
- m = item.match(RANGE_REGEX)
417
-
418
- fail ArgumentError.new(
419
- "cannot parse #{item.inspect}"
420
- ) unless m
421
-
422
- mmin = min == -30 ? 1 : min # days
423
-
424
- sta = m[1]
425
- sta = sta == '*' ? mmin : sta.to_i
426
-
427
- edn = m[2]
428
- edn = edn ? edn.to_i : sta
429
- edn = max if m[1] == '*'
430
-
431
- inc = m[3]
432
- inc = inc ? inc.to_i : 1
433
-
434
- fail ArgumentError.new(
435
- "#{item.inspect} positive/negative ranges not allowed"
436
- ) if (sta < 0 && edn > 0) || (sta > 0 && edn < 0)
437
-
438
- fail ArgumentError.new(
439
- "#{item.inspect} descending day ranges not allowed"
440
- ) if min == -30 && sta > edn
441
-
442
- fail ArgumentError.new(
443
- "#{item.inspect} is not in range #{min}..#{max}"
444
- ) if sta < min || edn > max
445
-
446
- fail ArgumentError.new(
447
- "#{item.inspect} increment must be greater than zero"
448
- ) if inc == 0
449
-
450
- r = []
451
- val = sta
452
-
453
- loop do
454
- v = val
455
- v = 0 if max == 24 && v == 24 # hours
456
- r << v
457
- break if inc == 1 && val == edn
458
- val += inc
459
- break if inc > 1 && val > edn
460
- val = min if val > max
461
- end
462
-
463
- r.uniq
464
- end
465
-
466
- # FIXME: Eventually split into day_match?, hour_match? and monthdays_match?o
467
- #
468
- def sub_match?(time, accessor, values)
469
-
470
- return true if values.nil?
471
-
472
- value = time.send(accessor)
473
-
474
- if accessor == :day
475
-
476
- values.each do |v|
477
- return true if v == 'L' && (time + DAY_S).day == 1
478
- return true if v.to_i < 0 && (time + (1 - v) * DAY_S).day == 1
479
- end
480
- end
481
-
482
- if accessor == :hour
483
-
484
- return true if value == 0 && values.include?(24)
485
- end
486
-
487
- if accessor == :monthdays
488
-
489
- return true if (values & value).any?
490
- end
491
-
492
- values.include?(value)
493
- end
494
-
495
- # def monthday_match?(zt, values)
496
- #
497
- # return true if values.nil?
498
- #
499
- # today_values = monthdays(zt)
500
- #
501
- # (today_values & values).any?
502
- # end
503
-
504
- def date_match?(zt)
505
-
506
- return false unless sub_match?(zt, :day, @days)
507
- return false unless sub_match?(zt, :month, @months)
508
-
509
- return true if (
510
- (@weekdays && @monthdays) &&
511
- (sub_match?(zt, :wday, @weekdays) ||
512
- sub_match?(zt, :monthdays, @monthdays)))
513
-
514
- return false unless sub_match?(zt, :wday, @weekdays)
515
- return false unless sub_match?(zt, :monthdays, @monthdays)
516
-
517
- true
518
- end
519
- end
520
- end
@@ -1,524 +0,0 @@
1
- #--
2
- # Copyright (c) 2006-2017, John Mettraux, jmettraux@gmail.com
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
- #
22
- # Made in Japan.
23
- #++
24
- #@formatter:off
25
-
26
- module RocketJob::Plugins::Rufus
27
- #
28
- # Zon{ing|ed}Time, whatever.
29
- #
30
- class ZoTime
31
-
32
- attr_reader :seconds
33
- attr_reader :zone
34
-
35
- def initialize(s, zone)
36
-
37
- @seconds = s.to_f
38
- @zone = self.class.get_tzone(zone || :current)
39
-
40
- fail ArgumentError.new(
41
- "cannot determine timezone from #{zone.inspect}" +
42
- " (etz:#{ENV['TZ'].inspect},tnz:#{Time.now.zone.inspect}," +
43
- "tzid:#{defined?(TZInfo::Data).inspect})\n" +
44
- "Try setting `ENV['TZ'] = 'Continent/City'` in your script " +
45
- "(see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)" +
46
- (defined?(TZInfo::Data) ? '' : " and adding 'tzinfo-data' to your gems")
47
- ) unless @zone
48
-
49
- @time = nil # cache for #to_time result
50
- end
51
-
52
- def seconds=(f)
53
-
54
- @time = nil
55
- @seconds = f
56
- end
57
-
58
- def zone=(z)
59
-
60
- @time = nil
61
- @zone = self.class.get_tzone(zone || :current)
62
- end
63
-
64
- def utc
65
-
66
- Time.utc(1970, 1, 1) + @seconds
67
- end
68
-
69
- # Returns a Ruby Time instance.
70
- #
71
- # Warning: the timezone of that Time instance will be UTC.
72
- #
73
- def to_time
74
-
75
- @time ||= begin; u = utc; @zone.period_for_utc(u).to_local(u); end
76
- end
77
-
78
- %w[
79
- year month day wday hour min sec usec asctime
80
- ].each do |m|
81
- define_method(m) { to_time.send(m) }
82
- end
83
- def iso8601(fraction_digits=0); to_time.iso8601(fraction_digits); end
84
-
85
- def ==(o)
86
-
87
- o.is_a?(ZoTime) && o.seconds == @seconds && o.zone == @zone
88
- end
89
- #alias eq? == # FIXME see Object#== (ri)
90
-
91
- def >(o); @seconds > _to_f(o); end
92
- def >=(o); @seconds >= _to_f(o); end
93
- def <(o); @seconds < _to_f(o); end
94
- def <=(o); @seconds <= _to_f(o); end
95
- def <=>(o); @seconds <=> _to_f(o); end
96
-
97
- alias getutc utc
98
- alias getgm utc
99
-
100
- def to_i
101
-
102
- @seconds.to_i
103
- end
104
-
105
- def to_f
106
-
107
- @seconds
108
- end
109
-
110
- def is_dst?
111
-
112
- @zone.period_for_utc(utc).std_offset != 0
113
- end
114
- alias isdst is_dst?
115
-
116
- def utc_offset
117
-
118
- #@zone.period_for_utc(utc).utc_offset
119
- #@zone.period_for_utc(utc).utc_total_offset
120
- #@zone.period_for_utc(utc).std_offset
121
- @zone.period_for_utc(utc).utc_offset
122
- end
123
-
124
- def strftime(format)
125
-
126
- format = format.gsub(/%(\/?Z|:{0,2}z)/) { |f| strfz(f) }
127
-
128
- to_time.strftime(format)
129
- end
130
-
131
- def add(t); @time = nil; @seconds += t.to_f; end
132
- def substract(t); @time = nil; @seconds -= t.to_f; end
133
-
134
- def +(t); inc(t, 1); end
135
- def -(t); inc(t, -1); end
136
-
137
- WEEK_S = 7 * 24 * 3600
138
-
139
- def monthdays
140
-
141
- date = to_time
142
-
143
- pos = 1
144
- d = self.dup
145
-
146
- loop do
147
- d.add(-WEEK_S)
148
- break if d.month != date.month
149
- pos = pos + 1
150
- end
151
-
152
- neg = -1
153
- d = self.dup
154
-
155
- loop do
156
- d.add(WEEK_S)
157
- break if d.month != date.month
158
- neg = neg - 1
159
- end
160
-
161
- [ "#{date.wday}##{pos}", "#{date.wday}##{neg}" ]
162
- end
163
-
164
- def to_s
165
-
166
- strftime('%Y-%m-%d %H:%M:%S %z')
167
- end
168
-
169
- def to_debug_s
170
-
171
- uo = self.utc_offset
172
- uos = uo < 0 ? '-' : '+'
173
- uo = uo.abs
174
- uoh, uom = [ uo / 3600, uo % 3600 ]
175
-
176
- [
177
- 'zt',
178
- self.strftime('%Y-%m-%d %H:%M:%S'),
179
- "%s%02d:%02d" % [ uos, uoh, uom ],
180
- "dst:#{self.isdst}"
181
- ].join(' ')
182
- end
183
-
184
- # Debug current time by showing local time / delta / utc time
185
- # for example: "0120-7(0820)"
186
- #
187
- def to_utc_comparison_s
188
-
189
- per = @zone.period_for_utc(utc)
190
- off = per.utc_total_offset
191
-
192
- off = off / 3600
193
- off = off >= 0 ? "+#{off}" : off.to_s
194
-
195
- strftime('%H%M') + off + utc.strftime('(%H%M)')
196
- end
197
-
198
- def to_time_s
199
-
200
- strftime("%H:%M:%S.#{'%06d' % usec}")
201
- end
202
-
203
- def self.now(zone=nil)
204
-
205
- ZoTime.new(Time.now.to_f, zone)
206
- end
207
-
208
- # https://en.wikipedia.org/wiki/ISO_8601
209
- # Postel's law applies
210
- #
211
- def self.extract_iso8601_zone(s)
212
-
213
- m = s.match(
214
- /[0-2]\d(?::?[0-6]\d(?::?[0-6]\d))?\s*([+-]\d\d(?::?\d\d)?)\s*\z/)
215
- return nil unless m
216
-
217
- zs = m[1].split(':')
218
- zs << '00' if zs.length < 2
219
-
220
- zh = zs[0].to_i.abs
221
-
222
- return nil if zh > 24
223
- return nil if zh == 24 && zs[1].to_i != 0
224
-
225
- zs.join(':')
226
- end
227
-
228
- def self.parse(str, opts={})
229
-
230
- if defined?(::Chronic) && t = ::Chronic.parse(str, opts)
231
- return ZoTime.new(t, nil)
232
- end
233
-
234
- #rold = RUBY_VERSION < '1.9.0'
235
- #rold = RUBY_VERSION < '2.0.0'
236
-
237
- begin
238
- DateTime.parse(str)
239
- rescue
240
- fail ArgumentError, "no time information in #{str.inspect}"
241
- end #if rold
242
- #
243
- # is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now`
244
-
245
- zone = nil
246
-
247
- s =
248
- str.gsub(/\S+/) do |w|
249
- if z = get_tzone(w)
250
- zone ||= z
251
- ''
252
- else
253
- w
254
- end
255
- end
256
-
257
- local = Time.parse(s)
258
- izone = extract_iso8601_zone(s)
259
-
260
- zone ||=
261
- if s.match(/\dZ\b/)
262
- get_tzone('Zulu')
263
- #elsif rold && izone
264
- elsif izone
265
- get_tzone(izone)
266
- elsif local.zone.nil? && izone
267
- get_tzone(local.strftime('%:z'))
268
- else
269
- get_tzone(:local)
270
- end
271
-
272
- secs =
273
- #if rold && izone
274
- if izone
275
- local.to_f
276
- else
277
- zone.period_for_local(local).to_utc(local).to_f
278
- end
279
-
280
- ZoTime.new(secs, zone)
281
- end
282
-
283
- def self.get_tzone(str)
284
-
285
- return str if str.is_a?(::TZInfo::Timezone)
286
-
287
- # discard quickly when it's certainly not a timezone
288
-
289
- return nil if str == nil
290
- return nil if str == '*'
291
-
292
- ostr = str
293
- str = :current if str == :local
294
-
295
- # use Rails' zone by default if Rails is present
296
-
297
- return Time.zone.tzinfo if (
298
- ENV['TZ'].nil? && str == :current &&
299
- Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo)
300
- )
301
-
302
- # ok, it's a timezone then
303
-
304
- str = ENV['TZ'] || Time.now.zone if str == :current
305
-
306
- # utc_offset
307
-
308
- if str.is_a?(Numeric)
309
- i = str.to_i
310
- sn = i < 0 ? '-' : '+'; i = i.abs
311
- hr = i / 3600; mn = i % 3600; sc = i % 60
312
- str = (sc > 0 ? "%s%02d:%02d:%02d" : "%s%02d:%02d") % [ sn, hr, mn, sc ]
313
- end
314
-
315
- return nil if str.index('#')
316
- # counters "sun#2", etc... On OSX would go all the way to true
317
-
318
- # vanilla time zones
319
-
320
- z = (::TZInfo::Timezone.get(str) rescue nil)
321
- return z if z
322
-
323
- # time zone abbreviations
324
-
325
- if str.match(/\A[A-Z0-9-]{3,6}\z/)
326
-
327
- toff = Time.now.utc_offset
328
- toff = nil if str != Time.now.zone
329
-
330
- twin = Time.utc(Time.now.year, 1, 1) # winter
331
- tsum = Time.utc(Time.now.year, 7, 1) # summer
332
-
333
- z =
334
- ::TZInfo::Timezone.all.find do |tz|
335
-
336
- pwin = tz.period_for_utc(twin)
337
- psum = tz.period_for_utc(tsum)
338
-
339
- if toff
340
- (pwin.abbreviation.to_s == str && pwin.utc_offset == toff) ||
341
- (psum.abbreviation.to_s == str && psum.utc_offset == toff)
342
- else
343
- # returns the first tz with the given abbreviation, almost useless
344
- # favour fully named zones...
345
- pwin.abbreviation.to_s == str ||
346
- psum.abbreviation.to_s == str
347
- end
348
- end
349
- return z if z
350
- end
351
-
352
- # some time zone aliases
353
-
354
- return ::TZInfo::Timezone.get('Zulu') if %w[ Z ].include?(str)
355
-
356
- # custom timezones, no DST, just an offset, like "+08:00" or "-01:30"
357
-
358
- tz = (@custom_tz_cache ||= {})[str]
359
- return tz if tz
360
-
361
- if m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])\z/)
362
-
363
- hr = m[1].to_i
364
- mn = m[2].to_i
365
-
366
- hr = nil if hr.abs > 11
367
- hr = nil if mn > 59
368
- mn = -mn if hr && hr < 0
369
-
370
- return (
371
- @custom_tz_cache[str] =
372
- begin
373
- tzi = TZInfo::TransitionDataTimezoneInfo.new(str)
374
- tzi.offset(str, hr * 3600 + mn * 60, 0, str)
375
- tzi.create_timezone
376
- end
377
- ) if hr
378
- end
379
-
380
- # try with ENV['TZ']
381
-
382
- z = ostr == :current && (::TZInfo::Timezone.get(ENV['TZ']) rescue nil)
383
- return z if z
384
-
385
- # ask the system
386
-
387
- z = ostr == :current && (debian_tz || centos_tz || osx_tz)
388
- return z if z
389
-
390
- # so it's not a timezone.
391
-
392
- nil
393
- end
394
-
395
- def self.debian_tz
396
-
397
- path = '/etc/timezone'
398
-
399
- File.exist?(path) &&
400
- (::TZInfo::Timezone.get(File.read(path).strip) rescue nil)
401
- end
402
-
403
- def self.centos_tz
404
-
405
- path = '/etc/sysconfig/clock'
406
-
407
- File.open(path, 'rb') do |f|
408
- until f.eof?
409
- m = f.readline.match(/ZONE="([^"]+)"/)
410
- return (::TZInfo::Timezone.get(m[1]) rescue nil) if m
411
- end
412
- end if File.exist?(path)
413
-
414
- nil
415
- end
416
-
417
- def self.osx_tz
418
-
419
- path = '/etc/localtime'
420
-
421
- return nil unless File.exist?(path)
422
-
423
- ::TZInfo::Timezone.get(
424
- File.readlink(path).split('/')[4..-1].join('/')
425
- ) rescue nil
426
- end
427
-
428
- def self.local_tzone
429
-
430
- get_tzone(:local)
431
- end
432
-
433
- def self.make(o)
434
-
435
- zt =
436
- case o
437
- when Time
438
- ZoTime.new(o.to_f, o.zone)
439
- when Date
440
- t =
441
- o.respond_to?(:to_time) ?
442
- o.to_time :
443
- Time.parse(o.strftime('%Y-%m-%d %H:%M:%S'))
444
- ZoTime.new(t.to_f, t.zone)
445
- when String
446
- Rufus::Scheduler.parse_in(o, :no_error => true) || self.parse(o)
447
- else
448
- o
449
- end
450
-
451
- zt = ZoTime.new(Time.now.to_f + zt, nil) if zt.is_a?(Numeric)
452
-
453
- fail ArgumentError.new(
454
- "cannot turn #{o.inspect} to a ZoTime instance"
455
- ) unless zt.is_a?(ZoTime)
456
-
457
- zt
458
- end
459
-
460
- # def in_zone(&block)
461
- #
462
- # current_timezone = ENV['TZ']
463
- # ENV['TZ'] = @zone
464
- #
465
- # block.call
466
- #
467
- # ensure
468
- #
469
- # ENV['TZ'] = current_timezone
470
- # end
471
-
472
- protected
473
-
474
- def inc(t, dir)
475
-
476
- if t.is_a?(Numeric)
477
- nt = self.dup
478
- nt.seconds += dir * t.to_f
479
- nt
480
- elsif t.respond_to?(:to_f)
481
- @seconds + dir * t.to_f
482
- else
483
- fail ArgumentError.new(
484
- "cannot call ZoTime #- or #+ with arg of class #{t.class}")
485
- end
486
- end
487
-
488
- def _to_f(o)
489
-
490
- fail ArgumentError(
491
- "comparison of ZoTime with #{o.inspect} failed"
492
- ) unless o.is_a?(ZoTime) || o.is_a?(Time)
493
-
494
- o.to_f
495
- end
496
-
497
- def strfz(code)
498
-
499
- return @zone.name if code == '%/Z'
500
-
501
- per = @zone.period_for_utc(utc)
502
-
503
- return per.abbreviation.to_s if code == '%Z'
504
-
505
- off = per.utc_total_offset
506
- #
507
- sn = off < 0 ? '-' : '+'; off = off.abs
508
- hr = off / 3600
509
- mn = (off % 3600) / 60
510
- sc = 0
511
-
512
- fmt =
513
- if code == '%z'
514
- "%s%02d%02d"
515
- elsif code == '%:z'
516
- "%s%02d:%02d"
517
- else
518
- "%s%02d:%02d:%02d"
519
- end
520
-
521
- fmt % [ sn, hr, mn, sc ]
522
- end
523
- end
524
- end