A
Adam Shelly
Here's my Windows solution. I offer it as an object lesson about why-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
## 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.
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 Quiz #
## -Adam Shelly
## (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)]