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