rufus-scheduler 3.4.2 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4b2f35e030fcf0e6338856400c56ebc4ac4cc8f7
4
- data.tar.gz: bf3e0d885255a7654d4dfbcb95ed3ad4ead9c0ad
3
+ metadata.gz: b8c6844d5f77112580ff29651a33fe2bc3b9545f
4
+ data.tar.gz: 329289b1d8a476270440672b4efd71d4d41e976d
5
5
  SHA512:
6
- metadata.gz: a05aed4a749ec1ddff6423b732f0c3db85fd58836301b70a4a6efbc83fccab5a156f2877800d2cf7be34ec1f600342b30660fa71947322aba38e165c9490a118
7
- data.tar.gz: 4ef7fa38c78541dbf72312fadbb3f3a3dc2090dcd13ed2df5390a0950ec45c757c4af0d9ed7109fe3057281deeb0c86a7b75f3e27bbb47338e72bef14528a1dd
6
+ metadata.gz: 8a7a056a68f73e62a54b44cbb40c3862e70b75cd92bfbe887ac39c8f72c7398798bb5062272d7d78b8aeeaf3083971276febfa36fb2e7e534b4cd9efb5b7e41a
7
+ data.tar.gz: a0e682b539d953cf706ee0d431dfd175994c67f8ddeeb61dc5e1d966bf5fd56e79b0c92d5f32e1abaf80fa1674a774d743cb4acc1b248125b55b000a98344d1a
@@ -2,6 +2,13 @@
2
2
  = rufus-scheduler CHANGELOG.txt
3
3
 
4
4
 
5
+ == rufus-scheduler - 3.5.0 released 2018-05-15
6
+
7
+ - Use fugit to parse durations
8
+ - Drop .parse_time_string and .parse_duration_string
9
+ - Use fugit to parse cronlines
10
+
11
+
5
12
  == rufus-scheduler - 3.4.2 released 2017-05-24
6
13
 
7
14
  (had yanked 3.4.1, going 3.4.2)
@@ -58,6 +58,7 @@
58
58
 
59
59
  == Feedback
60
60
 
61
+ - Gian - https://github.com/snmgian - cron vs :first clarification - gh-266
61
62
  - Vito Laurenza - https://github.com/veetow - help debugging tz issues - gh-240
62
63
  - Yoshimi Keiji - https://githuc.com/walf443 - v.3.3.3 mislabelling
63
64
  - Alexander Deeb - https://github.com/adeeb1 - gh-230
data/Makefile CHANGED
@@ -4,6 +4,10 @@ NAME = \
4
4
  VERSION = \
5
5
  $(shell ruby -e "s = eval(File.read(Dir['*.gemspec'][0])); puts s.version")
6
6
 
7
+ count_lines:
8
+ find lib -name "*.rb" | xargs cat | ruby -e "p STDIN.readlines.count { |l| l = l.strip; l[0, 1] != '#' && l != '' }"
9
+ find spec -name "*_spec.rb" | xargs cat | ruby -e "p STDIN.readlines.count { |l| l = l.strip; l[0, 1] != '#' && l != '' }"
10
+ cl: count_lines
7
11
 
8
12
  gemspec_validate:
9
13
  @echo "---"
data/README.md CHANGED
@@ -128,6 +128,7 @@ Yes, issues can be reported in [rufus-scheduler issues](https://github.com/jmett
128
128
  * [Passenger in-depth spawn methods](https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/)
129
129
  * [Passenger in-depth spawn methods (smart spawning)](https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/#smart-spawning-hooks)
130
130
  * [The scheduler comes up when running the Rails console or a Rake task](https://github.com/jmettraux/rufus-scheduler#avoid-scheduling-when-running-the-ruby-on-rails-console)
131
+ * [The job triggers twice](https://github.com/jmettraux/rufus-scheduler#lockfile--mylockfiletxt)
131
132
  * [I don't get any of this, I just want it to work in my Rails application](#so-rails)
132
133
  * [I get "zotime.rb:41:in `initialize': cannot determine timezone from nil"](#i-get-zotimerb41in-initialize-cannot-determine-timezone-from-nil)
133
134
 
@@ -376,7 +377,7 @@ Since, by default, jobs are triggered in their own new thread, job instances mig
376
377
 
377
378
  To prevent overlap, one can set :overlap => false. Such a job will not trigger if one of its instance is already running.
378
379
 
379
- The `:overlap` option is considered before the `:mutex` option when the scheduler is reviewing jobs for triggering.
380
+ The `:overlap` option is considered after the `:mutex` option when the scheduler is reviewing jobs for triggering.
380
381
 
381
382
  ### :mutex => mutex_instance / mutex_name / array of mutexes
382
383
 
@@ -415,7 +416,7 @@ This option is for repeat jobs (cron / every) only.
415
416
  It's used to specify the first time after which the repeat job should trigger for the first time.
416
417
 
417
418
  In the case of an "every" job, this will be the first time (modulo the scheduler frequency) the job triggers.
418
- For a "cron" job, it's the time *after* which the first schedule will trigger.
419
+ For a "cron" job as well, the :first will point to the first time the job has to trigger, the following trigger time are then determined by the cron string.
419
420
 
420
421
  ```ruby
421
422
  scheduler.every '2d', :first_at => Time.now + 10 * 3600 do
@@ -4,20 +4,19 @@ require 'date' if RUBY_VERSION < '1.9.0'
4
4
  require 'time'
5
5
  require 'thread'
6
6
 
7
- require 'et-orbi'
7
+ require 'fugit'
8
8
 
9
9
 
10
10
  module Rufus
11
11
 
12
12
  class Scheduler
13
13
 
14
- VERSION = '3.4.2'
14
+ VERSION = '3.5.0'
15
15
 
16
16
  EoTime = ::EtOrbi::EoTime
17
17
 
18
18
  require 'rufus/scheduler/util'
19
19
  require 'rufus/scheduler/jobs'
20
- require 'rufus/scheduler/cronline'
21
20
  require 'rufus/scheduler/job_array'
22
21
  require 'rufus/scheduler/locks'
23
22
 
@@ -238,9 +237,9 @@ module Rufus
238
237
  opts[:_t] = Scheduler.parse(arg, opts)
239
238
 
240
239
  case opts[:_t]
241
- when CronLine then schedule_cron(arg, callable, opts, &block)
242
- when Time then schedule_at(arg, callable, opts, &block)
243
- else schedule_in(arg, callable, opts, &block)
240
+ when ::Fugit::Cron then schedule_cron(arg, callable, opts, &block)
241
+ when ::EtOrbi::EoTime, Time then schedule_at(arg, callable, opts, &block)
242
+ else schedule_in(arg, callable, opts, &block)
244
243
  end
245
244
  end
246
245
 
@@ -252,8 +251,8 @@ module Rufus
252
251
  opts[:_t] = Scheduler.parse(arg, opts)
253
252
 
254
253
  case opts[:_t]
255
- when CronLine then schedule_cron(arg, callable, opts, &block)
256
- else schedule_every(arg, callable, opts, &block)
254
+ when ::Fugit::Cron then schedule_cron(arg, callable, opts, &block)
255
+ else schedule_every(arg, callable, opts, &block)
257
256
  end
258
257
  end
259
258
 
@@ -620,10 +619,35 @@ module Rufus
620
619
 
621
620
  job = job_class.new(self, t, opts, block || callable)
622
621
 
623
- fail ArgumentError.new(
624
- "job frequency (#{job.frequency}) is higher than " +
625
- "scheduler frequency (#{@frequency})"
626
- ) if job.respond_to?(:frequency) && job.frequency < @frequency
622
+ #fail ArgumentError.new(
623
+ # "job frequency (#{job.frequency}) is higher than " +
624
+ # "scheduler frequency (#{@frequency})"
625
+ #) if job.respond_to?(:frequency) && job.frequency < @frequency
626
+ #
627
+ # This was expensive
628
+
629
+ if (
630
+ ! job.is_a?(Rufus::Scheduler::IntervalJob) &&
631
+ job.methods.include?(:next_time_from)
632
+ ) then
633
+
634
+ nts = (1..365)
635
+ .inject([ job.send(:next_time_from, EtOrbi.now) ]) { |a, i|
636
+ a << job.send(:next_time_from, a.last); a }
637
+ deltas = []; prev = nts.shift
638
+ while (nt = nts.shift); deltas << (nt - prev).to_f; prev = nt; end
639
+
640
+ deltas = deltas[1..-1] \
641
+ if opts.keys.find { |k| k.to_s.match(/\Afirst/) }
642
+ #
643
+ # do not consider the first delta if there is a first, first_at,
644
+ # or first_in involved
645
+
646
+ fail ArgumentError.new(
647
+ "job frequency (~max #{deltas.min}s) is higher than " +
648
+ "scheduler frequency (#{@frequency})"
649
+ ) if deltas.min < @frequency * 0.9
650
+ end
627
651
 
628
652
  @jobs.push(job)
629
653
 
@@ -612,9 +612,7 @@ module Rufus
612
612
 
613
613
  super(scheduler, cronline, opts, block)
614
614
 
615
- @cron_line =
616
- opts[:_t] ||
617
- (cronline.is_a?(CronLine) ? cronline : CronLine.new(cronline))
615
+ @cron_line = opts[:_t] || ::Fugit::Cron.parse(cronline)
618
616
 
619
617
  set_next_time(nil)
620
618
  end
@@ -3,255 +3,190 @@ module Rufus
3
3
 
4
4
  class Scheduler
5
5
 
6
- #--
7
- # time and string methods
8
- #++
9
-
10
- def self.parse(o, opts={})
11
-
12
- opts[:no_error] = true
13
-
14
- parse_cron(o, opts) ||
15
- parse_in(o, opts) || # covers 'every' schedule strings
16
- parse_at(o, opts) ||
17
- fail(ArgumentError.new("couldn't parse #{o.inspect} (#{o.class})"))
18
- end
19
-
20
- def self.parse_cron(o, opts)
21
-
22
- o.is_a?(CronLine) ? o : CronLine.new(o)
23
-
24
- rescue ArgumentError => ae
25
-
26
- return nil if opts[:no_error]
27
- fail ae
28
- end
29
-
30
- def self.parse_in(o, opts={})
31
-
32
- #o.is_a?(String) ? parse_duration(o, opts) : o
33
-
34
- return parse_duration(o, opts) if o.is_a?(String)
35
- return o if o.is_a?(Numeric)
6
+ class << self
36
7
 
37
- fail ArgumentError.new("couldn't parse time point in #{o.inspect}")
8
+ #--
9
+ # time and string methods
10
+ #++
38
11
 
39
- rescue ArgumentError => ae
12
+ def parse(o, opts={})
40
13
 
41
- return nil if opts[:no_error]
42
- fail ae
43
- end
14
+ opts[:no_error] = true
44
15
 
45
- def self.parse_at(o, opts={})
16
+ parse_cron(o, opts) ||
17
+ parse_in(o, opts) || # covers 'every' schedule strings
18
+ parse_at(o, opts) ||
19
+ fail(ArgumentError.new("couldn't parse #{o.inspect} (#{o.class})"))
20
+ end
46
21
 
47
- return o if o.is_a?(EoTime)
48
- return EoTime.make(o) if o.is_a?(Time)
49
- EoTime.parse(o, opts)
22
+ def parse_cron(o, opts)
50
23
 
51
- rescue StandardError => se
24
+ Fugit.parse_cron(o)
25
+ end
52
26
 
53
- return nil if opts[:no_error]
54
- fail se
55
- end
27
+ def parse_in(o, opts={})
56
28
 
57
- DURATIONS2M = [
58
- [ 'y', 365 * 24 * 3600 ],
59
- [ 'M', 30 * 24 * 3600 ],
60
- [ 'w', 7 * 24 * 3600 ],
61
- [ 'd', 24 * 3600 ],
62
- [ 'h', 3600 ],
63
- [ 'm', 60 ],
64
- [ 's', 1 ]
65
- ]
66
- DURATIONS2 = DURATIONS2M.dup
67
- DURATIONS2.delete_at(1)
68
-
69
- DURATIONS = DURATIONS2M.inject({}) { |r, (k, v)| r[k] = v; r }
70
- DURATION_LETTERS = DURATIONS.keys.join
71
-
72
- DU_KEYS = DURATIONS2M.collect { |k, v| k.to_sym }
73
-
74
- # Turns a string like '1m10s' into a float like '70.0', more formally,
75
- # turns a time duration expressed as a string into a Float instance
76
- # (millisecond count).
77
- #
78
- # w -> week
79
- # d -> day
80
- # h -> hour
81
- # m -> minute
82
- # s -> second
83
- # M -> month
84
- # y -> year
85
- # 'nada' -> millisecond
86
- #
87
- # Some examples:
88
- #
89
- # Rufus::Scheduler.parse_duration "0.5" # => 0.5
90
- # Rufus::Scheduler.parse_duration "500" # => 0.5
91
- # Rufus::Scheduler.parse_duration "1000" # => 1.0
92
- # Rufus::Scheduler.parse_duration "1h" # => 3600.0
93
- # Rufus::Scheduler.parse_duration "1h10s" # => 3610.0
94
- # Rufus::Scheduler.parse_duration "1w2d" # => 777600.0
95
- #
96
- # Negative time strings are OK (Thanks Danny Fullerton):
97
- #
98
- # Rufus::Scheduler.parse_duration "-0.5" # => -0.5
99
- # Rufus::Scheduler.parse_duration "-1h" # => -3600.0
100
- #
101
- def self.parse_duration(string, opts={})
29
+ #o.is_a?(String) ? parse_duration(o, opts) : o
102
30
 
103
- s = string.to_s.strip
104
- mod = s[0, 1] == '-' ? -1 : 1
105
- s = s[1..-1] if mod == -1
31
+ return parse_duration(o, opts) if o.is_a?(String)
32
+ return o if o.is_a?(Numeric)
106
33
 
107
- ss = mod < 0 ? '-' : ''
108
- r = 0.0
34
+ fail ArgumentError.new("couldn't parse time point in #{o.inspect}")
109
35
 
110
- s.scan(/(\d*\.\d+|\d+\.?)([#{DURATION_LETTERS}]?)/) do |f, d|
111
- ss += "#{f}#{d}"
112
- r += f.to_f * (DURATIONS[d] || 1.0)
113
- end
36
+ rescue ArgumentError => ae
114
37
 
115
- if ss == '-' || ss != string.to_s.strip
116
38
  return nil if opts[:no_error]
117
- fail ArgumentError.new("invalid time duration #{string.inspect}")
39
+ fail ae
118
40
  end
119
41
 
120
- mod * r
121
- end
42
+ def parse_at(o, opts={})
122
43
 
123
- class << self
124
- #-
125
- # for compatibility with rufus-scheduler 2.x
126
- #+
127
- alias parse_duration_string parse_duration
128
- alias parse_time_string parse_duration
129
- end
44
+ return o if o.is_a?(EoTime)
45
+ return EoTime.make(o) if o.is_a?(Time)
46
+ EoTime.parse(o, opts)
130
47
 
48
+ rescue StandardError => se
131
49
 
132
- # Turns a number of seconds into a a time string
133
- #
134
- # Rufus.to_duration 0 # => '0s'
135
- # Rufus.to_duration 60 # => '1m'
136
- # Rufus.to_duration 3661 # => '1h1m1s'
137
- # Rufus.to_duration 7 * 24 * 3600 # => '1w'
138
- # Rufus.to_duration 30 * 24 * 3600 + 1 # => "4w2d1s"
139
- #
140
- # It goes from seconds to the year. Months are not counted (as they
141
- # are of variable length). Weeks are counted.
142
- #
143
- # For 30 days months to be counted, the second parameter of this
144
- # method can be set to true.
145
- #
146
- # Rufus.to_duration 30 * 24 * 3600 + 1, true # => "1M1s"
147
- #
148
- # If a Float value is passed, milliseconds will be displayed without
149
- # 'marker'
150
- #
151
- # Rufus.to_duration 0.051 # => "51"
152
- # Rufus.to_duration 7.051 # => "7s51"
153
- # Rufus.to_duration 0.120 + 30 * 24 * 3600 + 1 # => "4w2d1s120"
154
- #
155
- # (this behaviour mirrors the one found for parse_time_string()).
156
- #
157
- # Options are :
158
- #
159
- # * :months, if set to true, months (M) of 30 days will be taken into
160
- # account when building up the result
161
- # * :drop_seconds, if set to true, seconds and milliseconds will be trimmed
162
- # from the result
163
- #
164
- def self.to_duration(seconds, options={})
165
-
166
- h = to_duration_hash(seconds, options)
167
-
168
- return (options[:drop_seconds] ? '0m' : '0s') if h.empty?
169
-
170
- s =
171
- DU_KEYS.inject('') { |r, key|
172
- count = h[key]
173
- count = nil if count == 0
174
- r << "#{count}#{key}" if count
175
- r
176
- }
177
-
178
- ms = h[:ms]
179
- s << ms.to_s if ms
180
-
181
- s
182
- end
183
-
184
- class << self
185
- #-
186
- # for compatibility with rufus-scheduler 2.x
187
- #+
188
- alias to_duration_string to_duration
189
- alias to_time_string to_duration
190
- end
191
-
192
- # Turns a number of seconds (integer or Float) into a hash like in :
193
- #
194
- # Rufus.to_duration_hash 0.051
195
- # # => { :ms => "51" }
196
- # Rufus.to_duration_hash 7.051
197
- # # => { :s => 7, :ms => "51" }
198
- # Rufus.to_duration_hash 0.120 + 30 * 24 * 3600 + 1
199
- # # => { :w => 4, :d => 2, :s => 1, :ms => "120" }
200
- #
201
- # This method is used by to_duration behind the scenes.
202
- #
203
- # Options are :
204
- #
205
- # * :months, if set to true, months (M) of 30 days will be taken into
206
- # account when building up the result
207
- # * :drop_seconds, if set to true, seconds and milliseconds will be trimmed
208
- # from the result
209
- #
210
- def self.to_duration_hash(seconds, options={})
50
+ return nil if opts[:no_error]
51
+ fail se
52
+ end
211
53
 
212
- h = {}
54
+ # Turns a string like '1m10s' into a float like '70.0', more formally,
55
+ # turns a time duration expressed as a string into a Float instance
56
+ # (millisecond count).
57
+ #
58
+ # w -> week
59
+ # d -> day
60
+ # h -> hour
61
+ # m -> minute
62
+ # s -> second
63
+ # M -> month
64
+ # y -> year
65
+ # 'nada' -> millisecond
66
+ #
67
+ # Some examples:
68
+ #
69
+ # Rufus::Scheduler.parse_duration "0.5" # => 0.5
70
+ # Rufus::Scheduler.parse_duration "500" # => 0.5
71
+ # Rufus::Scheduler.parse_duration "1000" # => 1.0
72
+ # Rufus::Scheduler.parse_duration "1h" # => 3600.0
73
+ # Rufus::Scheduler.parse_duration "1h10s" # => 3610.0
74
+ # Rufus::Scheduler.parse_duration "1w2d" # => 777600.0
75
+ #
76
+ # Negative time strings are OK (Thanks Danny Fullerton):
77
+ #
78
+ # Rufus::Scheduler.parse_duration "-0.5" # => -0.5
79
+ # Rufus::Scheduler.parse_duration "-1h" # => -3600.0
80
+ #
81
+ def parse_duration(str, opts={})
82
+
83
+ d =
84
+ opts[:no_error] ?
85
+ Fugit::Duration.parse(str, opts) :
86
+ Fugit::Duration.do_parse(str, opts)
87
+ d ?
88
+ d.to_sec :
89
+ nil
90
+ end
213
91
 
214
- if seconds.is_a?(Float)
215
- h[:ms] = (seconds % 1 * 1000).to_i
216
- seconds = seconds.to_i
92
+ # Turns a number of seconds into a a time string
93
+ #
94
+ # Rufus.to_duration 0 # => '0s'
95
+ # Rufus.to_duration 60 # => '1m'
96
+ # Rufus.to_duration 3661 # => '1h1m1s'
97
+ # Rufus.to_duration 7 * 24 * 3600 # => '1w'
98
+ # Rufus.to_duration 30 * 24 * 3600 + 1 # => "4w2d1s"
99
+ #
100
+ # It goes from seconds to the year. Months are not counted (as they
101
+ # are of variable length). Weeks are counted.
102
+ #
103
+ # For 30 days months to be counted, the second parameter of this
104
+ # method can be set to true.
105
+ #
106
+ # Rufus.to_duration 30 * 24 * 3600 + 1, true # => "1M1s"
107
+ #
108
+ # If a Float value is passed, milliseconds will be displayed without
109
+ # 'marker'
110
+ #
111
+ # Rufus.to_duration 0.051 # => "51"
112
+ # Rufus.to_duration 7.051 # => "7s51"
113
+ # Rufus.to_duration 0.120 + 30 * 24 * 3600 + 1 # => "4w2d1s120"
114
+ #
115
+ # (this behaviour mirrors the one found for parse_time_string()).
116
+ #
117
+ # Options are :
118
+ #
119
+ # * :months, if set to true, months (M) of 30 days will be taken into
120
+ # account when building up the result
121
+ # * :drop_seconds, if set to true, seconds and milliseconds will be
122
+ # trimmed from the result
123
+ #
124
+ def to_duration(seconds, options={})
125
+
126
+ #d = Fugit::Duration.parse(seconds, options).deflate
127
+ #d = d.drop_seconds if options[:drop_seconds]
128
+ #d = d.deflate(:month => options[:months]) if options[:months]
129
+ #d.to_rufus_s
130
+
131
+ to_fugit_duration(seconds, options).to_rufus_s
217
132
  end
218
133
 
219
- if options[:drop_seconds]
220
- h.delete(:ms)
221
- seconds = (seconds - seconds % 60)
134
+ # Turns a number of seconds (integer or Float) into a hash like in :
135
+ #
136
+ # Rufus.to_duration_hash 0.051
137
+ # # => { :s => 0.051 }
138
+ # Rufus.to_duration_hash 7.051
139
+ # # => { :s => 7.051 }
140
+ # Rufus.to_duration_hash 0.120 + 30 * 24 * 3600 + 1
141
+ # # => { :w => 4, :d => 2, :s => 1.120 }
142
+ #
143
+ # This method is used by to_duration behind the scenes.
144
+ #
145
+ # Options are :
146
+ #
147
+ # * :months, if set to true, months (M) of 30 days will be taken into
148
+ # account when building up the result
149
+ # * :drop_seconds, if set to true, seconds and milliseconds will be
150
+ # trimmed from the result
151
+ #
152
+ def to_duration_hash(seconds, options={})
153
+
154
+ to_fugit_duration(seconds, options).to_rufus_h
222
155
  end
223
156
 
224
- durations = options[:months] ? DURATIONS2M : DURATIONS2
157
+ # Used by both .to_duration and .to_duration_hash
158
+ #
159
+ def to_fugit_duration(seconds, options={})
225
160
 
226
- durations.each do |key, duration|
161
+ d = Fugit::Duration
162
+ .parse(seconds, options)
163
+ .deflate
227
164
 
228
- count = seconds / duration
229
- seconds = seconds % duration
165
+ d = d.drop_seconds if options[:drop_seconds]
166
+ d = d.deflate(:month => options[:months]) if options[:months]
230
167
 
231
- h[key.to_sym] = count if count > 0
168
+ d
232
169
  end
233
170
 
234
- h
235
- end
171
+ #--
172
+ # misc
173
+ #++
236
174
 
237
- #--
238
- # misc
239
- #++
175
+ # Produces the UTC string representation of a Time instance
176
+ #
177
+ # like "2009/11/23 11:11:50.947109 UTC"
178
+ #
179
+ def utc_to_s(t=Time.now)
240
180
 
241
- # Produces the UTC string representation of a Time instance
242
- #
243
- # like "2009/11/23 11:11:50.947109 UTC"
244
- #
245
- def self.utc_to_s(t=Time.now)
246
-
247
- "#{t.utc.strftime('%Y-%m-%d %H:%M:%S')}.#{sprintf('%06d', t.usec)} UTC"
248
- end
181
+ "#{t.utc.strftime('%Y-%m-%d %H:%M:%S')}.#{sprintf('%06d', t.usec)} UTC"
182
+ end
249
183
 
250
- # Produces a hour/min/sec/milli string representation of Time instance
251
- #
252
- def self.h_to_s(t=Time.now)
184
+ # Produces a hour/min/sec/milli string representation of Time instance
185
+ #
186
+ def h_to_s(t=Time.now)
253
187
 
254
- "#{t.strftime('%H:%M:%S')}.#{sprintf('%06d', t.usec)}"
188
+ "#{t.strftime('%H:%M:%S')}.#{sprintf('%06d', t.usec)}"
189
+ end
255
190
  end
256
191
 
257
192
  # Debugging tools...