rufus-scheduler 3.3.4 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,488 +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
-
25
-
26
- class Rufus::Scheduler
27
-
28
- #
29
- # Zon{ing|ed}Time, whatever.
30
- #
31
- class ZoTime
32
-
33
- attr_reader :seconds
34
- attr_reader :zone
35
-
36
- def initialize(s, zone)
37
-
38
- @seconds = s.to_f
39
- @zone = self.class.get_tzone(zone || :current)
40
-
41
- fail ArgumentError.new(
42
- "cannot determine timezone from #{zone.inspect}" +
43
- " (etz:#{ENV['TZ'].inspect},tnz:#{Time.now.zone.inspect}," +
44
- "tzid:#{defined?(TZInfo::Data).inspect})\n" +
45
- "Try setting `ENV['TZ'] = 'Continent/City'` in your script " +
46
- "(see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)" +
47
- (defined?(TZInfo::Data) ? '' : " and adding 'tzinfo-data' to your gems")
48
- ) unless @zone
49
-
50
- @time = nil # cache for #to_time result
51
- end
52
-
53
- def seconds=(f)
54
-
55
- @time = nil
56
- @seconds = f
57
- end
58
-
59
- def zone=(z)
60
-
61
- @time = nil
62
- @zone = self.class.get_tzone(zone || :current)
63
- end
64
-
65
- def utc
66
-
67
- Time.utc(1970, 1, 1) + @seconds
68
- end
69
-
70
- # Returns a Ruby Time instance.
71
- #
72
- # Warning: the timezone of that Time instance will be UTC.
73
- #
74
- def to_time
75
-
76
- @time ||= begin; u = utc; @zone.period_for_utc(u).to_local(u); end
77
- end
78
-
79
- %w[
80
- year month day wday hour min sec usec asctime
81
- ].each do |m|
82
- define_method(m) { to_time.send(m) }
83
- end
84
- def iso8601(fraction_digits=0); to_time.iso8601(fraction_digits); end
85
-
86
- def ==(o)
87
-
88
- o.is_a?(ZoTime) && o.seconds == @seconds && o.zone == @zone
89
- end
90
- #alias eq? == # FIXME see Object#== (ri)
91
-
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
- def <=>(o); @seconds <=> _to_f(o); end
97
-
98
- alias getutc utc
99
- alias getgm utc
100
-
101
- def to_i
102
-
103
- @seconds.to_i
104
- end
105
-
106
- def to_f
107
-
108
- @seconds
109
- end
110
-
111
- def is_dst?
112
-
113
- @zone.period_for_utc(utc).std_offset != 0
114
- end
115
- alias isdst is_dst?
116
-
117
- def utc_offset
118
-
119
- #@zone.period_for_utc(utc).utc_offset
120
- #@zone.period_for_utc(utc).utc_total_offset
121
- #@zone.period_for_utc(utc).std_offset
122
- @zone.period_for_utc(utc).utc_offset
123
- end
124
-
125
- def strftime(format)
126
-
127
- format = format.gsub(/%(\/?Z|:{0,2}z)/) { |f| strfz(f) }
128
-
129
- to_time.strftime(format)
130
- end
131
-
132
- def add(t); @time = nil; @seconds += t.to_f; end
133
- def substract(t); @time = nil; @seconds -= t.to_f; end
134
-
135
- def +(t); inc(t, 1); end
136
- def -(t); inc(t, -1); end
137
-
138
- WEEK_S = 7 * 24 * 3600
139
-
140
- def monthdays
141
-
142
- date = to_time
143
-
144
- pos = 1
145
- d = self.dup
146
-
147
- loop do
148
- d.add(-WEEK_S)
149
- break if d.month != date.month
150
- pos = pos + 1
151
- end
152
-
153
- neg = -1
154
- d = self.dup
155
-
156
- loop do
157
- d.add(WEEK_S)
158
- break if d.month != date.month
159
- neg = neg - 1
160
- end
161
-
162
- [ "#{date.wday}##{pos}", "#{date.wday}##{neg}" ]
163
- end
164
-
165
- def to_s
166
-
167
- strftime('%Y-%m-%d %H:%M:%S %z')
168
- end
169
-
170
- def to_debug_s
171
-
172
- uo = self.utc_offset
173
- uos = uo < 0 ? '-' : '+'
174
- uo = uo.abs
175
- uoh, uom = [ uo / 3600, uo % 3600 ]
176
-
177
- [
178
- 'zt',
179
- self.strftime('%Y-%m-%d %H:%M:%S'),
180
- "%s%02d:%02d" % [ uos, uoh, uom ],
181
- "dst:#{self.isdst}"
182
- ].join(' ')
183
- end
184
-
185
- # Debug current time by showing local time / delta / utc time
186
- # for example: "0120-7(0820)"
187
- #
188
- def to_utc_comparison_s
189
-
190
- per = @zone.period_for_utc(utc)
191
- off = per.utc_total_offset
192
-
193
- off = off / 3600
194
- off = off >= 0 ? "+#{off}" : off.to_s
195
-
196
- strftime('%H%M') + off + utc.strftime('(%H%M)')
197
- end
198
-
199
- def to_time_s
200
-
201
- strftime("%H:%M:%S.#{'%06d' % usec}")
202
- end
203
-
204
- def self.now(zone=nil)
205
-
206
- ZoTime.new(Time.now.to_f, zone)
207
- end
208
-
209
- # https://en.wikipedia.org/wiki/ISO_8601
210
- # Postel's law applies
211
- #
212
- def self.extract_iso8601_zone(s)
213
-
214
- m = s.match(
215
- /[0-2]\d(?::?[0-6]\d(?::?[0-6]\d))?\s*([+-]\d\d(?::?\d\d)?)\s*\z/)
216
- return nil unless m
217
-
218
- zs = m[1].split(':')
219
- zs << '00' if zs.length < 2
220
-
221
- zh = zs[0].to_i.abs
222
-
223
- return nil if zh > 24
224
- return nil if zh == 24 && zs[1].to_i != 0
225
-
226
- zs.join(':')
227
- end
228
-
229
- def self.parse(str, opts={})
230
-
231
- if defined?(::Chronic) && t = ::Chronic.parse(str, opts)
232
- return ZoTime.new(t, nil)
233
- end
234
-
235
- #rold = RUBY_VERSION < '1.9.0'
236
- #rold = RUBY_VERSION < '2.0.0'
237
-
238
- begin
239
- DateTime.parse(str)
240
- rescue
241
- fail ArgumentError, "no time information in #{str.inspect}"
242
- end #if rold
243
- #
244
- # is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now`
245
-
246
- zone = nil
247
-
248
- s =
249
- str.gsub(/\S+/) do |w|
250
- if z = get_tzone(w)
251
- zone ||= z
252
- ''
253
- else
254
- w
255
- end
256
- end
257
-
258
- local = Time.parse(s)
259
- izone = extract_iso8601_zone(s)
260
-
261
- zone ||=
262
- if s.match(/\dZ\b/)
263
- get_tzone('Zulu')
264
- #elsif rold && izone
265
- elsif izone
266
- get_tzone(izone)
267
- elsif local.zone.nil? && izone
268
- get_tzone(local.strftime('%:z'))
269
- else
270
- get_tzone(:local)
271
- end
272
-
273
- secs =
274
- #if rold && izone
275
- if izone
276
- local.to_f
277
- else
278
- zone.period_for_local(local).to_utc(local).to_f
279
- end
280
-
281
- ZoTime.new(secs, zone)
282
- end
283
-
284
- def self.get_tzone(str)
285
-
286
- return str if str.is_a?(::TZInfo::Timezone)
287
-
288
- # discard quickly when it's certainly not a timezone
289
-
290
- return nil if str == nil
291
- return nil if str == '*'
292
-
293
- ostr = str
294
- str = :current if str == :local
295
-
296
- # use Rails' zone by default if Rails is present
297
-
298
- return Time.zone.tzinfo if (
299
- ENV['TZ'].nil? && str == :current &&
300
- Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo)
301
- )
302
-
303
- # ok, it's a timezone then
304
-
305
- str = ENV['TZ'] || Time.now.zone if str == :current
306
-
307
- # utc_offset
308
-
309
- if str.is_a?(Numeric)
310
- i = str.to_i
311
- sn = i < 0 ? '-' : '+'; i = i.abs
312
- hr = i / 3600; mn = i % 3600; sc = i % 60
313
- str = (sc > 0 ? "%s%02d:%02d:%02d" : "%s%02d:%02d") % [ sn, hr, mn, sc ]
314
- end
315
-
316
- return nil if str.index('#')
317
- # counters "sun#2", etc... On OSX would go all the way to true
318
-
319
- # vanilla time zones
320
-
321
- z = (::TZInfo::Timezone.get(str) rescue nil)
322
- return z if z
323
-
324
- # time zone abbreviations
325
-
326
- if str.match(/\A[A-Z0-9-]{3,6}\z/)
327
-
328
- toff = Time.now.utc_offset
329
- toff = nil if str != Time.now.zone
330
-
331
- twin = Time.utc(Time.now.year, 1, 1) # winter
332
- tsum = Time.utc(Time.now.year, 7, 1) # summer
333
-
334
- z =
335
- ::TZInfo::Timezone.all.find do |tz|
336
-
337
- pwin = tz.period_for_utc(twin)
338
- psum = tz.period_for_utc(tsum)
339
-
340
- if toff
341
- (pwin.abbreviation.to_s == str && pwin.utc_offset == toff) ||
342
- (psum.abbreviation.to_s == str && psum.utc_offset == toff)
343
- else
344
- # returns the first tz with the given abbreviation, almost useless
345
- # favour fully named zones...
346
- pwin.abbreviation.to_s == str ||
347
- psum.abbreviation.to_s == str
348
- end
349
- end
350
- return z if z
351
- end
352
-
353
- # some time zone aliases
354
-
355
- return ::TZInfo::Timezone.get('Zulu') if %w[ Z ].include?(str)
356
-
357
- # custom timezones, no DST, just an offset, like "+08:00" or "-01:30"
358
-
359
- tz = (@custom_tz_cache ||= {})[str]
360
- return tz if tz
361
-
362
- if m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])\z/)
363
-
364
- hr = m[1].to_i
365
- mn = m[2].to_i
366
-
367
- hr = nil if hr.abs > 11
368
- hr = nil if mn > 59
369
- mn = -mn if hr && hr < 0
370
-
371
- return (
372
- @custom_tz_cache[str] =
373
- begin
374
- tzi = TZInfo::TransitionDataTimezoneInfo.new(str)
375
- tzi.offset(str, hr * 3600 + mn * 60, 0, str)
376
- tzi.create_timezone
377
- end
378
- ) if hr
379
- end
380
-
381
- # last try with ENV['TZ']
382
-
383
- z = ostr == :current && (::TZInfo::Timezone.get(ENV['TZ']) rescue nil)
384
- return z if z
385
-
386
- # so it's not a timezone.
387
-
388
- nil
389
- end
390
-
391
- def self.local_tzone
392
-
393
- get_tzone(:local)
394
- end
395
-
396
- def self.make(o)
397
-
398
- zt =
399
- case o
400
- when Time
401
- ZoTime.new(o.to_f, o.zone)
402
- when Date
403
- t =
404
- o.respond_to?(:to_time) ?
405
- o.to_time :
406
- Time.parse(o.strftime('%Y-%m-%d %H:%M:%S'))
407
- ZoTime.new(t.to_f, t.zone)
408
- when String
409
- Rufus::Scheduler.parse_in(o, :no_error => true) || self.parse(o)
410
- else
411
- o
412
- end
413
-
414
- zt = ZoTime.new(Time.now.to_f + zt, nil) if zt.is_a?(Numeric)
415
-
416
- fail ArgumentError.new(
417
- "cannot turn #{o.inspect} to a ZoTime instance"
418
- ) unless zt.is_a?(ZoTime)
419
-
420
- zt
421
- end
422
-
423
- # def in_zone(&block)
424
- #
425
- # current_timezone = ENV['TZ']
426
- # ENV['TZ'] = @zone
427
- #
428
- # block.call
429
- #
430
- # ensure
431
- #
432
- # ENV['TZ'] = current_timezone
433
- # end
434
-
435
- protected
436
-
437
- def inc(t, dir)
438
-
439
- if t.is_a?(Numeric)
440
- nt = self.dup
441
- nt.seconds += dir * t.to_f
442
- nt
443
- elsif t.respond_to?(:to_f)
444
- @seconds + dir * t.to_f
445
- else
446
- fail ArgumentError.new(
447
- "cannot call ZoTime #- or #+ with arg of class #{t.class}")
448
- end
449
- end
450
-
451
- def _to_f(o)
452
-
453
- fail ArgumentError(
454
- "comparison of ZoTime with #{o.inspect} failed"
455
- ) unless o.is_a?(ZoTime) || o.is_a?(Time)
456
-
457
- o.to_f
458
- end
459
-
460
- def strfz(code)
461
-
462
- return @zone.name if code == '%/Z'
463
-
464
- per = @zone.period_for_utc(utc)
465
-
466
- return per.abbreviation.to_s if code == '%Z'
467
-
468
- off = per.utc_total_offset
469
- #
470
- sn = off < 0 ? '-' : '+'; off = off.abs
471
- hr = off / 3600
472
- mn = (off % 3600) / 60
473
- sc = 0
474
-
475
- fmt =
476
- if code == '%z'
477
- "%s%02d%02d"
478
- elsif code == '%:z'
479
- "%s%02d:%02d"
480
- else
481
- "%s%02d:%02d:%02d"
482
- end
483
-
484
- fmt % [ sn, hr, mn, sc ]
485
- end
486
- end
487
- end
488
-