rufus-scheduler 3.3.4 → 3.4.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.
@@ -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
-