rocketjob 3.0.0.rc5 → 3.0.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/rocket_job/cli.rb +3 -0
- data/lib/rocket_job/extensions/aasm.rb +15 -0
- data/lib/rocket_job/plugins/rufus/cron_line.rb +115 -87
- data/lib/rocket_job/plugins/rufus/zo_time.rb +377 -194
- data/lib/rocket_job/plugins/state_machine.rb +4 -0
- data/lib/rocket_job/server.rb +15 -2
- data/lib/rocket_job/version.rb +1 -1
- data/test/plugins/cron_test.rb +2 -2
- data/test/plugins/processing_window_test.rb +5 -5
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 665d894a78de154892a8c53e03f2a2a81f6230f9
|
4
|
+
data.tar.gz: d89dba90f39ef98f7205366b166a45d928fb84db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c275f0ca475518de2acddce890b924312e8e40e5f04f74e2f4c8a235235723c5b1d3a322d0c10dd29d512f3d2f492b6c52d917b8a491185a18a61634ecfde174
|
7
|
+
data.tar.gz: 6f368bdd55d728530252d30d16a34b0b215caaa4cbe1a199b3aa1d369a48044cbd90642e2bec55fa039238897401a5223f67c56bbe7d02d98adea63e44f2b09e
|
data/lib/rocket_job/cli.rb
CHANGED
@@ -32,6 +32,9 @@ module RocketJob
|
|
32
32
|
rails? ? boot_rails : boot_standalone
|
33
33
|
write_pidfile
|
34
34
|
|
35
|
+
# In case Rails did not load the Mongoid Config
|
36
|
+
RocketJob::Config.load!(environment, mongo_config, symmetric_encryption_config) if Mongoid::Config.clients.empty?
|
37
|
+
|
35
38
|
opts = {}
|
36
39
|
opts[:name] = name if name
|
37
40
|
opts[:max_workers] = workers if workers
|
@@ -55,3 +55,18 @@ module AASM
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
# Patch to try and make AASM threadsafe
|
59
|
+
AASM::StateMachineStore
|
60
|
+
module AASM
|
61
|
+
class StateMachineStore
|
62
|
+
@stores = Concurrent::Map.new
|
63
|
+
|
64
|
+
def self.stores
|
65
|
+
@stores
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize
|
69
|
+
@machines = Concurrent::Map.new
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2006-
|
2
|
+
# Copyright (c) 2006-2017, John Mettraux, jmettraux@gmail.com
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -32,9 +32,15 @@ module RocketJob::Plugins::Rufus
|
|
32
32
|
#
|
33
33
|
class CronLine
|
34
34
|
|
35
|
+
# The max number of years in the future or the past before giving up
|
36
|
+
# searching for #next_time or #previous_time respectively
|
37
|
+
#
|
38
|
+
NEXT_TIME_MAX_YEARS = 14
|
39
|
+
|
35
40
|
# The string used for creating this cronline instance.
|
36
41
|
#
|
37
42
|
attr_reader :original
|
43
|
+
attr_reader :original_timezone
|
38
44
|
|
39
45
|
attr_reader :seconds
|
40
46
|
attr_reader :minutes
|
@@ -52,10 +58,15 @@ module RocketJob::Plugins::Rufus
|
|
52
58
|
) unless line.is_a?(String)
|
53
59
|
|
54
60
|
@original = line
|
61
|
+
@original_timezone = nil
|
55
62
|
|
56
63
|
items = line.split
|
57
64
|
|
58
|
-
@timezone =
|
65
|
+
if @timezone = RocketJob::Plugins::Rufus::ZoTime.get_tzone(items.last)
|
66
|
+
@original_timezone = items.pop
|
67
|
+
else
|
68
|
+
@timezone = RocketJob::Plugins::Rufus::ZoTime.get_tzone(:current)
|
69
|
+
end
|
59
70
|
|
60
71
|
fail ArgumentError.new(
|
61
72
|
"not a valid cronline : '#{line}'"
|
@@ -76,18 +87,26 @@ module RocketJob::Plugins::Rufus
|
|
76
87
|
"invalid cronline: '#{line}'"
|
77
88
|
) if es && es.find { |e| ! e.is_a?(Fixnum) }
|
78
89
|
end
|
90
|
+
|
91
|
+
if @days && @days.include?(0) # gh-221
|
92
|
+
|
93
|
+
fail ArgumentError.new('invalid day 0 in cronline')
|
94
|
+
end
|
79
95
|
end
|
80
96
|
|
81
97
|
# Returns true if the given time matches this cron line.
|
82
98
|
#
|
83
99
|
def matches?(time)
|
84
100
|
|
85
|
-
|
101
|
+
# FIXME Don't create a new ZoTime if time is already a ZoTime in same
|
102
|
+
# zone ...
|
103
|
+
# Wait, this seems only used in specs...
|
104
|
+
t = ZoTime.new(time.to_f, @timezone)
|
86
105
|
|
87
|
-
return false unless sub_match?(
|
88
|
-
return false unless sub_match?(
|
89
|
-
return false unless sub_match?(
|
90
|
-
return false unless date_match?(
|
106
|
+
return false unless sub_match?(t, :sec, @seconds)
|
107
|
+
return false unless sub_match?(t, :min, @minutes)
|
108
|
+
return false unless sub_match?(t, :hour, @hours)
|
109
|
+
return false unless date_match?(t)
|
91
110
|
true
|
92
111
|
end
|
93
112
|
|
@@ -118,78 +137,90 @@ module RocketJob::Plugins::Rufus
|
|
118
137
|
#
|
119
138
|
# (Thanks to K Liu for the note and the examples)
|
120
139
|
#
|
121
|
-
def next_time(from=
|
140
|
+
def next_time(from=ZoTime.now)
|
122
141
|
|
123
|
-
|
124
|
-
|
142
|
+
nt = nil
|
143
|
+
zt = ZoTime.new(from.to_i + 1, @timezone)
|
144
|
+
maxy = from.year + NEXT_TIME_MAX_YEARS
|
125
145
|
|
126
146
|
loop do
|
127
147
|
|
128
|
-
|
148
|
+
nt = zt.dup
|
149
|
+
|
150
|
+
fail RangeError.new(
|
151
|
+
"failed to reach occurrence within " +
|
152
|
+
"#{NEXT_TIME_MAX_YEARS} years for '#{original}'"
|
153
|
+
) if nt.year > maxy
|
129
154
|
|
130
|
-
unless date_match?(
|
131
|
-
|
155
|
+
unless date_match?(nt)
|
156
|
+
zt.add((24 - nt.hour) * 3600 - nt.min * 60 - nt.sec)
|
132
157
|
next
|
133
158
|
end
|
134
|
-
unless sub_match?(
|
135
|
-
|
159
|
+
unless sub_match?(nt, :hour, @hours)
|
160
|
+
zt.add((60 - nt.min) * 60 - nt.sec)
|
136
161
|
next
|
137
162
|
end
|
138
|
-
unless sub_match?(
|
139
|
-
|
163
|
+
unless sub_match?(nt, :min, @minutes)
|
164
|
+
zt.add(60 - nt.sec)
|
140
165
|
next
|
141
166
|
end
|
142
|
-
unless sub_match?(
|
143
|
-
|
167
|
+
unless sub_match?(nt, :sec, @seconds)
|
168
|
+
zt.add(next_second(nt))
|
144
169
|
next
|
145
170
|
end
|
146
171
|
|
147
172
|
break
|
148
173
|
end
|
149
174
|
|
150
|
-
|
175
|
+
nt
|
151
176
|
end
|
152
177
|
|
153
178
|
# Returns the previous time the cronline matched. It's like next_time, but
|
154
179
|
# for the past.
|
155
180
|
#
|
156
|
-
def previous_time(from=
|
181
|
+
def previous_time(from=ZoTime.now)
|
157
182
|
|
158
|
-
|
159
|
-
|
183
|
+
pt = nil
|
184
|
+
zt = ZoTime.new(from.to_i - 1, @timezone)
|
185
|
+
miny = from.year - NEXT_TIME_MAX_YEARS
|
160
186
|
|
161
187
|
loop do
|
162
188
|
|
163
|
-
|
189
|
+
pt = zt.dup
|
190
|
+
|
191
|
+
fail RangeError.new(
|
192
|
+
"failed to reach occurrence within " +
|
193
|
+
"#{NEXT_TIME_MAX_YEARS} years for '#{original}'"
|
194
|
+
) if pt.year < miny
|
164
195
|
|
165
|
-
unless date_match?(
|
166
|
-
|
196
|
+
unless date_match?(pt)
|
197
|
+
zt.substract(pt.hour * 3600 + pt.min * 60 + pt.sec + 1)
|
167
198
|
next
|
168
199
|
end
|
169
|
-
unless sub_match?(
|
170
|
-
|
200
|
+
unless sub_match?(pt, :hour, @hours)
|
201
|
+
zt.substract(pt.min * 60 + pt.sec + 1)
|
171
202
|
next
|
172
203
|
end
|
173
|
-
unless sub_match?(
|
174
|
-
|
204
|
+
unless sub_match?(pt, :min, @minutes)
|
205
|
+
zt.substract(pt.sec + 1)
|
175
206
|
next
|
176
207
|
end
|
177
|
-
unless sub_match?(
|
178
|
-
|
208
|
+
unless sub_match?(pt, :sec, @seconds)
|
209
|
+
zt.substract(prev_second(pt))
|
179
210
|
next
|
180
211
|
end
|
181
212
|
|
182
213
|
break
|
183
214
|
end
|
184
215
|
|
185
|
-
|
216
|
+
pt
|
186
217
|
end
|
187
218
|
|
188
219
|
# Returns an array of 6 arrays (seconds, minutes, hours, days,
|
189
220
|
# months, weekdays).
|
190
|
-
# This method is used by the cronline
|
221
|
+
# This method is mostly used by the cronline specs.
|
191
222
|
#
|
192
|
-
def
|
223
|
+
def to_a
|
193
224
|
|
194
225
|
[
|
195
226
|
toa(@seconds),
|
@@ -199,9 +230,10 @@ module RocketJob::Plugins::Rufus
|
|
199
230
|
toa(@months),
|
200
231
|
toa(@weekdays),
|
201
232
|
toa(@monthdays),
|
202
|
-
@timezone
|
233
|
+
@timezone.name
|
203
234
|
]
|
204
235
|
end
|
236
|
+
alias to_array to_a
|
205
237
|
|
206
238
|
# Returns a quickly computed approximation of the frequency for this
|
207
239
|
# cron line.
|
@@ -261,8 +293,10 @@ module RocketJob::Plugins::Rufus
|
|
261
293
|
t1 = next_time(t0)
|
262
294
|
d = t1 - t0
|
263
295
|
delta = d if d < delta
|
264
|
-
|
265
|
-
break if @months
|
296
|
+
break if @months.nil? && t1.month == 2
|
297
|
+
break if @months.nil? && @days.nil? && t1.day == 2
|
298
|
+
break if @months.nil? && @days.nil? && @hours.nil? && t1.hour == 1
|
299
|
+
break if @months.nil? && @days.nil? && @hours.nil? && @minutes.nil? && t1.min == 1
|
266
300
|
break if t1.year >= 2001
|
267
301
|
|
268
302
|
t0 = t1
|
@@ -312,39 +346,38 @@ module RocketJob::Plugins::Rufus
|
|
312
346
|
|
313
347
|
WEEKDAYS = %w[ sun mon tue wed thu fri sat ]
|
314
348
|
DAY_S = 24 * 3600
|
315
|
-
WEEK_S = 7 * DAY_S
|
316
349
|
|
317
350
|
def parse_weekdays(item)
|
318
351
|
|
319
352
|
return nil if item == '*'
|
320
353
|
|
321
|
-
items = item.downcase.split(',')
|
322
|
-
|
323
354
|
weekdays = nil
|
324
355
|
monthdays = nil
|
325
356
|
|
326
|
-
|
357
|
+
item.downcase.split(',').each do |it|
|
327
358
|
|
328
|
-
|
359
|
+
WEEKDAYS.each_with_index { |a, i| it.gsub!(/#{a}/, i.to_s) }
|
360
|
+
|
361
|
+
it = it.gsub(/([^#])l/, '\1#-1')
|
362
|
+
# "5L" == "5#-1" == the last Friday
|
363
|
+
|
364
|
+
if m = it.match(/\A(.+)#(l|-?[12345])\z/)
|
329
365
|
|
330
366
|
fail ArgumentError.new(
|
331
367
|
"ranges are not supported for monthdays (#{it})"
|
332
368
|
) if m[1].index('-')
|
333
369
|
|
334
|
-
|
370
|
+
it = it.gsub(/#l/, '#-1')
|
335
371
|
|
336
|
-
(monthdays ||= []) <<
|
372
|
+
(monthdays ||= []) << it
|
337
373
|
|
338
374
|
else
|
339
375
|
|
340
|
-
expr = it.dup
|
341
|
-
WEEKDAYS.each_with_index { |a, i| expr.gsub!(/#{a}/, i.to_s) }
|
342
|
-
|
343
376
|
fail ArgumentError.new(
|
344
|
-
"invalid weekday expression (#{
|
345
|
-
) if
|
377
|
+
"invalid weekday expression (#{item})"
|
378
|
+
) if it !~ /\A0*[0-7](-0*[0-7])?\z/
|
346
379
|
|
347
|
-
its =
|
380
|
+
its = it.index('-') ? parse_range(it, 0, 7) : [ Integer(it) ]
|
348
381
|
its = its.collect { |i| i == 7 ? 0 : i }
|
349
382
|
|
350
383
|
(weekdays ||= []).concat(its)
|
@@ -371,7 +404,7 @@ module RocketJob::Plugins::Rufus
|
|
371
404
|
Set.new(r)
|
372
405
|
end
|
373
406
|
|
374
|
-
RANGE_REGEX =
|
407
|
+
RANGE_REGEX = /\A(\*|-?\d{1,2})(?:-(-?\d{1,2}))?(?:\/(\d{1,2}))?\z/
|
375
408
|
|
376
409
|
def parse_range(item, min, max)
|
377
410
|
|
@@ -409,6 +442,10 @@ module RocketJob::Plugins::Rufus
|
|
409
442
|
"#{item.inspect} is not in range #{min}..#{max}"
|
410
443
|
) if sta < min || edn > max
|
411
444
|
|
445
|
+
fail ArgumentError.new(
|
446
|
+
"#{item.inspect} increment must be greater than zero"
|
447
|
+
) if inc == 0
|
448
|
+
|
412
449
|
r = []
|
413
450
|
val = sta
|
414
451
|
|
@@ -425,12 +462,14 @@ module RocketJob::Plugins::Rufus
|
|
425
462
|
r.uniq
|
426
463
|
end
|
427
464
|
|
465
|
+
# FIXME: Eventually split into day_match?, hour_match? and monthdays_match?o
|
466
|
+
#
|
428
467
|
def sub_match?(time, accessor, values)
|
429
468
|
|
430
|
-
value = time.send(accessor)
|
431
|
-
|
432
469
|
return true if values.nil?
|
433
470
|
|
471
|
+
value = time.send(accessor)
|
472
|
+
|
434
473
|
if accessor == :day
|
435
474
|
|
436
475
|
values.each do |v|
|
@@ -444,48 +483,37 @@ module RocketJob::Plugins::Rufus
|
|
444
483
|
return true if value == 0 && values.include?(24)
|
445
484
|
end
|
446
485
|
|
447
|
-
|
448
|
-
end
|
449
|
-
|
450
|
-
def monthday_match?(date, values)
|
451
|
-
|
452
|
-
return true if values.nil?
|
486
|
+
if accessor == :monthdays
|
453
487
|
|
454
|
-
|
455
|
-
|
456
|
-
(today_values & values).any?
|
457
|
-
end
|
458
|
-
|
459
|
-
def date_match?(date)
|
488
|
+
return true if (values & value).any?
|
489
|
+
end
|
460
490
|
|
461
|
-
|
462
|
-
return false unless sub_match?(date, :month, @months)
|
463
|
-
return false unless sub_match?(date, :wday, @weekdays)
|
464
|
-
return false unless monthday_match?(date, @monthdays)
|
465
|
-
true
|
491
|
+
values.include?(value)
|
466
492
|
end
|
467
493
|
|
468
|
-
def
|
494
|
+
# def monthday_match?(zt, values)
|
495
|
+
#
|
496
|
+
# return true if values.nil?
|
497
|
+
#
|
498
|
+
# today_values = monthdays(zt)
|
499
|
+
#
|
500
|
+
# (today_values & values).any?
|
501
|
+
# end
|
469
502
|
|
470
|
-
|
471
|
-
d = date.dup
|
503
|
+
def date_match?(zt)
|
472
504
|
|
473
|
-
|
474
|
-
|
475
|
-
break if d.month != date.month
|
476
|
-
pos = pos + 1
|
477
|
-
end
|
505
|
+
return false unless sub_match?(zt, :day, @days)
|
506
|
+
return false unless sub_match?(zt, :month, @months)
|
478
507
|
|
479
|
-
|
480
|
-
|
508
|
+
return true if (
|
509
|
+
(@weekdays && @monthdays) &&
|
510
|
+
(sub_match?(zt, :wday, @weekdays) ||
|
511
|
+
sub_match?(zt, :monthdays, @monthdays)))
|
481
512
|
|
482
|
-
|
483
|
-
|
484
|
-
break if d.month != date.month
|
485
|
-
neg = neg - 1
|
486
|
-
end
|
513
|
+
return false unless sub_match?(zt, :wday, @weekdays)
|
514
|
+
return false unless sub_match?(zt, :monthdays, @monthdays)
|
487
515
|
|
488
|
-
|
516
|
+
true
|
489
517
|
end
|
490
518
|
end
|
491
519
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2006-
|
2
|
+
# Copyright (c) 2006-2017, John Mettraux, jmettraux@gmail.com
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -23,186 +23,79 @@
|
|
23
23
|
#++
|
24
24
|
|
25
25
|
module RocketJob::Plugins::Rufus
|
26
|
-
|
27
|
-
TIMEZONES = %w[
|
28
|
-
GB NZ UCT EET CET PRC ROC WET GMT EST ROK UTC MST HST MET Zulu Cuba Iran W-SU
|
29
|
-
Eire GMT0 Libya Japan Egypt GMT+0 GMT-0 Israel Poland Navajo Turkey GB-Eire
|
30
|
-
Iceland PST8PDT Etc/UCT CST6CDT NZ-CHAT MST7MDT Jamaica EST5EDT Etc/GMT Etc/UTC
|
31
|
-
US/Samoa Etc/GMT0 Portugal Hongkong Etc/Zulu Singapore Asia/Baku Etc/GMT-9
|
32
|
-
Etc/GMT+1 Etc/GMT+0 Asia/Aden Etc/GMT+2 Etc/GMT+3 Etc/GMT+4 Etc/GMT+5 Etc/GMT+6
|
33
|
-
Etc/GMT+7 Etc/GMT+8 Etc/GMT+9 Etc/GMT-0 Etc/GMT-1 Universal Asia/Dili Greenwich
|
34
|
-
Asia/Gaza Etc/GMT-8 Etc/GMT-7 US/Alaska Asia/Oral Etc/GMT-6 Etc/GMT-5 Etc/GMT-4
|
35
|
-
Asia/Hovd Etc/GMT-3 US/Hawaii Etc/GMT-2 Kwajalein Asia/Omsk Asia/Macao
|
36
|
-
Etc/GMT-14 Asia/Kabul US/Central Etc/GMT-13 US/Arizona Asia/Macau Asia/Qatar
|
37
|
-
Asia/Seoul Asia/Tokyo Asia/Dubai US/Pacific Etc/GMT-12 Etc/GMT-11 Etc/GMT-10
|
38
|
-
Asia/Dhaka Asia/Dacca Asia/Chita Etc/GMT+12 Etc/GMT+10 Asia/Amman Asia/Aqtau
|
39
|
-
Etc/GMT+11 US/Eastern Asia/Thimbu Asia/Brunei Asia/Tehran Asia/Beirut
|
40
|
-
Europe/Rome Europe/Riga Brazil/Acre Brazil/East Europe/Oslo Brazil/West
|
41
|
-
Africa/Lome Asia/Taipei Asia/Saigon Asia/Riyadh Asia/Aqtobe Asia/Anadyr
|
42
|
-
Europe/Kiev Asia/Almaty Africa/Juba Pacific/Yap US/Aleutian Asia/Muscat
|
43
|
-
US/Mountain Asia/Harbin Asia/Hebron Asia/Manila Asia/Kuwait Asia/Urumqi
|
44
|
-
US/Michigan Indian/Mahe SystemV/EST5 Asia/Kashgar Indian/Cocos Asia/Jakarta
|
45
|
-
Asia/Kolkata Asia/Kuching America/Atka Asia/Irkutsk Pacific/Apia Asia/Magadan
|
46
|
-
Africa/Dakar America/Lima Pacific/Fiji Pacific/Guam Europe/Vaduz Pacific/Niue
|
47
|
-
Asia/Nicosia Africa/Ceuta Pacific/Truk America/Adak Pacific/Wake Africa/Tunis
|
48
|
-
Africa/Cairo Asia/Colombo SystemV/AST4 SystemV/CST6 Asia/Karachi Asia/Rangoon
|
49
|
-
SystemV/MST7 Asia/Baghdad Europe/Malta Africa/Lagos Europe/Minsk SystemV/PST8
|
50
|
-
Canada/Yukon Asia/Tbilisi America/Nome Asia/Bahrain Africa/Accra Europe/Paris
|
51
|
-
Asia/Bangkok Asia/Bishkek Asia/Thimphu SystemV/YST9 Asia/Yerevan Asia/Yakutsk
|
52
|
-
Europe/Sofia Asia/Ust-Nera Australia/ACT Australia/LHI Europe/Tirane
|
53
|
-
Asia/Tel_Aviv Australia/NSW Africa/Luanda Asia/Tashkent Africa/Lusaka
|
54
|
-
Asia/Shanghai Africa/Malabo Asia/Sakhalin Africa/Maputo Africa/Maseru
|
55
|
-
SystemV/HST10 Africa/Kigali Africa/Niamey Pacific/Samoa America/Sitka
|
56
|
-
Pacific/Palau Pacific/Nauru Pacific/Efate Asia/Makassar Pacific/Chuuk
|
57
|
-
Africa/Harare Africa/Douala America/Aruba America/Thule America/Bahia
|
58
|
-
America/Jujuy America/Belem Asia/Katmandu America/Boise Indian/Comoro
|
59
|
-
Indian/Chagos Asia/Jayapura Europe/Zurich Asia/Istanbul Europe/Zagreb
|
60
|
-
Etc/Greenwich Europe/Warsaw Europe/Vienna Etc/Universal Asia/Dushanbe
|
61
|
-
Europe/Athens Europe/Berlin Africa/Bissau Asia/Damascus Africa/Banjul
|
62
|
-
Europe/Dublin Africa/Bangui Africa/Bamako Europe/Jersey Africa/Asmera
|
63
|
-
Europe/Lisbon Africa/Asmara Europe/London Asia/Ashgabat Asia/Calcutta
|
64
|
-
Europe/Madrid Europe/Monaco Europe/Moscow Europe/Prague Europe/Samara
|
65
|
-
Europe/Skopje Asia/Khandyga Canada/Pacific Africa/Abidjan America/Manaus
|
66
|
-
Asia/Chongqing Asia/Chungking Africa/Algiers America/Maceio US/Pacific-New
|
67
|
-
Africa/Conakry America/La_Paz America/Juneau America/Nassau America/Inuvik
|
68
|
-
Europe/Andorra Africa/Kampala Asia/Ashkhabad Asia/Hong_Kong America/Havana
|
69
|
-
Canada/Eastern Europe/Belfast Canada/Central Australia/West Asia/Jerusalem
|
70
|
-
Africa/Mbabane Asia/Kamchatka America/Virgin America/Guyana Asia/Kathmandu
|
71
|
-
Mexico/General America/Panama Europe/Nicosia America/Denver Europe/Tallinn
|
72
|
-
Africa/Nairobi America/Dawson Europe/Vatican Europe/Vilnius America/Cuiaba
|
73
|
-
Africa/Tripoli Pacific/Wallis Atlantic/Faroe Pacific/Tarawa Pacific/Tahiti
|
74
|
-
Pacific/Saipan Pacific/Ponape America/Cayman America/Cancun Asia/Pontianak
|
75
|
-
Asia/Pyongyang Asia/Vientiane Asia/Qyzylorda Pacific/Noumea America/Bogota
|
76
|
-
Pacific/Midway Pacific/Majuro Asia/Samarkand Indian/Mayotte Pacific/Kosrae
|
77
|
-
Asia/Singapore Indian/Reunion America/Belize America/Regina America/Recife
|
78
|
-
Pacific/Easter Mexico/BajaSur America/Merida Pacific/Chatham Pacific/Fakaofo
|
79
|
-
Pacific/Gambier America/Rosario Asia/Ulan_Bator Indian/Maldives Pacific/Norfolk
|
80
|
-
America/Antigua Asia/Phnom_Penh America/Phoenix America/Caracas America/Cayenne
|
81
|
-
Atlantic/Azores Pacific/Pohnpei Atlantic/Canary America/Chicago Atlantic/Faeroe
|
82
|
-
Africa/Windhoek America/Cordoba America/Creston Africa/Timbuktu America/Curacao
|
83
|
-
Africa/Sao_Tome Africa/Ndjamena SystemV/AST4ADT Europe/Uzhgorod Europe/Tiraspol
|
84
|
-
SystemV/CST6CDT Africa/Monrovia America/Detroit Europe/Sarajevo Australia/Eucla
|
85
|
-
America/Tijuana America/Toronto America/Godthab America/Grenada Europe/Istanbul
|
86
|
-
America/Ojinaga America/Tortola Australia/Perth Europe/Helsinki Australia/South
|
87
|
-
Europe/Guernsey SystemV/EST5EDT Europe/Chisinau SystemV/MST7MDT Europe/Busingen
|
88
|
-
Europe/Budapest Europe/Brussels America/Halifax America/Mendoza America/Noronha
|
89
|
-
America/Nipigon Canada/Atlantic America/Yakutat SystemV/PST8PDT SystemV/YST9YDT
|
90
|
-
Canada/Mountain Africa/Kinshasa Africa/Khartoum Africa/Gaborone Africa/Freetown
|
91
|
-
America/Iqaluit America/Jamaica US/East-Indiana Africa/El_Aaiun America/Knox_IN
|
92
|
-
Africa/Djibouti Africa/Blantyre America/Moncton America/Managua Asia/Choibalsan
|
93
|
-
America/Marigot Australia/North Europe/Belgrade America/Resolute
|
94
|
-
America/Mazatlan Pacific/Funafuti Pacific/Auckland Pacific/Honolulu
|
95
|
-
Pacific/Johnston America/Miquelon America/Santarem Mexico/BajaNorte
|
96
|
-
America/Santiago Antarctica/Troll America/Asuncion America/Atikokan
|
97
|
-
America/Montreal America/Barbados Africa/Bujumbura Pacific/Pitcairn
|
98
|
-
Asia/Ulaanbaatar Indian/Mauritius America/New_York Antarctica/Syowa
|
99
|
-
America/Shiprock Indian/Kerguelen Asia/Novosibirsk America/Anguilla
|
100
|
-
Indian/Christmas Asia/Vladivostok Asia/Ho_Chi_Minh Antarctica/Davis
|
101
|
-
Atlantic/Bermuda Europe/Amsterdam Antarctica/Casey America/St_Johns
|
102
|
-
Atlantic/Madeira America/Winnipeg America/St_Kitts Europe/Volgograd
|
103
|
-
Brazil/DeNoronha Europe/Bucharest Africa/Mogadishu America/St_Lucia
|
104
|
-
Atlantic/Stanley Europe/Stockholm Australia/Currie Europe/Gibraltar
|
105
|
-
Australia/Sydney Asia/Krasnoyarsk Australia/Darwin America/Dominica
|
106
|
-
America/Edmonton America/Eirunepe Europe/Podgorica America/Ensenada
|
107
|
-
Europe/Ljubljana Australia/Hobart Europe/Mariehamn Africa/Lubumbashi
|
108
|
-
America/Goose_Bay Europe/Luxembourg America/Menominee America/Glace_Bay
|
109
|
-
America/Fortaleza Africa/Nouakchott America/Matamoros Pacific/Galapagos
|
110
|
-
America/Guatemala Pacific/Kwajalein Pacific/Marquesas America/Guayaquil
|
111
|
-
Asia/Kuala_Lumpur Europe/San_Marino America/Monterrey Europe/Simferopol
|
112
|
-
America/Araguaina Antarctica/Vostok Europe/Copenhagen America/Catamarca
|
113
|
-
Pacific/Pago_Pago America/Sao_Paulo America/Boa_Vista America/St_Thomas
|
114
|
-
Chile/Continental America/Vancouver Africa/Casablanca Europe/Bratislava
|
115
|
-
Pacific/Enderbury Pacific/Rarotonga Europe/Zaporozhye US/Indiana-Starke
|
116
|
-
Antarctica/Palmer Asia/Novokuznetsk Africa/Libreville America/Chihuahua
|
117
|
-
America/Anchorage Pacific/Tongatapu Antarctica/Mawson Africa/Porto-Novo
|
118
|
-
Asia/Yekaterinburg America/Paramaribo America/Hermosillo Atlantic/Jan_Mayen
|
119
|
-
Antarctica/McMurdo America/Costa_Rica Antarctica/Rothera America/Grand_Turk
|
120
|
-
Atlantic/Reykjavik Atlantic/St_Helena Australia/Victoria Chile/EasterIsland
|
121
|
-
Asia/Ujung_Pandang Australia/Adelaide America/Montserrat America/Porto_Acre
|
122
|
-
Africa/Brazzaville Australia/Brisbane America/Kralendijk America/Montevideo
|
123
|
-
America/St_Vincent America/Louisville Australia/Canberra Australia/Tasmania
|
124
|
-
Europe/Isle_of_Man Europe/Kaliningrad Africa/Ouagadougou America/Rio_Branco
|
125
|
-
Pacific/Kiritimati Africa/Addis_Ababa America/Metlakatla America/Martinique
|
126
|
-
Asia/Srednekolymsk America/Guadeloupe America/Fort_Wayne Australia/Lindeman
|
127
|
-
America/Whitehorse Arctic/Longyearbyen America/Pangnirtung America/Mexico_City
|
128
|
-
America/Los_Angeles America/Rainy_River Atlantic/Cape_Verde Pacific/Guadalcanal
|
129
|
-
Indian/Antananarivo America/El_Salvador Australia/Lord_Howe Africa/Johannesburg
|
130
|
-
America/Tegucigalpa Canada/Saskatchewan America/Thunder_Bay Canada/Newfoundland
|
131
|
-
America/Puerto_Rico America/Yellowknife Australia/Melbourne America/Porto_Velho
|
132
|
-
Australia/Queensland Australia/Yancowinna America/Santa_Isabel
|
133
|
-
America/Blanc-Sablon America/Scoresbysund America/Danmarkshavn
|
134
|
-
Pacific/Port_Moresby Antarctica/Macquarie America/Buenos_Aires
|
135
|
-
Africa/Dar_es_Salaam America/Campo_Grande America/Dawson_Creek
|
136
|
-
America/Indianapolis Pacific/Bougainville America/Rankin_Inlet
|
137
|
-
America/Indiana/Knox America/Lower_Princes America/Coral_Harbour
|
138
|
-
America/St_Barthelemy Australia/Broken_Hill America/Cambridge_Bay
|
139
|
-
America/Indiana/Vevay America/Swift_Current America/Port_of_Spain
|
140
|
-
Antarctica/South_Pole America/Santo_Domingo Atlantic/South_Georgia
|
141
|
-
America/Port-au-Prince America/Bahia_Banderas America/Indiana/Winamac
|
142
|
-
America/Indiana/Marengo America/Argentina/Jujuy America/Argentina/Salta
|
143
|
-
Canada/East-Saskatchewan America/Indiana/Vincennes America/Argentina/Tucuman
|
144
|
-
America/Argentina/Ushuaia Antarctica/DumontDUrville America/Indiana/Tell_City
|
145
|
-
America/Argentina/Mendoza America/Argentina/Cordoba America/Indiana/Petersburg
|
146
|
-
America/Argentina/San_Luis America/Argentina/San_Juan America/Argentina/La_Rioja
|
147
|
-
America/North_Dakota/Center America/Kentucky/Monticello
|
148
|
-
America/North_Dakota/Beulah America/Kentucky/Louisville
|
149
|
-
America/Argentina/Catamarca America/Indiana/Indianapolis
|
150
|
-
America/North_Dakota/New_Salem America/Argentina/Rio_Gallegos
|
151
|
-
America/Argentina/Buenos_Aires America/Argentina/ComodRivadavia
|
152
|
-
]
|
153
|
-
TIMEZONEs = TIMEZONES.collect(&:downcase)
|
154
|
-
|
155
26
|
#
|
156
27
|
# Zon{ing|ed}Time, whatever.
|
157
28
|
#
|
158
29
|
class ZoTime
|
159
30
|
|
160
|
-
|
161
|
-
|
31
|
+
attr_reader :seconds
|
32
|
+
attr_reader :zone
|
162
33
|
|
163
34
|
def initialize(s, zone)
|
164
35
|
|
165
36
|
@seconds = s.to_f
|
166
|
-
@zone = zone
|
167
|
-
end
|
37
|
+
@zone = self.class.get_tzone(zone || :current)
|
168
38
|
|
169
|
-
|
39
|
+
fail ArgumentError.new(
|
40
|
+
"cannot determine timezone from #{zone.inspect}" +
|
41
|
+
" (etz:#{ENV['TZ'].inspect},tnz:#{Time.now.zone.inspect}," +
|
42
|
+
"tzid:#{defined?(TZInfo::Data).inspect})"
|
43
|
+
) unless @zone
|
170
44
|
|
171
|
-
|
45
|
+
@time = nil # cache for #to_time result
|
46
|
+
end
|
172
47
|
|
173
|
-
|
48
|
+
def seconds=(f)
|
174
49
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
# # ambiguous TZ (getting out of DST)
|
179
|
-
#else
|
180
|
-
# t.hour # force t to compute itself
|
181
|
-
#end
|
182
|
-
#
|
183
|
-
# jump out of DST as soon as possible, jumps 1h as seen from UTC
|
50
|
+
@time = nil
|
51
|
+
@seconds = f
|
52
|
+
end
|
184
53
|
|
185
|
-
|
186
|
-
#
|
187
|
-
# stay in DST as long as possible, no jump seen from UTC
|
54
|
+
def zone=(z)
|
188
55
|
|
189
|
-
|
190
|
-
|
56
|
+
@time = nil
|
57
|
+
@zone = self.class.get_tzone(zone || :current)
|
191
58
|
end
|
192
59
|
|
193
60
|
def utc
|
194
61
|
|
195
|
-
|
62
|
+
Time.utc(1970, 1, 1) + @seconds
|
196
63
|
end
|
197
64
|
|
198
|
-
|
65
|
+
# Returns a Ruby Time instance.
|
66
|
+
#
|
67
|
+
# Warning: the timezone of that Time instance will be UTC.
|
68
|
+
#
|
69
|
+
def to_time
|
199
70
|
|
200
|
-
@
|
71
|
+
@time ||= begin; u = utc; @zone.period_for_utc(u).to_local(u); end
|
201
72
|
end
|
202
73
|
|
203
|
-
|
74
|
+
%w[
|
75
|
+
year month day wday hour min sec usec asctime
|
76
|
+
].each do |m|
|
77
|
+
define_method(m) { to_time.send(m) }
|
78
|
+
end
|
79
|
+
def iso8601(fraction_digits=0); to_time.iso8601(fraction_digits); end
|
80
|
+
|
81
|
+
def ==(o)
|
82
|
+
|
83
|
+
o.is_a?(ZoTime) && o.seconds == @seconds && o.zone == @zone
|
84
|
+
end
|
85
|
+
#alias eq? == # FIXME see Object#== (ri)
|
86
|
+
|
87
|
+
def >(o); @seconds > _to_f(o); end
|
88
|
+
def >=(o); @seconds >= _to_f(o); end
|
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
|
+
|
93
|
+
alias getutc utc
|
94
|
+
alias getgm utc
|
204
95
|
|
205
|
-
|
96
|
+
def to_i
|
97
|
+
|
98
|
+
@seconds.to_i
|
206
99
|
end
|
207
100
|
|
208
101
|
def to_f
|
@@ -210,82 +103,372 @@ America/Argentina/Buenos_Aires America/Argentina/ComodRivadavia
|
|
210
103
|
@seconds
|
211
104
|
end
|
212
105
|
|
213
|
-
|
106
|
+
def is_dst?
|
107
|
+
|
108
|
+
@zone.period_for_utc(utc).std_offset != 0
|
109
|
+
end
|
110
|
+
alias isdst is_dst?
|
111
|
+
|
112
|
+
def utc_offset
|
113
|
+
|
114
|
+
#@zone.period_for_utc(utc).utc_offset
|
115
|
+
#@zone.period_for_utc(utc).utc_total_offset
|
116
|
+
#@zone.period_for_utc(utc).std_offset
|
117
|
+
@zone.period_for_utc(utc).utc_offset
|
118
|
+
end
|
119
|
+
|
120
|
+
def strftime(format)
|
121
|
+
|
122
|
+
format = format.gsub(/%(\/?Z|:{0,2}z)/) { |f| strfz(f) }
|
123
|
+
|
124
|
+
to_time.strftime(format)
|
125
|
+
end
|
126
|
+
|
127
|
+
def add(t); @time = nil; @seconds += t.to_f; end
|
128
|
+
def substract(t); @time = nil; @seconds -= t.to_f; end
|
129
|
+
|
130
|
+
def +(t); inc(t, 1); end
|
131
|
+
def -(t); inc(t, -1); end
|
132
|
+
|
133
|
+
WEEK_S = 7 * 24 * 3600
|
134
|
+
|
135
|
+
def monthdays
|
136
|
+
|
137
|
+
date = to_time
|
138
|
+
|
139
|
+
pos = 1
|
140
|
+
d = self.dup
|
141
|
+
|
142
|
+
loop do
|
143
|
+
d.add(-WEEK_S)
|
144
|
+
break if d.month != date.month
|
145
|
+
pos = pos + 1
|
146
|
+
end
|
147
|
+
|
148
|
+
neg = -1
|
149
|
+
d = self.dup
|
150
|
+
|
151
|
+
loop do
|
152
|
+
d.add(WEEK_S)
|
153
|
+
break if d.month != date.month
|
154
|
+
neg = neg - 1
|
155
|
+
end
|
156
|
+
|
157
|
+
[ "#{date.wday}##{pos}", "#{date.wday}##{neg}" ]
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_s
|
214
161
|
|
215
|
-
|
162
|
+
strftime('%Y-%m-%d %H:%M:%S %z')
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_debug_s
|
166
|
+
|
167
|
+
uo = self.utc_offset
|
168
|
+
uos = uo < 0 ? '-' : '+'
|
169
|
+
uo = uo.abs
|
170
|
+
uoh, uom = [ uo / 3600, uo % 3600 ]
|
171
|
+
|
172
|
+
[
|
173
|
+
'zt',
|
174
|
+
self.strftime('%Y-%m-%d %H:%M:%S'),
|
175
|
+
"%s%02d:%02d" % [ uos, uoh, uom ],
|
176
|
+
"dst:#{self.isdst}"
|
177
|
+
].join(' ')
|
178
|
+
end
|
179
|
+
|
180
|
+
# Debug current time by showing local time / delta / utc time
|
181
|
+
# for example: "0120-7(0820)"
|
182
|
+
#
|
183
|
+
def to_utc_comparison_s
|
216
184
|
|
217
|
-
|
185
|
+
per = @zone.period_for_utc(utc)
|
186
|
+
off = per.utc_total_offset
|
187
|
+
|
188
|
+
off = off / 3600
|
189
|
+
off = off >= 0 ? "+#{off}" : off.to_s
|
190
|
+
|
191
|
+
strftime('%H%M') + off + utc.strftime('(%H%M)')
|
192
|
+
end
|
193
|
+
|
194
|
+
def to_time_s
|
195
|
+
|
196
|
+
strftime("%H:%M:%S.#{'%06d' % usec}")
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.now(zone=nil)
|
200
|
+
|
201
|
+
ZoTime.new(Time.now.to_f, zone)
|
202
|
+
end
|
203
|
+
|
204
|
+
# https://en.wikipedia.org/wiki/ISO_8601
|
205
|
+
# Postel's law applies
|
206
|
+
#
|
207
|
+
def self.extract_iso8601_zone(s)
|
208
|
+
|
209
|
+
m = s.match(
|
210
|
+
/[0-2]\d(?::?[0-6]\d(?::?[0-6]\d))?\s*([+-]\d\d(?::?\d\d)?)\s*\z/)
|
211
|
+
return nil unless m
|
212
|
+
|
213
|
+
zs = m[1].split(':')
|
214
|
+
zs << '00' if zs.length < 2
|
215
|
+
|
216
|
+
zh = zs[0].to_i.abs
|
217
|
+
|
218
|
+
return nil if zh > 24
|
219
|
+
return nil if zh == 24 && zs[1].to_i != 0
|
220
|
+
|
221
|
+
zs.join(':')
|
218
222
|
end
|
219
223
|
|
220
224
|
def self.parse(str, opts={})
|
221
225
|
|
222
226
|
if defined?(::Chronic) && t = ::Chronic.parse(str, opts)
|
223
|
-
return ZoTime.new(t,
|
227
|
+
return ZoTime.new(t, nil)
|
224
228
|
end
|
225
229
|
|
230
|
+
#rold = RUBY_VERSION < '1.9.0'
|
231
|
+
#rold = RUBY_VERSION < '2.0.0'
|
232
|
+
|
226
233
|
begin
|
227
234
|
DateTime.parse(str)
|
228
235
|
rescue
|
229
|
-
fail ArgumentError, "no time information in #{
|
230
|
-
end if
|
236
|
+
fail ArgumentError, "no time information in #{str.inspect}"
|
237
|
+
end #if rold
|
238
|
+
#
|
239
|
+
# is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now`
|
231
240
|
|
232
241
|
zone = nil
|
233
242
|
|
234
243
|
s =
|
235
|
-
str.gsub(/\S+/)
|
236
|
-
if
|
237
|
-
zone ||=
|
244
|
+
str.gsub(/\S+/) do |w|
|
245
|
+
if z = get_tzone(w)
|
246
|
+
zone ||= z
|
238
247
|
''
|
239
248
|
else
|
240
|
-
|
249
|
+
w
|
241
250
|
end
|
242
|
-
|
251
|
+
end
|
252
|
+
|
253
|
+
local = Time.parse(s)
|
254
|
+
izone = extract_iso8601_zone(s)
|
255
|
+
|
256
|
+
zone ||=
|
257
|
+
if s.match(/\dZ\b/)
|
258
|
+
get_tzone('Zulu')
|
259
|
+
#elsif rold && izone
|
260
|
+
elsif izone
|
261
|
+
get_tzone(izone)
|
262
|
+
elsif local.zone.nil? && izone
|
263
|
+
get_tzone(local.strftime('%:z'))
|
264
|
+
else
|
265
|
+
get_tzone(:local)
|
266
|
+
end
|
267
|
+
|
268
|
+
secs =
|
269
|
+
#if rold && izone
|
270
|
+
if izone
|
271
|
+
local.to_f
|
272
|
+
else
|
273
|
+
zone.period_for_local(local).to_utc(local).to_f
|
274
|
+
end
|
275
|
+
|
276
|
+
ZoTime.new(secs, zone)
|
277
|
+
end
|
243
278
|
|
244
|
-
|
279
|
+
def self.get_tzone(str)
|
280
|
+
return str if str.is_a?(::TZInfo::Timezone)
|
245
281
|
|
246
|
-
|
247
|
-
zt.in_zone { zt.seconds = Time.parse(s).to_f }
|
282
|
+
# discard quickly when it's certainly not a timezone
|
248
283
|
|
249
|
-
|
250
|
-
|
284
|
+
return nil if str == nil
|
285
|
+
return nil if str == '*'
|
251
286
|
|
252
|
-
|
287
|
+
# ok, it's a timezone then
|
253
288
|
|
254
|
-
|
255
|
-
|
289
|
+
ostr = str
|
290
|
+
str = ENV['TZ'] || Time.now.zone if str == :current || str == :local
|
256
291
|
|
257
|
-
|
258
|
-
# "sun#2", etc... On OSX would go all the way to true
|
292
|
+
# utc_offset
|
259
293
|
|
260
|
-
|
294
|
+
if str.is_a?(Numeric)
|
295
|
+
i = str.to_i
|
296
|
+
sn = i < 0 ? '-' : '+'; i = i.abs
|
297
|
+
hr = i / 3600; mn = i % 3600; sc = i % 60
|
298
|
+
str = (sc > 0 ? "%s%02d:%02d:%02d" : "%s%02d:%02d") % [ sn, hr, mn, sc ]
|
299
|
+
end
|
300
|
+
|
301
|
+
return nil if str.nil? || str.index('#')
|
302
|
+
# counters "sun#2", etc... On OSX would go all the way to true
|
303
|
+
|
304
|
+
# vanilla time zones
|
305
|
+
|
306
|
+
z = (::TZInfo::Timezone.get(str) rescue nil)
|
307
|
+
return z if z
|
308
|
+
|
309
|
+
# time zone abbreviations
|
310
|
+
|
311
|
+
if str.match(/\A[A-Z0-9-]{3,6}\z/)
|
261
312
|
|
262
|
-
|
313
|
+
toff = Time.now.utc_offset
|
314
|
+
toff = nil if str != Time.now.zone
|
315
|
+
|
316
|
+
twin = Time.utc(Time.now.year, 1, 1) # winter
|
317
|
+
tsum = Time.utc(Time.now.year, 7, 1) # summer
|
318
|
+
|
319
|
+
z =
|
320
|
+
::TZInfo::Timezone.all.find do |tz|
|
321
|
+
|
322
|
+
pwin = tz.period_for_utc(twin)
|
323
|
+
psum = tz.period_for_utc(tsum)
|
324
|
+
|
325
|
+
if toff
|
326
|
+
(pwin.abbreviation.to_s == str && pwin.utc_offset == toff) ||
|
327
|
+
(psum.abbreviation.to_s == str && psum.utc_offset == toff)
|
328
|
+
else
|
329
|
+
# returns the first tz with the given abbreviation, almost useless
|
330
|
+
# favour fully named zones...
|
331
|
+
pwin.abbreviation.to_s == str ||
|
332
|
+
psum.abbreviation.to_s == str
|
333
|
+
end
|
334
|
+
end
|
335
|
+
return z if z
|
336
|
+
end
|
263
337
|
|
264
|
-
|
265
|
-
return true if TIMEZONEs.include?(str)
|
338
|
+
# some time zone aliases
|
266
339
|
|
267
|
-
|
340
|
+
return ::TZInfo::Timezone.get('Zulu') if %w[ Z ].include?(str)
|
268
341
|
|
269
|
-
|
270
|
-
return false if t.zone == 'UTC'
|
271
|
-
return false if t.utc_offset == 0 && str.start_with?(t.zone)
|
272
|
-
# 3 common fallbacks...
|
342
|
+
# custom timezones, no DST, just an offset, like "+08:00" or "-01:30"
|
273
343
|
|
274
|
-
|
344
|
+
tz = (@custom_tz_cache ||= {})[str]
|
345
|
+
return tz if tz
|
275
346
|
|
276
|
-
|
347
|
+
if m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])\z/)
|
348
|
+
|
349
|
+
hr = m[1].to_i
|
350
|
+
mn = m[2].to_i
|
351
|
+
|
352
|
+
hr = nil if hr.abs > 11
|
353
|
+
hr = nil if mn > 59
|
354
|
+
mn = -mn if hr && hr < 0
|
355
|
+
|
356
|
+
return (
|
357
|
+
@custom_tz_cache[str] =
|
358
|
+
begin
|
359
|
+
tzi = TZInfo::TransitionDataTimezoneInfo.new(str)
|
360
|
+
tzi.offset(str, hr * 3600 + mn * 60, 0, str)
|
361
|
+
tzi.create_timezone
|
362
|
+
end
|
363
|
+
) if hr
|
364
|
+
end
|
365
|
+
|
366
|
+
# last try with ENV['TZ']
|
367
|
+
|
368
|
+
z =
|
369
|
+
(ostr == :local || ostr == :current) &&
|
370
|
+
(::TZInfo::Timezone.get(ENV['TZ']) rescue nil)
|
371
|
+
return z if z
|
372
|
+
|
373
|
+
# so it's not a timezone.
|
374
|
+
|
375
|
+
nil
|
376
|
+
end
|
377
|
+
|
378
|
+
def self.local_tzone
|
379
|
+
|
380
|
+
get_tzone(:local)
|
277
381
|
end
|
278
382
|
|
279
|
-
def
|
383
|
+
def self.make(o)
|
384
|
+
|
385
|
+
zt =
|
386
|
+
case o
|
387
|
+
when Time
|
388
|
+
ZoTime.new(o.to_f, o.zone)
|
389
|
+
when Date
|
390
|
+
t =
|
391
|
+
o.respond_to?(:to_time) ?
|
392
|
+
o.to_time :
|
393
|
+
Time.parse(o.strftime('%Y-%m-%d %H:%M:%S'))
|
394
|
+
ZoTime.new(t.to_f, t.zone)
|
395
|
+
when String
|
396
|
+
Rufus::Scheduler.parse_in(o, :no_error => true) || self.parse(o)
|
397
|
+
else
|
398
|
+
o
|
399
|
+
end
|
400
|
+
|
401
|
+
zt = ZoTime.new(Time.now.to_f + zt, nil) if zt.is_a?(Numeric)
|
402
|
+
|
403
|
+
fail ArgumentError.new(
|
404
|
+
"cannot turn #{o.inspect} to a ZoTime instance"
|
405
|
+
) unless zt.is_a?(ZoTime)
|
406
|
+
|
407
|
+
zt
|
408
|
+
end
|
409
|
+
|
410
|
+
# def in_zone(&block)
|
411
|
+
#
|
412
|
+
# current_timezone = ENV['TZ']
|
413
|
+
# ENV['TZ'] = @zone
|
414
|
+
#
|
415
|
+
# block.call
|
416
|
+
#
|
417
|
+
# ensure
|
418
|
+
#
|
419
|
+
# ENV['TZ'] = current_timezone
|
420
|
+
# end
|
421
|
+
|
422
|
+
protected
|
423
|
+
|
424
|
+
def inc(t, dir)
|
425
|
+
|
426
|
+
if t.is_a?(Numeric)
|
427
|
+
nt = self.dup
|
428
|
+
nt.seconds += dir * t.to_f
|
429
|
+
nt
|
430
|
+
elsif t.respond_to?(:to_f)
|
431
|
+
@seconds + dir * t.to_f
|
432
|
+
else
|
433
|
+
fail ArgumentError.new(
|
434
|
+
"cannot call ZoTime #- or #+ with arg of class #{t.class}")
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def _to_f(o)
|
439
|
+
|
440
|
+
fail ArgumentError(
|
441
|
+
"comparison of ZoTime with #{o.inspect} failed"
|
442
|
+
) unless o.is_a?(ZoTime) || o.is_a?(Time)
|
443
|
+
|
444
|
+
o.to_f
|
445
|
+
end
|
446
|
+
|
447
|
+
def strfz(code)
|
448
|
+
|
449
|
+
return @zone.name if code == '%/Z'
|
450
|
+
|
451
|
+
per = @zone.period_for_utc(utc)
|
280
452
|
|
281
|
-
|
282
|
-
ENV['TZ'] = @zone
|
453
|
+
return per.abbreviation.to_s if code == '%Z'
|
283
454
|
|
284
|
-
|
455
|
+
off = per.utc_total_offset
|
456
|
+
#
|
457
|
+
sn = off < 0 ? '-' : '+'; off = off.abs
|
458
|
+
hr = off / 3600
|
459
|
+
mn = (off % 3600) / 60
|
460
|
+
sc = 0
|
285
461
|
|
286
|
-
|
462
|
+
fmt =
|
463
|
+
if code == '%z'
|
464
|
+
"%s%02d%02d"
|
465
|
+
elsif code == '%:z'
|
466
|
+
"%s%02d:%02d"
|
467
|
+
else
|
468
|
+
"%s%02d:%02d:%02d"
|
469
|
+
end
|
287
470
|
|
288
|
-
|
471
|
+
fmt % [ sn, hr, mn, sc ]
|
289
472
|
end
|
290
473
|
end
|
291
474
|
end
|
@@ -31,6 +31,10 @@ module RocketJob
|
|
31
31
|
|
32
32
|
included do
|
33
33
|
include AASM
|
34
|
+
# Try to make aasm lookup thread safe
|
35
|
+
@aasm = Concurrent::Map.new
|
36
|
+
# Call aasm to create default instance upfront
|
37
|
+
aasm
|
34
38
|
|
35
39
|
# Adds a :before or :after callback to an event
|
36
40
|
# state_machine_add_event_callback(:start, :before, :my_method)
|
data/lib/rocket_job/server.rb
CHANGED
@@ -202,6 +202,19 @@ module RocketJob
|
|
202
202
|
self.class.shutdown? || !running?
|
203
203
|
end
|
204
204
|
|
205
|
+
# Scope for all zombie servers
|
206
|
+
def self.zombies(missed = 4)
|
207
|
+
dead_seconds = Config.instance.heartbeat_seconds * missed
|
208
|
+
last_heartbeat_time = Time.now - dead_seconds
|
209
|
+
where(
|
210
|
+
:state.in => [:stopping, :running, :paused],
|
211
|
+
'$or' => [
|
212
|
+
{"heartbeat.updated_at" => {'$exists' => false}},
|
213
|
+
{"heartbeat.updated_at" => {'$lte' => last_heartbeat_time}}
|
214
|
+
]
|
215
|
+
)
|
216
|
+
end
|
217
|
+
|
205
218
|
# Returns [true|false] if this server has missed at least the last 4 heartbeats
|
206
219
|
#
|
207
220
|
# Possible causes for a server to miss its heartbeats:
|
@@ -209,7 +222,7 @@ module RocketJob
|
|
209
222
|
# - The server process is "hanging"
|
210
223
|
# - The server is no longer able to communicate with the MongoDB Server
|
211
224
|
def zombie?(missed = 4)
|
212
|
-
return false unless running? || stopping?
|
225
|
+
return false unless running? || stopping? || paused?
|
213
226
|
return true if heartbeat.nil? || heartbeat.updated_at.nil?
|
214
227
|
dead_seconds = Config.instance.heartbeat_seconds * missed
|
215
228
|
(Time.now - heartbeat.updated_at) >= dead_seconds
|
@@ -258,7 +271,7 @@ module RocketJob
|
|
258
271
|
ensure
|
259
272
|
# Logs the backtrace for each running worker
|
260
273
|
if SemanticLogger::VERSION.to_i >= 4
|
261
|
-
workers.each { |
|
274
|
+
workers.each { |worker| logger.backtrace(thread: worker.thread) }
|
262
275
|
end
|
263
276
|
end
|
264
277
|
|
data/lib/rocket_job/version.rb
CHANGED
data/test/plugins/cron_test.rb
CHANGED
@@ -32,7 +32,7 @@ module Plugins
|
|
32
32
|
|
33
33
|
describe 'timezones are supported' do
|
34
34
|
it 'handles UTC' do
|
35
|
-
time = Time.parse('2015-12-09 17:50:05
|
35
|
+
time = Time.parse('2015-12-09 17:50:05 UTC')
|
36
36
|
Time.stub(:now, time) do
|
37
37
|
@job = CronJob.create!(cron_schedule: '* 1 * * * UTC')
|
38
38
|
end
|
@@ -42,7 +42,7 @@ module Plugins
|
|
42
42
|
end
|
43
43
|
|
44
44
|
it 'handles Eastern' do
|
45
|
-
time = Time.parse('2015-12-09 17:50:05
|
45
|
+
time = Time.parse('2015-12-09 17:50:05 UTC')
|
46
46
|
Time.stub(:now, time) do
|
47
47
|
@job = CronJob.create!(cron_schedule: '* 1 * * * America/New_York')
|
48
48
|
end
|
@@ -32,7 +32,7 @@ module Plugins
|
|
32
32
|
|
33
33
|
describe 'timezones are supported' do
|
34
34
|
it 'handles UTC' do
|
35
|
-
time = Time.parse('2015-12-09 17:50:05
|
35
|
+
time = Time.parse('2015-12-09 17:50:05 UTC')
|
36
36
|
Time.stub(:now, time) do
|
37
37
|
@job = ProcessingWindowJob.create!(processing_schedule: '* 1 * * * UTC', processing_duration: 1.hour)
|
38
38
|
end
|
@@ -42,7 +42,7 @@ module Plugins
|
|
42
42
|
end
|
43
43
|
|
44
44
|
it 'handles Eastern' do
|
45
|
-
time = Time.parse('2015-12-09 17:50:05
|
45
|
+
time = Time.parse('2015-12-09 17:50:05 UTC')
|
46
46
|
Time.stub(:now, time) do
|
47
47
|
@job = ProcessingWindowJob.create!(processing_schedule: '* 1 * * * America/New_York', processing_duration: 1.hour)
|
48
48
|
end
|
@@ -55,7 +55,7 @@ module Plugins
|
|
55
55
|
|
56
56
|
describe '#rocket_job_processing_window_active?' do
|
57
57
|
it 'returns true when in the processing window' do
|
58
|
-
time = Time.parse('2015-12-09 17:50:05
|
58
|
+
time = Time.parse('2015-12-09 17:50:05 UTC')
|
59
59
|
@job = ProcessingWindowJob.new(processing_schedule: '* 17 * * * UTC', processing_duration: 1.hour)
|
60
60
|
result = Time.stub(:now, time) do
|
61
61
|
@job.rocket_job_processing_window_active?
|
@@ -64,7 +64,7 @@ module Plugins
|
|
64
64
|
end
|
65
65
|
|
66
66
|
it 'returns false when not in the processing window' do
|
67
|
-
time = Time.parse('2015-12-09 16:50:05
|
67
|
+
time = Time.parse('2015-12-09 16:50:05 UTC')
|
68
68
|
@job = ProcessingWindowJob.new(processing_schedule: '* 17 * * * UTC', processing_duration: 1.hour)
|
69
69
|
result = Time.stub(:now, time) do
|
70
70
|
@job.rocket_job_processing_window_active?
|
@@ -96,7 +96,7 @@ module Plugins
|
|
96
96
|
|
97
97
|
describe 're-queue' do
|
98
98
|
it 'if outside processing window' do
|
99
|
-
time = Time.parse('2015-12-09 16:50:05
|
99
|
+
time = Time.parse('2015-12-09 16:50:05 UTC')
|
100
100
|
Time.stub(:now, time) do
|
101
101
|
@job = ProcessingWindowJob.new(processing_schedule: '* 17 * * * UTC', processing_duration: 1.hour)
|
102
102
|
@job.perform_now
|
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: 3.0.0
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -165,9 +165,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
165
165
|
version: '2.1'
|
166
166
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
167
167
|
requirements:
|
168
|
-
- - "
|
168
|
+
- - ">="
|
169
169
|
- !ruby/object:Gem::Version
|
170
|
-
version:
|
170
|
+
version: '0'
|
171
171
|
requirements: []
|
172
172
|
rubyforge_project:
|
173
173
|
rubygems_version: 2.6.8
|