How Accurate Is Your Clock?
I bumped my head against another one.
While Ruby is not equal in all environments, I at least want to control the difference wherever possible.
When you do a `Time.now`, you'll get something like
{% highlight text %}
Tue May 20 17:08:21 -400 2008
{% endhighlight %}
Lets say that you're formatting that time for a log message though...
{% highlight ruby %}
Time.now.strftime "%m/%d/%y %H:%M:%S"
{% endhighlight %}
which gives
{% highlight text %}
05/20/08 17:08:21
{% endhighlight %}
Great, except that you'd like *sub-second* precision.
{% highlight ruby %}
(time = Time.now).strftime "%m/%d/%y %H:%M:%S." << ("%06d" % time.usec)
{% endhighlight %}
Well, ruby on my iMac gives me six digits of microseconds,
{% highlight text %}
05/20/08 17:08:21.943286
{% endhighlight %}
but alas, my PC only three,
{% highlight text %}
05/20/08 17:08:21.943000
{% endhighlight %}
While you may say, "So what?" I must reply, "Yuck."
I just don't want those empty zeros hanging out there.
The purist in me wants to lop them off. What I want is
{% highlight ruby %}
(time = Time.now).strftime "%m/%d/%y %H:%M:%S." << ("%06d" % time.usec)[0,@usecs]
{% endhighlight %}
where `@usecs` is the sub-second precision of the clock.
Now, I could just use the Config to get the platform I'm on and assign the correct value, but in this instance I'd like to be more proactive.
I can figure out the right value empirically.
I start by getting an Array of sample microseconds that are slightly spread out in time.
{% highlight ruby %}
t = (1..5).collect { sleep 0.001001; "#{Time.now.usec}" }
{% endhighlight %}
Then I figure out what digit contains the last non-zero digit
{% highlight ruby %}
nz = t.collect { |s| s.length - (/[1-9]/ =~ s.reverse) }
{% endhighlight %}
And finally, I just take the max
{% highlight ruby %}
@usecs = nz.max
{% endhighlight %}
Why the multiple samples?
Because there's still a one in ten chance that a zero will occur naturally in the real non-zero digit position.
Or one in one hundred for two zeros, or one in a thousand for three.
By running multiple samples and taking the max, we won't be likely fooled, statistically speaking.
Why five samples?
I just figure that those odds are pretty darned good.
Of course, we can collapse this all nicely,
{% highlight ruby %}
class Time
@@subsecond_precision = nil
def self.subsecond_precision
@@subsecond_precision ||=
(1..5).collect {
sleep 0.001001
s = "#{Time.now.usec}"
s.length - (/[1-9]/ =~ s.reverse)
}.max
end
end
{% endhighlight %}
Now I can just use `Time.subsecond_precision` in place of `@usecs` above.
I don't have to worry about using system-dependent assignments.
I can just do it once when I need it and move forward.