One of my first code projects that I started dealt with time lengths. I would take a number of seconds and convert it into a form that uses other units of time. I.E. 75 would get turned into ‘1 minute, 15 seconds’. My first attempt at it was pretty much just ‘if’ statements:
Obviously it was my first time coding. Well not really, but the first time with Python and no memory of the C++ course from a few years before. The code worked, but it was incredibly long and of just if statements. Later on my spiral project I needed something to track how long it would run, but the code I had just was too long and I didn’t like it. So I quickly came up with this:
def timer(T1,T2):
print(' time: '+str(T2-T1)+' seconds')
print(' '+str((T2-T1)/60)+' (minutes)')
print(' '+str((((T2-T1)/60)/60))+' (hours)')
print('\n')
return
This was clearly shorter and somewhat better, and that was sufficient for a while, but eventually I wanted something even better. After some searching I discovered the divmod function that Python has. The divmod function returns the quotient and remainder of a number A when divided by a number B. For example, divmod(A,B) returns (1,15) when A is 75 and B is 60; 1 being the quotient of 75/60 and 15 being the remainder. With that in mind, I came up with this:
def DeltaT(sec):
"75 -> '1 minute, 15 seconds'\nfrom Orthallelous"
if sec==0:return 'no elasped time'
S,Q,c,time=[],abs(sec),0,''
N=['second','minute','hour','day']
for j in[60,60,24]:Q,re=divmod(Q,j);S.append(re)
S.append(Q)
for T in S:
if T==0:c+=1;continue
val=str(int(T))
if c==0 and int(T)!=T:val=str(round(T,2))
nam=N[c]
if T!=1:nam=''.join([nam,'s'])
time=''.join([val,' ',nam,', ',time]);c+=1
return time[:-2]
And here are a few variants:
def S(s):
S,N,c,t=[],['S','M','H','D'],0,''
for j in[60,60,24]:s,r=divmod(s,j);S.append(r)
S.append(s)
for T in S:
if T==0:c+=1;continue
t=''.join([str(int(T)),N[c],' ',t]);c+=1
return t.rstrip(' ')
def S(s): S,N,c,t=[],['S','M','H','D'],0,'' for j in[60,60,24]:s,r=divmod(int(s),j);S.append((str(r)+N[c]+' ')if r else'');c+=1 S.append((str(s)+N[c]+' ')if s else'');S.reverse();return''.join(S).rstrip()
def S(s): S,d,N,c,t=[],s,['s','m','h','d'],0,'' for j in [60,60,24]:d,r=divmod(d,j);S.append(str(r)+N[c]);c+=1 S.append(str(d)+N[c]);return reduce(lambda m,n:'%s %s'%(n,m),S)
And here is a super short obfuscated version (does not work with Python 3):
def S(s):exec("S,d,c,t=[],%i,0,''\nfor j in[60,60,24]:d,r=divmod(d,j);S+=[r]\nS+=[d]\nfor Q in S:t=`Q`+['s','m','h','d'][c]+' '+t;c+=1\nprint t"%s)
Okay, so how does it work? I’ll go through each line of the highlighted version:
"75 -> '1 minute, 15 seconds'\nfrom Orthallelous"
The documentation line. A line about what it does and where it’s from. This is also the line that Python’s help() function will return if you pass it this function.
if sec==0:return 'no elasped time'
This line is just to save some time. There’s no point in going through all the following code if 0 was given to the function. Also if this line wasn’t here you’d get an empty string.
S,Q,c,time=[],abs(sec),0,''
N=['second','minute','hour','day']
The variables that we need to use. Here S is defined to be an empty list that we’ll fill, Q is defined to be the absolute value of seconds that was given to the function (so no negative number of seconds), c is going to be the index of the N list and time is an empty string which we’ll put words into it later. N is the list of the units of time.
for j in[60,60,24]:Q,re=divmod(Q,j);S.append(re)
S.append(Q)
Here is where the number of seconds is broken into units of minutes, hours and days. Say for example the function was given the value (in seconds) 86400. Above, Q was defined to be this value. Here in the first iteration of the for loop the divmod is given (86400,60) and it returns (1440,0), in which Q is redefined as 1440 and the re (the remainder) is 0. Then re is stored in the S list. On the second iteration of the loop the divmod is now given (1440,60) and returns (24,0). The 0 is stored and Q is redefined as 24. On the last loop the divmod is given (24,24) and returns (1,0).
Now that the loop is finished and all the remainders are stored, whatever Q is now is also stored. The S list now looks like this: [0, 0, 0, 1]. Reading that from left to right would be the number of seconds, the number of minutes, the number of hours and finally the number of days. Now it’s time to give these values names.
for T in S:
if T==0:c+=1;continue
The first if statement here says to just move onto the next value in the S list if the current value for the iteration is 0. This way you don’t get something like ‘0 hours’. But just before it continues to the next iteration without doing any of the following code, it adds a 1 to c. Recall that c is the index for the N list (all the names) and this is to make sure the right unit name is assigned to the correct unit value.
val=str(int(T))
if c==0 and int(T)!=T:val=str(round(T,2))
Here the value is made to be a whole number just in case it isn’t (the int(T) part) and then made into a string. If the name index is 0 (‘second’) and the integer value of T is not the same as T, then round T to the nearest hundredth and use that instead. If you gave the function 5 you would get ‘5 seconds’; but if you gave it 5.45 you would get ‘5.45 seconds’ thanks to this line. The c==0 check is so this only happens to the seconds value.
nam=N[c]
if T!=1:nam=''.join([nam,'s'])
Now the name of the unit is finally acquired. If T isn’t 1, then the name needs to be a plural, so an ‘s’ is added to the name. The ”.join() function is faster at putting the two together then nam+’s’.
time=''.join([val,' ',nam,', ',time]);c+=1
The time variable obtains the value and the unit name in front of whatever it currently has. It’s done this way because the S list contains the values of seconds, minutes, hours and days in that order but the string should be read in the order of days, hours, minutes and seconds. The time string would look like this through each iteration if the function was given 234545:
‘5 seconds, ‘
‘9 minutes, 5 seconds, ‘
’17 hours, 9 minutes, 5 seconds, ‘
‘2 days, 17 hours, 9 minutes, 5 seconds, ‘
c is then advanced for the next unit name.
return time[:-2]
Finishing up is to return the now completed time string. If you noted above, the last two characters in the string are a comma and a space, which need to be trimmed off. This is what the [:-2] on the time variable does, by returning everything that it contains except the last two characters. So with the example value earlier (86400), that value has now become ‘1 day’.
I then extended this to handle larger values and more units of time. Then it… turned into a ‘Hey, get I get this under 4 kb?’ sort of thing. So it’s a bit difficult to read. It’s defined as a class instead of a def, so you’ll have to initialize it first:
d=Deltaizer()
Then you can pass any value of seconds to its strip function (I couldn’t think of a good name):
>>> d.Strip(453.778) '7 minutes, 33.78 seconds' >>> d.Strip(122212212) '3 years, 10 months, 2 weeks, 4 days, 18 hours, 22 minutes, 36 seconds' >>> d.Strip(60*60*24*365.2425) '1 year' >>> d.Strip(9999999999) '3 centuries, 1 decade, 6 years, 10 months, 3 weeks, 3 days, 2 hours, 39 minutes, 27 seconds'
It also has an Unstrip function, which can take a string returned by .Strip and convert it into a number of seconds. You can also use it to figure something out like how long 50 thousand hours is:
>>> d.Unstrip('7 minutes, 33.78 seconds')
453.78
>>> d.Strip(d.Unstrip('50000 hours'))
'5 years, 8 months, 2 weeks, 3 days, 2 hours, 54 minutes'
You can get it by copying the code below or downloading the .py file here.
class Deltaizer:
"From Orthallelous"
_year,_r,_galYr=31556952,2,230000#year in seconds, round float seconds to this many decimal fractions, how long a galactic year is(in centuries)
def Strip(self,seconds,just_days=False,just_years=False,short=False):
"""Converts a number of seconds into something more readable
Returns a string saying seconds, minutes, hours, etc
Set just_days to True to return a max time unit of days
Set just_years to True to return a max time unit of years
Set short to True to return a shortened form
A year is handled as 31556952 seconds (365.2425 days)
use .setYearLength to adjust the year length
use .setRoundSec to adjust how seconds are rounded, defaults to 2"""
sec=abs(seconds)
if just_days:return self._Namer(self._De(sec),short)
if just_years:dmy=self._weekMonth(sec);smhd=self._De(dmy[0]);return self._Namer(smhd+dmy[1:],short)
dwmy=self._weekMonth(sec);smhd=self._De(dwmy[0]);ydcmg=self._De(dwmy[-1],[10,10,10,self._galYr])
return self._Namer(smhd+dwmy[1:-1]+ydcmg,short)
def _Namer(self,striped,short=0):
if short:N,s,s1,s2,com,spc=self._shortnames()
else:N,s,s1,s2,com,spc=self._names()
time,ck='',0
for T in striped:
if T==0:ck+=1;continue
tmp=str(int(T))
if ck==0 and int(T)!=T:tmp=str(round(T,self._r))
tmp2=N[ck]
if T!=1:
tmp2=''.join([tmp2,s])
if ck==8:tmp2=s1
if ck==9:tmp2=s2
if short:tmp2=N[ck]
time=''.join([tmp,spc,tmp2,com,time]);ck+=1
return time.rstrip(', ')
def _De(self,sec,stp=[60,60,24]):
s,div=[],sec
for j in stp:div,re=divmod(div,j);s.append(re)
s.append(div);return s
def _weekMonth(self,sec):
w,re=[],sec
for j in [self._year,2592000,604800]:div,re=divmod(re,j);w.append(div)
w.append(re);w.reverse();return w
def _names(self):
N=['second','minute','hour','day','week','month','year','decade','century','millennium','galactic year']
s,s1,s2,com,spc='s','centuries','millennia',', ',' ';return N,s,s1,s2,com,spc
def _shortnames(self):
N=['s','m','h','d','w','mo','y','de','c','mi','gy']
s,s1,s2,com,spc='','','',' ','';return N,s,s1,s2,com,spc
def setYearLength(self,days):
"Set the length of a year (in Earth days)\nsee ._YearTypes for the years of planets in Earth days"
self._year=round(days*24*60*60);self._galYr = int(round(230000/(days/365.2425)))
def setRoundSec(self,R):
"Set how many digits the seconds are rounded to"
self._r=R
def _YearTypes(self):
'Sidereal years of the planets (in Earth days)'
yrs={'Mercury':87.9691,'Venus':224.698,'Earth':365.256363004,'Mars':686.971,
'Jupiter':4332.59,'Saturn':10759.22,'Uranus':30799.095,'Neptune':60190.03,'Pluto':89865.65};return yrs
def _decode(self,c,n,s):
d={}
for i in n:
for j in c:
if i in j:
if s:d[i]=j.replace(i,'');r=j
else:d[i]=j.split()[0];r=j
try:c.remove(r)
except:pass
return d
def Unstrip(self,string,short=False):
'Convert a string into a number of seconds\nTakes a string returned by .Strip and\nconverts it into a value in seconds'
t,J,E,s=[],1,[1,60,60,24],short
if s:N,s0,s1,s2,com,spc=self._shortnames()
else:
N,s0,s1,s2,com,spc=self._names()
for i in[0,1]:string=string.replace([s1,s2][i],N[8+i])
decode=self._decode(string.split(com),sorted(N,None,len,1),s)
for i in E:J*=i;t.append(J)
e=[10,10,10,self._galYr];J=1;sec=0
for i in e:J*=i;t.append(self._year*J)
times=dict(zip(N[:4]+N[7:],t));times.update(dict(zip(N[4:7],[604800,2592000,self._year])))
for i in decode:
T=decode[i]
if'.'in T:T=float(T)
else:T=int(T)
sec+=T*times[i]
return sec
————-
A note about the semicolon in Python: My understanding of it is that it lets you start a new line without actually starting a new line. So this
for j in[60,60,24]:qu,re=divmod(qu,j);S.append(re)
and this
for j in[60,60,24]:
qu,re=divmod(qu,j)
S.append(re)
are equivalent. But it only works if you’re not putting more than one statement in one line, so a line like
for i in someLoop:I=i+1;if I>75:break
isn’t valid.

Leave a comment