seconds = 356521N.B.: This relies on Ruby's integer division -- dividing an integer by an integer results in an integer, with any fractional remainder discarded.
days = seconds / (24 * 60 * 60)
seconds = seconds % (24 * 60 * 60)
hours = seconds / (60 * 60)
seconds = seconds % (60 * 60)
minutes = seconds / 60
seconds = seconds % 60
My pair and I ended up implementing something very much like this, but it left a bad taste in my mouth. I mean, it works and all, but it feels kind of ... non-functional. Ya know?
So, after much fretting and sleeplessness last night ... behold:
seconds = 356521This version needs to be run in a Rails
days, hours, minutes, seconds =
[1.day, 1.hour, 1.minute, 1.second].inject([]) do |acc, unit|
quotient, seconds = seconds.divmod unit
acc << quotient
end
script/console
rather than irb
, because it makes use of the Rails shortcut definitions of the number of seconds in various units of time. You could easily convert to plain irb
-able Ruby by replacing 1.day
et al above with their numeric equivalents.The resulting code is both (more) functional, and more quintessentially Ruby-ish.
divmod
and multiple assignment let us figure out the quotient and the remainder of the division in one go, and inject
lets us accumulate the results and ultimately multiply assign them to their respective units. Neato.
I'm a little bummed, though, that this version has the side effect of destroying the original contents of
seconds
, as well as requiring seconds
to be defined outside the scope of the inject
. What would be really cool would be to have a version of inject
that allowed for multiple accumulators (or, really, a 'decumulator' in this case) such that all side effects could be contained within the inject
.
4 comments:
Hi David,
I came up with something similar that doesn't have any side effects, and thought that you may find it useful.
ScanL
Regards,
Bill
How about
>> t=Time.at(356521-3600*25)
=> Sun Jan 04 03:02:01 0100 1970
>> t.day
=> 4
>> t.hour
=> 3
>> t.min
=> 2
>> t.sec
=> 1
@Jonas: I was hoping at first that I could use Time.at(), but some things about it bothered me. For example, when I run Time.at(356521 - 3600*25) locally (Atlanta, GA, US) I get Sat Jan 03 21:02:01 -0500 1970. Plus, I had to figure out why used 25 and not 24 (apparently you're in UTC + 1).
We can use your trick plus a quick call to utc() to correct for local timezones and the fact that the beginning of the epoch is Day 1 (not Day 0) thusly -- Time.at(356521 - 3600*24).utc.
Thanks for the tip. :-)
If you need to handle years you also need to compensate for Time.at(0) not starting at year 0 but at 1970. Come to think of it I think I prefer your solution because it's general and needs no tweaking. There really should be a timespan somewhere in the Ruby core but I can't find it.
I don't know if you want to bring in the ActiveSupport beast but if you do the http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Numeric/Time.html might save your day.
Post a Comment