On 8/23/08, Matthew M. [email protected] wrote:
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Uptime Since… (#174)
Nice and easy one this week. Your task is to write a Ruby script that
reports the date and time of your last reboot, making use of the
uptime
command.
Here’s my Windows solution. I offer it as an object lesson about why
you should always check the built-in library documentation first. I
knew that Windows could report the dates in several different formats
based on user settings. I wanted to be able to handle all of them, so
I rolled my own parser based on checking the registry to see what
format to expect. Then I discovered Time#parse…
-Adam
##########################
Windows-uptime.rb
Ruby Q.
-Adam S.
(The hard way)
#find out what form windows is going to report the date in
dateform = reg query "HKCU\\Control Panel\\International" /v sShortDate
.chomp
dateform = /REG_SZ\t(.)$/m.match(dateform)[1].chomp
timeform = reg query "HKCU\\Control Panel\\International" /v sTimeFormat
.chomp
timeform = /REG_SZ\t(.)$/m.match(timeform)[1].chomp
timeform.gsub!(/:[sS]+/,‘’)
is_PM = reg query "HKCU\\Control Panel\\International" /v s2359
.chomp
is_PM = /REG_SZ\t(.*)$/m.match(is_PM)[1].chomp
#these are the format characters it may use (sorted in descending
order by size: year → minute)
$special=“yYmMdDhHmMtT”
#build a regexp that matches the format string,
record which order the result groups are returned in
Assumes that there is sone separator character
won’t work if the format is something like DDMMYY
def buildQuery formatstr, indexes
idx=1
formatstr.each_byte{|b|
if $special.include?(b.chr)
indexes[b.chr.upcase]=idx
else
idx+=1
end
}
#replace all the format chars with word matchers
f = formatstr.gsub(Regexp.new(“[#{$special}]”),‘\w’)
#make groups
f = f.gsub(/(\w)+/,‘(\w+)’)
return f
end
#storage for regexp group indexes
dateidx,timeidx={},{}
#build regexp to match result
regexp = Regexp.new "since "+buildQuery(dateform, dateidx)+’
'+buildQuery(timeform, timeidx)
#since we are combining 2 regexps, the second set of groups are offset
by the number of groups in the first
timeidx.each_key{|k| timeidx[k]+=dateidx.values.max}
#do the match
datematch = regexp.match(net stats srv
)
a=[]
idxset=dateidx
#build an array with the results of the match, in descending order,
$special.upcase.squeeze.each_byte{|b|
idxset = timeidx if b==?H #switch to time indexes when we get to
Hours
a<<datematch[idxset[b.chr]]
}
#add 12 if string contains this locale’s version of ‘PM’
a[3]=a[3].to_i+12 if datematch[timeidx[‘T’]]==is_PM
#convert month names to integers, if needed
??Question: Does strftime return names in the current locale??
(1…12).each{|m|
a[1]=m if
datematch[dateidx[‘M’]].include?(Time.utc(0,m).strftime(“%b”))
}
#construct the start time, calculate seconds elapsed until now
starttime = Time.local(*a)
s=(Time.now-starttime).to_i;
#show results
print datematch[0].gsub(“since”, “Last reboot at”)
puts “.\n #{s} seconds ago.”
#expand seconds into normalized units. (Is there a library function
to do this?)
puts ‘uptime = ‘+
[[’%d seconds’,60],[‘%d minutes’,60],[‘%d hours’,24],[‘%d
days’,365],[‘%d years’,1000]].map{|w,v|
s,rem=s.divmod v;
w%rem if rem>0
}.compact.reverse*’ ’
####################################
Now, the Easy way
require ‘Time’
datematch = /since (.*)$/.match(net stats srv
)
s= Time.now - Time.parse(datematch[1])
print “\n”+datematch[0].gsub(“since”, “Last reboot at”)
puts “.\n #{s.to_i} seconds ago.”
puts “uptime = %.2f days.”%[s/(60*60*24.0)]