rufus-scheduler 3.2.2 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -86,7 +86,7 @@ module Rufus
86
86
  nil
87
87
  end
88
88
 
89
- @scheduled_at = Time.now
89
+ @scheduled_at = Rufus::Scheduler::ZoTime.now
90
90
  @unscheduled_at = nil
91
91
  @last_time = nil
92
92
 
@@ -124,27 +124,22 @@ module Rufus
124
124
  @previous_time = @next_time
125
125
  set_next_time(time)
126
126
 
127
- return if (
128
- opts[:overlap] == false &&
129
- running?
130
- )
131
- return if (
132
- callback(:confirm_lock, time) &&
133
- callback(:on_pre_trigger, time)
134
- ) == false
127
+ do_trigger(time)
128
+ end
135
129
 
136
- @count += 1
130
+ # Trigger the job right now, off of its schedule.
131
+ #
132
+ # Done in collaboration with Piavka in
133
+ # https://github.com/jmettraux/rufus-scheduler/issues/214
134
+ #
135
+ def trigger_off_schedule(time=Rufus::Scheduler::ZoTime.now)
137
136
 
138
- if opts[:blocking]
139
- do_trigger(time)
140
- else
141
- do_trigger_in_thread(time)
142
- end
137
+ do_trigger(time)
143
138
  end
144
139
 
145
140
  def unschedule
146
141
 
147
- @unscheduled_at = Time.now
142
+ @unscheduled_at = Rufus::Scheduler::ZoTime.now
148
143
  end
149
144
 
150
145
  def threads
@@ -204,7 +199,7 @@ module Rufus
204
199
  #
205
200
  def call(do_rescue=false)
206
201
 
207
- do_call(Time.now, do_rescue)
202
+ do_call(Rufus::Scheduler::ZoTime.now, do_rescue)
208
203
  end
209
204
 
210
205
  protected
@@ -251,7 +246,27 @@ module Rufus
251
246
 
252
247
  def do_trigger(time)
253
248
 
254
- t = Time.now
249
+ return if (
250
+ opts[:overlap] == false &&
251
+ running?
252
+ )
253
+ return if (
254
+ callback(:confirm_lock, time) &&
255
+ callback(:on_pre_trigger, time)
256
+ ) == false
257
+
258
+ @count += 1
259
+
260
+ if opts[:blocking]
261
+ trigger_now(time)
262
+ else
263
+ trigger_queue(time)
264
+ end
265
+ end
266
+
267
+ def trigger_now(time)
268
+
269
+ t = Rufus::Scheduler::ZoTime.now
255
270
  # if there are mutexes, t might be really bigger than time
256
271
 
257
272
  Thread.current[:rufus_scheduler_job] = self
@@ -265,7 +280,7 @@ module Rufus
265
280
  ensure
266
281
 
267
282
  @last_work_time =
268
- Time.now - Thread.current[:rufus_scheduler_time]
283
+ Rufus::Scheduler::ZoTime.now - Thread.current[:rufus_scheduler_time]
269
284
  @mean_work_time =
270
285
  ((@count - 1) * @mean_work_time + @last_work_time) / @count
271
286
 
@@ -302,7 +317,7 @@ module Rufus
302
317
  begin
303
318
 
304
319
  (job.opts[:mutex] || []).reduce(
305
- lambda { job.do_trigger(time) }
320
+ lambda { job.trigger_now(time) }
306
321
  ) do |b, m|
307
322
  lambda { mutex(m).synchronize { b.call } }
308
323
  end.call
@@ -321,9 +336,11 @@ module Rufus
321
336
  # but since it has to be done as quickly as possible.
322
337
  # So, whoever is running first (scheduler thread vs job thread)
323
338
  # sets this information
339
+
340
+ thread
324
341
  end
325
342
 
326
- def do_trigger_in_thread(time)
343
+ def trigger_queue(time)
327
344
 
328
345
  threads = @scheduler.work_threads
329
346
 
@@ -423,12 +440,12 @@ module Rufus
423
440
 
424
441
  return (@first_at = nil) if first == nil
425
442
 
426
- n0 = Time.now
443
+ n0 = Rufus::Scheduler::ZoTime.now
427
444
  n1 = n0 + 0.003
428
445
 
429
446
  first = n0 if first == :now || first == :immediately || first == 0
430
447
 
431
- @first_at = Rufus::Scheduler.parse_to_time(first)
448
+ @first_at = ZoTime.make(first)
432
449
  @first_at = n1 if @first_at >= n0 && @first_at < n1
433
450
 
434
451
  fail ArgumentError.new(
@@ -441,12 +458,13 @@ module Rufus
441
458
 
442
459
  def last_at=(last)
443
460
 
444
- @last_at = last ? Rufus::Scheduler.parse_to_time(last) : nil
461
+ #@last_at = last ? Rufus::Scheduler.parse_to_time(last) : nil
462
+ @last_at = last ? ZoTime.make(last) : nil
445
463
 
446
464
  fail ArgumentError.new(
447
465
  "cannot set last[_at|_in] in the past: " +
448
466
  "#{last.inspect} -> #{@last_at.inspect}"
449
- ) if last && @last_at < Time.now
467
+ ) if last && @last_at < Rufus::Scheduler::ZoTime.now
450
468
 
451
469
  @last_at
452
470
  end
@@ -458,7 +476,7 @@ module Rufus
458
476
  return (@next_time = nil) if @times && @times < 1
459
477
  return (@next_time = nil) if @last_at && time >= @last_at
460
478
  #
461
- # TODO: rework that, jobs are thus kept 1 step too much in @jobs
479
+ # It keeps jobs one step too much in @jobs, but it's OK
462
480
 
463
481
  super
464
482
 
@@ -467,7 +485,7 @@ module Rufus
467
485
 
468
486
  def pause
469
487
 
470
- @paused_at = Time.now
488
+ @paused_at = Rufus::Scheduler::ZoTime.now
471
489
  end
472
490
 
473
491
  def resume
@@ -518,9 +536,7 @@ module Rufus
518
536
 
519
537
  def first_at=(first)
520
538
 
521
- super
522
-
523
- @next_time = @first_at
539
+ @next_time = super
524
540
  end
525
541
  end
526
542
 
@@ -548,10 +564,10 @@ module Rufus
548
564
 
549
565
  return if is_post
550
566
 
551
- n = Time.now
567
+ n = Rufus::Scheduler::ZoTime.now
552
568
 
553
569
  @next_time =
554
- if @first_at == nil || @first_at < n
570
+ if @first_at == nil || @first_at < (n - @scheduler.frequency)
555
571
  nt = (@next_time || trigger_time || n) + @frequency
556
572
  nt > n ? nt : (trigger_time || n) + @frequency
557
573
  else
@@ -589,10 +605,10 @@ module Rufus
589
605
 
590
606
  @next_time =
591
607
  if is_post
592
- Time.now + @interval
608
+ Rufus::Scheduler::ZoTime.now + @interval
593
609
  elsif trigger_time.nil?
594
610
  if @first_at == nil || @first_at < Time.now
595
- Time.now + @interval
611
+ Rufus::Scheduler::ZoTime.now + @interval
596
612
  else
597
613
  @first_at
598
614
  end
@@ -38,27 +38,27 @@ module Rufus
38
38
  parse_cron(o, opts) ||
39
39
  parse_in(o, opts) || # covers 'every' schedule strings
40
40
  parse_at(o, opts) ||
41
- fail(ArgumentError.new("couldn't parse \"#{o}\""))
41
+ fail(ArgumentError.new("couldn't parse #{o.inspect} (#{o.class})"))
42
42
  end
43
43
 
44
- def self.parse_in(o, opts={})
45
-
46
- o.is_a?(String) ? parse_duration(o, opts) : o
47
- end
48
-
49
- def self.parse_at(o, opts={})
44
+ def self.parse_cron(o, opts)
50
45
 
51
- Rufus::Scheduler::ZoTime.parse(o, opts).time
46
+ CronLine.new(o)
52
47
 
53
- rescue StandardError => se
48
+ rescue ArgumentError => ae
54
49
 
55
50
  return nil if opts[:no_error]
56
- fail se
51
+ fail ae
57
52
  end
58
53
 
59
- def self.parse_cron(o, opts)
54
+ def self.parse_in(o, opts={})
60
55
 
61
- CronLine.new(o)
56
+ #o.is_a?(String) ? parse_duration(o, opts) : o
57
+
58
+ return parse_duration(o, opts) if o.is_a?(String)
59
+ return o if o.is_a?(Numeric)
60
+
61
+ fail ArgumentError.new("couldn't parse time point in #{o.inspect}")
62
62
 
63
63
  rescue ArgumentError => ae
64
64
 
@@ -66,17 +66,16 @@ module Rufus
66
66
  fail ae
67
67
  end
68
68
 
69
- def self.parse_to_time(o)
69
+ def self.parse_at(o, opts={})
70
70
 
71
- t = o
72
- t = parse(t) if t.is_a?(String)
73
- t = Time.now + t if t.is_a?(Numeric)
71
+ return o if o.is_a?(Rufus::Scheduler::ZoTime)
72
+ return Rufus::Scheduler::ZoTime.make(o) if o.is_a?(Time)
73
+ Rufus::Scheduler::ZoTime.parse(o, opts)
74
74
 
75
- fail ArgumentError.new(
76
- "cannot turn #{o.inspect} to a point in time, doesn't make sense"
77
- ) unless t.is_a?(Time)
75
+ rescue StandardError => se
78
76
 
79
- t
77
+ return nil if opts[:no_error]
78
+ fail se
80
79
  end
81
80
 
82
81
  DURATIONS2M = [
@@ -22,8 +22,6 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
- require 'rufus/scheduler/zones'
26
-
27
25
 
28
26
  class Rufus::Scheduler
29
27
 
@@ -32,52 +30,72 @@ class Rufus::Scheduler
32
30
  #
33
31
  class ZoTime
34
32
 
35
- attr_accessor :seconds
36
- attr_accessor :zone
33
+ attr_reader :seconds
34
+ attr_reader :zone
37
35
 
38
36
  def initialize(s, zone)
39
37
 
40
38
  @seconds = s.to_f
41
- @zone = zone
42
- end
39
+ @zone = self.class.get_tzone(zone || :current)
43
40
 
44
- def time
41
+ fail ArgumentError.new(
42
+ "cannot determine timezone from #{zone.inspect}"
43
+ ) unless @zone
45
44
 
46
- in_zone do
45
+ @time = nil # cache for #to_time result
46
+ end
47
47
 
48
- t = Time.at(@seconds)
48
+ def seconds=(f)
49
49
 
50
- #if t.isdst
51
- # t1 = Time.at(@seconds + 3600)
52
- # t = t1 if t.zone != t1.zone && t.hour == t1.hour && t.min == t1.min
53
- # # ambiguous TZ (getting out of DST)
54
- #else
55
- # t.hour # force t to compute itself
56
- #end
57
- #
58
- # jump out of DST as soon as possible, jumps 1h as seen from UTC
50
+ @time = nil
51
+ @seconds = f
52
+ end
59
53
 
60
- t.hour # force t to compute itself
61
- #
62
- # stay in DST as long as possible, no jump seen from UTC
54
+ def zone=(z)
63
55
 
64
- t
65
- end
56
+ @time = nil
57
+ @zone = self.class.get_tzone(zone || :current)
66
58
  end
67
59
 
68
60
  def utc
69
61
 
70
- time.utc
62
+ Time.utc(1970, 1, 1) + @seconds
63
+ end
64
+
65
+ # Returns a Ruby Time instance.
66
+ #
67
+ # Warning: the timezone of that Time instance will be UTC.
68
+ #
69
+ def to_time
70
+
71
+ @time ||= begin; u = utc; @zone.period_for_utc(u).to_local(u); end
72
+ end
73
+
74
+ %w[
75
+ year month day wday hour min sec usec asctime
76
+ ].each do |m|
77
+ define_method(m) { to_time.send(m) }
71
78
  end
79
+ def iso8601(fraction_digits=0); to_time.iso8601(fraction_digits); end
72
80
 
73
- def add(s)
81
+ def ==(o)
74
82
 
75
- @seconds += s.to_f
83
+ o.is_a?(ZoTime) && o.seconds == @seconds && o.zone == @zone
76
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
77
95
 
78
- def substract(s)
96
+ def to_i
79
97
 
80
- @seconds -= s.to_f
98
+ @seconds.to_i
81
99
  end
82
100
 
83
101
  def to_f
@@ -85,82 +103,346 @@ class Rufus::Scheduler
85
103
  @seconds
86
104
  end
87
105
 
88
- #DELTA_TZ_REX = /\A[+-][0-1][0-9]:?[0-5][0-9]\z/
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
161
+
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
184
+
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 self.now(zone=nil)
195
+
196
+ ZoTime.new(Time.now.to_f, zone)
197
+ end
198
+
199
+ # https://en.wikipedia.org/wiki/ISO_8601
200
+ # Postel's law applies
201
+ #
202
+ def self.extract_iso8601_zone(s)
203
+
204
+ m = s.match(
205
+ /[0-2]\d(?::?[0-6]\d(?::?[0-6]\d))?\s*([+-]\d\d(?::?\d\d)?)\s*\z/)
206
+ return nil unless m
207
+
208
+ zs = m[1].split(':')
209
+ zs << '00' if zs.length < 2
89
210
 
90
- def self.envtzable?(s)
211
+ zh = zs[0].to_i.abs
91
212
 
92
- TIMEZONES.include?(s)
213
+ return nil if zh > 24
214
+ return nil if zh == 24 && zs[1].to_i != 0
215
+
216
+ zs.join(':')
93
217
  end
94
218
 
95
219
  def self.parse(str, opts={})
96
220
 
97
221
  if defined?(::Chronic) && t = ::Chronic.parse(str, opts)
98
- return ZoTime.new(t, ENV['TZ'])
222
+ return ZoTime.new(t, nil)
99
223
  end
100
224
 
225
+ #rold = RUBY_VERSION < '1.9.0'
226
+ #rold = RUBY_VERSION < '2.0.0'
227
+
101
228
  begin
102
229
  DateTime.parse(str)
103
230
  rescue
104
- fail ArgumentError, "no time information in #{o.inspect}"
105
- end if RUBY_VERSION < '1.9.0'
231
+ fail ArgumentError, "no time information in #{str.inspect}"
232
+ end #if rold
233
+ #
234
+ # is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now`
106
235
 
107
236
  zone = nil
108
237
 
109
238
  s =
110
- str.gsub(/\S+/) { |m|
111
- if envtzable?(m)
112
- zone ||= m
239
+ str.gsub(/\S+/) do |w|
240
+ if z = get_tzone(w)
241
+ zone ||= z
113
242
  ''
114
243
  else
115
- m
244
+ w
245
+ end
246
+ end
247
+
248
+ local = Time.parse(s)
249
+ izone = extract_iso8601_zone(s)
250
+
251
+ zone ||=
252
+ if s.match(/\dZ\b/)
253
+ get_tzone('Zulu')
254
+ #elsif rold && izone
255
+ elsif izone
256
+ get_tzone(izone)
257
+ elsif local.zone.nil? && izone
258
+ get_tzone(local.strftime('%:z'))
259
+ else
260
+ get_tzone(:local)
261
+ end
262
+
263
+ secs =
264
+ #if rold && izone
265
+ if izone
266
+ local.to_f
267
+ else
268
+ zone.period_for_local(local).to_utc(local).to_f
269
+ end
270
+
271
+ ZoTime.new(secs, zone)
272
+ end
273
+
274
+ def self.get_tzone(str)
275
+
276
+ return str if str.is_a?(::TZInfo::Timezone)
277
+
278
+ # discard quickly when it's certainly not a timezone
279
+
280
+ return nil if str == nil
281
+ return nil if str == '*'
282
+
283
+ # ok, it's a timezone then
284
+
285
+ str = Time.now.zone if str == :current || str == :local
286
+
287
+ # utc_offset
288
+
289
+ if str.is_a?(Numeric)
290
+ i = str.to_i
291
+ sn = i < 0 ? '-' : '+'; i = i.abs
292
+ hr = i / 3600; mn = i % 3600; sc = i % 60
293
+ str = (sc > 0 ? "%s%02d:%02d:%02d" : "%s%02d:%02d") % [ sn, hr, mn, sc ]
294
+ end
295
+
296
+ return nil if str.index('#')
297
+ # counters "sun#2", etc... On OSX would go all the way to true
298
+
299
+ # vanilla time zones
300
+
301
+ z = (::TZInfo::Timezone.get(str) rescue nil)
302
+ return z if z
303
+
304
+ # time zone abbreviations
305
+
306
+ if str.match(/\A[A-Z0-9-]{3,6}\z/)
307
+
308
+ twin = Time.utc(Time.now.year, 1, 1)
309
+ tsum = Time.utc(Time.now.year, 7, 1)
310
+
311
+ z =
312
+ ::TZInfo::Timezone.all.find do |tz|
313
+ tz.period_for_utc(twin).abbreviation.to_s == str ||
314
+ tz.period_for_utc(tsum).abbreviation.to_s == str
116
315
  end
117
- }
316
+ return z if z
317
+ end
318
+
319
+ # some time zone aliases
320
+
321
+ return ::TZInfo::Timezone.get('Zulu') if %w[ Z ].include?(str)
118
322
 
119
- return nil unless zone.nil? || is_timezone?(zone)
323
+ # custom timezones, no DST, just an offset, like "+08:00" or "-01:30"
324
+
325
+ tz = (@custom_tz_cache ||= {})[str]
326
+ return tz if tz
327
+
328
+ if m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])\z/)
329
+
330
+ hr = m[1].to_i
331
+ mn = m[2].to_i
332
+
333
+ hr = nil if hr.abs > 11
334
+ hr = nil if mn > 59
335
+ mn = -mn if hr && hr < 0
336
+
337
+ return (
338
+ @custom_tz_cache[str] =
339
+ begin
340
+ tzi = TZInfo::TransitionDataTimezoneInfo.new(str)
341
+ tzi.offset(str, hr * 3600 + mn * 60, 0, str)
342
+ tzi.create_timezone
343
+ end
344
+ ) if hr
345
+ end
120
346
 
121
- zt = ZoTime.new(0, zone || ENV['TZ'])
122
- zt.in_zone { zt.seconds = Time.parse(s).to_f }
347
+ # so it's not a timezone.
123
348
 
124
- zt.seconds == nil ? nil : zt
349
+ nil
125
350
  end
126
351
 
127
- def self.is_timezone?(str)
352
+ def self.local_tzone
128
353
 
129
- return false if str == nil
130
- return false if str == '*'
354
+ get_tzone(:local)
355
+ end
131
356
 
132
- return false if str.index('#')
133
- # "sun#2", etc... On OSX would go all the way to true
357
+ def self.make(o)
358
+
359
+ zt =
360
+ case o
361
+ when Time
362
+ ZoTime.new(o.to_f, o.zone)
363
+ when Date
364
+ t =
365
+ o.respond_to?(:to_time) ?
366
+ o.to_time :
367
+ Time.parse(o.strftime('%Y-%m-%d %H:%M:%S'))
368
+ ZoTime.new(t.to_f, t.zone)
369
+ when String
370
+ Rufus::Scheduler.parse_in(o, :no_error => true) || self.parse(o)
371
+ else
372
+ o
373
+ end
134
374
 
135
- return true if Time.zone_offset(str)
375
+ zt = ZoTime.new(Time.now.to_f + zt, nil) if zt.is_a?(Numeric)
136
376
 
137
- return !! (::TZInfo::Timezone.get(str) rescue nil) if defined?(::TZInfo)
377
+ fail ArgumentError.new(
378
+ "cannot turn #{o.inspect} to a ZoTime instance"
379
+ ) unless zt.is_a?(ZoTime)
138
380
 
139
- return true if TIMEZONES.include?(str)
140
- return true if TIMEZONEs.include?(str)
381
+ zt
382
+ end
141
383
 
142
- t = ZoTime.new(0, str).time
384
+ # def in_zone(&block)
385
+ #
386
+ # current_timezone = ENV['TZ']
387
+ # ENV['TZ'] = @zone
388
+ #
389
+ # block.call
390
+ #
391
+ # ensure
392
+ #
393
+ # ENV['TZ'] = current_timezone
394
+ # end
395
+
396
+ protected
397
+
398
+ def inc(t, dir)
399
+
400
+ if t.is_a?(Numeric)
401
+ nt = self.dup
402
+ nt.seconds += dir * t.to_f
403
+ nt
404
+ elsif t.respond_to?(:to_f)
405
+ @seconds + dir * t.to_f
406
+ else
407
+ fail ArgumentError.new(
408
+ "cannot call ZoTime #- or #+ with arg of class #{t.class}")
409
+ end
410
+ end
143
411
 
144
- return false if t.zone == ''
145
- return false if t.zone == 'UTC'
146
- return false if t.utc_offset == 0 && str.start_with?(t.zone)
147
- # 3 common fallbacks...
412
+ def _to_f(o)
148
413
 
149
- return false if RUBY_PLATFORM.include?('java') && ! envtzable?(str)
414
+ fail ArgumentError(
415
+ "comparison of ZoTime with #{o.inspect} failed"
416
+ ) unless o.is_a?(ZoTime) || o.is_a?(Time)
150
417
 
151
- true
418
+ o.to_f
152
419
  end
153
420
 
154
- def in_zone(&block)
421
+ def strfz(code)
422
+
423
+ return @zone.name if code == '%/Z'
424
+
425
+ per = @zone.period_for_utc(utc)
155
426
 
156
- current_timezone = ENV['TZ']
157
- ENV['TZ'] = @zone
427
+ return per.abbreviation.to_s if code == '%Z'
158
428
 
159
- block.call
429
+ off = per.utc_total_offset
430
+ #
431
+ sn = off < 0 ? '-' : '+'; off = off.abs
432
+ hr = off / 3600
433
+ mn = (off % 3600) / 60
434
+ sc = 0
160
435
 
161
- ensure
436
+ fmt =
437
+ if code == '%z'
438
+ "%s%02d%02d"
439
+ elsif code == '%:z'
440
+ "%s%02d:%02d"
441
+ else
442
+ "%s%02d:%02d:%02d"
443
+ end
162
444
 
163
- ENV['TZ'] = current_timezone
445
+ fmt % [ sn, hr, mn, sc ]
164
446
  end
165
447
  end
166
448
  end