On 8/23/08, Matthew Moss <matthew.moss / gmail.com> 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 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)]