Most efficient way to convert string-time to time - python

I have to run about a million operations to do:
"Runtime": "01:12:00" --> datetime.time(1,12)
What would be the most performant way to do this? Right now I'm just doing a split on the semicolons, and the doing a datetime.time(...) --
s = '01:12:00'
h,m,s = [int(i) for i in s.split(':')
st = datetime.time(hour=h, minute=m, second=s)

Using the timeit module you could test different implementations yourself:
import datetime
import re
PAT = re.compile('(\d{2}):(\d{2}):(\d{2})')
TSTR = "01:12:00"
def fun1():
dt = datetime.datetime.strptime(TSTR, "%H:%M:%S")
return dt
def fun2():
h,m,s = [int(i) for i in TSTR.split(':')]
dt = datetime.time(hour=h, minute=m, second=s)
return dt
def fun3():
mat = PAT.match(TSTR)
dt = datetime.time(hour=int(mat.group(1)), minute=int(mat.group(2)), second=int(mat.group(3)))
return dt
def fun4():
h,m,s = int(TSTR[0:2]), int(TSTR[3:5]), int(TSTR[6:8])
dt = datetime.time(hour=h, minute=m, second=s)
return dt
if __name__ == "__main__":
import timeit
# Use the default repeat arguments: repeat=3, number=1000000
print(min(timeit.repeat("fun1()", setup="from __main__ import fun1"))) # 15.5739
print(min(timeit.repeat("fun2()", setup="from __main__ import fun2"))) # 3.4544
print(min(timeit.repeat("fun3()", setup="from __main__ import fun3"))) # 4.1829
print(min(timeit.repeat("fun4()", setup="from __main__ import fun4"))) # 2.8675
The fastest approach is in fun4. Your split method is next, followed closely (surprisingly, imo) by the regex approach, and straggling far behind is the strptime method.

In [48]: s = '"Runtime": "01:12:00"'
In [49]: dt.strptime(s, '"Runtime": "%H:%M:%S"')
Out[49]: datetime.datetime(1900, 1, 1, 1, 12)

>>> import time
>>> a='01:12:00'
>>> b=time.strptime(a,'%H:%M:%S') # use %I instead of %H if you use 12-hour clock
>>> b
time.struct_time(tm_year=1900, tm_mon=1, tm_mday=1, tm_hour=1, tm_min=12, tm_sec=0, tm_wday=0, tm_yday=1, tm_isdst=-1)
Then use b.tm_hour, b.tm_min and b.tm_sec to get hours, minutes and seconds.

I analyzed the performance of the regex method, the string.split to array method, and OP's method
It appears that split to array is faster than regex by about 38% and faster than OP's method by about 15%.
import time
import re
import datetime
timestring = "01:12:00"
# STRING.split method, stored temporarily in array
beforeMillis = int(round(time.time() * 1000))
for i in range(10000):
result = re.search(r"(\d{2}):(\d{2}):(\d{2})", timestring).groups()
theTime = datetime.time(int(result[0]), int(result[1]), int(result[2]))
afterMillis = int(round(time.time() * 1000))
print "Using Regex: " + str(afterMillis - beforeMillis) + "ms"
# regex method
beforeMillis = int(round(time.time() * 1000))
for i in range(10000):
result = timestring.split(":")
theTime = datetime.time(int(result[0]), int(result[1]), int(result[2]))
afterMillis = int(round(time.time() * 1000))
print "Using Split: " + str(afterMillis - beforeMillis) + "ms"
# STRING.split method, stored temporarily in three variables
beforeMillis = int(round(time.time() * 1000))
for i in range(10000):
h,m,s = [int(i) for i in timestring.split(':')]
theTime = datetime.time(hour=h, minute=m, second=s)
afterMillis = int(round(time.time() * 1000))
print "Using Split with 3 Variables: " + str(afterMillis - beforeMillis) + "ms"
Output:
$ python test.py
Using Regex: 52ms
Using Split: 34ms
Using Split with 3 Variables: 44ms
I don't think you'll find a much faster method than storing the split string in an array.
Storing the array temporarily is (a bit) faster than in three variables for a good reason: No further memory has to be used, and the compiler can probably optimize this easier.
All other answers (except for the one recommending regex) also fail to use datetime.time.
I recommend that you don't use the built-in time object for this purpose as it represents a unix time (seconds since Jan 1 1970), not a time of day.

Related

Python add digits on either side of ' : ' ie. 22:11 + 22:22 = 44:33

22:11 + 22:22 = 44:33
varible_A = ('22:11')
varible_B = ('22:11')
the numbers on the left(22) are minutes
the numbers on the right(11) are seconds
I'm trying to add the two numbers to get
total = 44:22
This is a Bonus but would really help me out*
is it possible to treat the digits like time for instance...
varible_A = ('22:50')
varible_B = ('22:30')
I would like to get 45:20
instead of
44:80
Use datetime.timedelta() to model time durations:
from datetime import timedelta
def to_delta(value):
minutes, seconds = map(int, value.split(':'))
return timedelta(minutes=minutes, seconds=seconds)
var_a = to_delta('22:50')
var_b = to_delta('22:30')
var_a + var_b
You can then turn a timedelta() object back to a minutes + seconds representation:
def to_minutes_seconds(delta):
return '{:02.0f}:{:02.0f}'.format(*divmod(delta.total_seconds(), 60))
Demo:
>>> var_a = to_delta('22:50')
>>> var_b = to_delta('22:30')
>>> var_a + var_b
datetime.timedelta(0, 2720)
>>> to_minutes_seconds(var_a + var_b)
'45:20'
Alternatively, the str() result of a timedelta is formatted as HH:MM:SS:
>>> str(var_a + var_b)
'00:45:20'
and may suit your needs too. Note that for deltas that present more than one hour, there is a difference between str() and to_minutes_seconds(); the former shows you hours, minutes and seconds, the latter just shows minutes, where the minutes value can be over 60. Deltas representing more than 24 hours gain an extra prefix for the number of days:
>>> str(timedelta(minutes=65, seconds=10))
'1:05:10'
>>> to_minutes_seconds(timedelta(minutes=65, seconds=10))
'65:10'
>>> str(timedelta(minutes=(60*24)+1, seconds=10))
'1 day, 0:01:10'
I think these should be represented by objects, rather than one line comprehensions etc... As such my suggestion is to use a class like follows:
class Time(object):
def __init__(self,minutes,seconds):
self.minutes = minutes
self.seconds = seconds
def __add__(self,other):
return Time(self.minutes+other.minutes,self.seconds+other.seconds)
def __str__(self):
return "{0}:{1}".format(self.minutes,self.seconds)
A = Time(22,11)
B = Time(22,22)
print(A+B)
Produces
>>>
44:33
You can Use similar function
def add_with_column(*args):
res = (0,0)
for data in args:
nums = map(int, data.split(':'))
res = [i + j for i,j in zip(res, nums)]
return ':'.join([str(res[0] + res[1]/60), str(res[1] % 60)])
>>> add_with_column('22:50', '22:30')
'45:20'
a = '22:50'
b = '22:30'
def add_time(a, b):
a = map(int, a.split(':'))
b = map(int, b.split(':'))
a[1] += b[1]
a[0] += b[0] + a[1]//60
a[1] %= 60
return '{}:{}'.format(*a)
print a, b, add_time(a,b)
Something like this:
>>> def add_time(t1,t2):
h1,s1 = map(int,t1.split(":"))
h2,s2 = map(int,t2.split(":"))
q,r = divmod(s1+s2, 60)
return "{0}:{1:02d}".format( h1+h2+q, r)
...
>>> add_time('22:30','22:50')
'45:20'
>>> add_time('22:30','22:30')
'45:00'
A simple way to do this without importing any external machinery would be:
def add(time1,time2):
t1 = map(int,time1.split(':')) # t1[0] is minutes, t1[1] is seconds
t2 = map(int,time2.split(':')) # map will apply int() to both elements of the array
m = t1[0]+t2[0]+(t1[1]+t2[1])/60 # New minutes; recall that 70/60=1 for integer division
s = (t1[1]+t2[1]) % 60 #new seconds
return str(m)+":"+str(s)
add('22:30','22:50') # prints '45:20'
Of course, you can always import from datetime (which is probably better for practical applications), but if you want to implement time operations by yourself, this is how you can do so.
You want to split the times into their components
def split_time(time):
return time.split(':')
and add them
def add_times(time1, time2):
minutes = time1[0] + time2[0]
seconds = time1[1] + time2[1]
return minutes, seconds
and do modular arithmetic to "carry the minute" when appropriate
def carry_the_minute(minutes, seconds):
minutes += seconds / 60
seconds %= 60
return minutes, seconds
and create a representation of the result
def represent_time(minutes, seconds):
"""Specifies two-character-wide seconds with leading 0."""
return "{0}:{1:02d}".format(minutes, seconds)
Putting it all together will look something like this:
def add_times(time1, time2):
time1 = split_time(time1)
time2 = split_time(time2)
minutes, seconds = add_times(time1, time2)
minutes, seconds = carry_the_minute(minutes, seconds)
print represent_time(minutes, seconds)

Format a datetime into a string with milliseconds

How can I format a datetime object as a string with milliseconds?
To get a date string with milliseconds, use [:-3] to trim the last three digits of %f (microseconds):
>>> from datetime import datetime
>>> datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
'2022-09-24 10:18:32.926'
Or slightly shorter:
>>> from datetime import datetime
>>> datetime.utcnow().strftime('%F %T.%f')[:-3]
With Python 3.6+, you can set isoformat's timespec:
>>> from datetime import datetime
>>> datetime.utcnow().isoformat(sep=' ', timespec='milliseconds')
'2019-05-10 09:08:53.155'
#Cabbi raised the issue that on some systems (Windows with Python 2.7), the microseconds format %f may incorrectly give "0", so it's not portable to simply trim the last three characters. Such systems do not follow the behavior specified by the documentation:
Directive
Meaning
Example
%f
Microsecond as a decimal number, zero-padded to 6 digits.
000000, 000001, …, 999999
The following code carefully formats a timestamp with milliseconds:
>>> from datetime import datetime
>>> (dt, micro) = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f').split('.')
>>> "%s.%03d" % (dt, int(micro) / 1000)
'2016-02-26 04:37:53.133'
To get the exact output that the OP wanted, we have to strip punctuation characters:
>>> from datetime import datetime
>>> (dt, micro) = datetime.utcnow().strftime('%Y%m%d%H%M%S.%f').split('.')
>>> "%s%03d" % (dt, int(micro) / 1000)
'20160226043839901'
Using strftime:
>>> from datetime import datetime
>>> datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
'20220402055654344968'
Use [:-3] to remove the 3 last characters since %f is for microseconds:
>>> from datetime import datetime
>>> datetime.now().strftime('%Y/%m/%d %H:%M:%S.%f')[:-3]
'2013/12/04 16:50:03.141'
import datetime
# convert string into date time format.
str_date = '2016-10-06 15:14:54.322989'
d_date = datetime.datetime.strptime(str_date , '%Y-%m-%d %H:%M:%S.%f')
print(d_date)
print(type(d_date)) # check d_date type.
# convert date time to regular format.
reg_format_date = d_date.strftime("%d %B %Y %I:%M:%S %p")
print(reg_format_date)
# some other date formats.
reg_format_date = d_date.strftime("%Y-%m-%d %I:%M:%S %p")
print(reg_format_date)
reg_format_date = d_date.strftime("%Y-%m-%d %H:%M:%S")
print(reg_format_date)
<<<<<< OUTPUT >>>>>>>
2016-10-06 15:14:54.322989
<class 'datetime.datetime'>
06 October 2016 03:14:54 PM
2016-10-06 03:14:54 PM
2016-10-06 15:14:54
In python 3.6 and above using python f-strings:
from datetime import datetime, timezone
dt = datetime.now(timezone.utc)
print(f"{dt:%Y-%m-%d %H:%M:%S}.{dt.microsecond // 1000:03d}")
The code specific to format milliseconds is:
{dt.microsecond // 1000:03d}
The format string {:03d} and microsecond to millisecond conversion // 1000 is from def _format_time in https://github.com/python/cpython/blob/master/Lib/datetime.py that is used for datetime.datetime.isoformat().
I assume you mean you're looking for something that is faster than datetime.datetime.strftime(), and are essentially stripping the non-alpha characters from a utc timestamp.
You're approach is marginally faster, and I think you can speed things up even more by slicing the string:
>>> import timeit
>>> t=timeit.Timer('datetime.utcnow().strftime("%Y%m%d%H%M%S%f")','''
... from datetime import datetime''')
>>> t.timeit(number=10000000)
116.15451288223267
>>> def replaceutc(s):
... return s\
... .replace('-','') \
... .replace(':','') \
... .replace('.','') \
... .replace(' ','') \
... .strip()
...
>>> t=timeit.Timer('replaceutc(str(datetime.datetime.utcnow()))','''
... from __main__ import replaceutc
... import datetime''')
>>> t.timeit(number=10000000)
77.96774983406067
>>> def sliceutc(s):
... return s[:4] + s[5:7] + s[8:10] + s[11:13] + s[14:16] + s[17:19] + s[20:]
...
>>> t=timeit.Timer('sliceutc(str(datetime.utcnow()))','''
... from __main__ import sliceutc
... from datetime import datetime''')
>>> t.timeit(number=10000000)
62.378515005111694
from datetime import datetime
from time import clock
t = datetime.utcnow()
print 't == %s %s\n\n' % (t,type(t))
n = 100000
te = clock()
for i in xrange(1):
t_stripped = t.strftime('%Y%m%d%H%M%S%f')
print clock()-te
print t_stripped," t.strftime('%Y%m%d%H%M%S%f')"
print
te = clock()
for i in xrange(1):
t_stripped = str(t).replace('-','').replace(':','').replace('.','').replace(' ','')
print clock()-te
print t_stripped," str(t).replace('-','').replace(':','').replace('.','').replace(' ','')"
print
te = clock()
for i in xrange(n):
t_stripped = str(t).translate(None,' -:.')
print clock()-te
print t_stripped," str(t).translate(None,' -:.')"
print
te = clock()
for i in xrange(n):
s = str(t)
t_stripped = s[:4] + s[5:7] + s[8:10] + s[11:13] + s[14:16] + s[17:19] + s[20:]
print clock()-te
print t_stripped," s[:4] + s[5:7] + s[8:10] + s[11:13] + s[14:16] + s[17:19] + s[20:] "
result
t == 2011-09-28 21:31:45.562000 <type 'datetime.datetime'>
3.33410112179
20110928212155046000 t.strftime('%Y%m%d%H%M%S%f')
1.17067364707
20110928212130453000 str(t).replace('-','').replace(':','').replace('.','').replace(' ','')
0.658806915404
20110928212130453000 str(t).translate(None,' -:.')
0.645189262881
20110928212130453000 s[:4] + s[5:7] + s[8:10] + s[11:13] + s[14:16] + s[17:19] + s[20:]
Use of translate() and slicing method run in same time
translate() presents the advantage to be usable in one line
Comparing the times on the basis of the first one:
1.000 * t.strftime('%Y%m%d%H%M%S%f')
0.351 * str(t).replace('-','').replace(':','').replace('.','').replace('
','')
0.198 * str(t).translate(None,' -:.')
0.194 * s[:4] + s[5:7] + s[8:10] + s[11:13] + s[14:16] + s[17:19] +
s[20:]
I dealt with the same problem but in my case it was important that the millisecond was rounded and not truncated
from datetime import datetime, timedelta
def strftime_ms(datetime_obj):
y,m,d,H,M,S = datetime_obj.timetuple()[:6]
ms = timedelta(microseconds = round(datetime_obj.microsecond/1000.0)*1000)
ms_date = datetime(y,m,d,H,M,S) + ms
return ms_date.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
python -c "from datetime import datetime; print str(datetime.now())[:-3]"
2017-02-09 10:06:37.006
datetime
t = datetime.datetime.now()
ms = '%s.%i' % (t.strftime('%H:%M:%S'), t.microsecond/1000)
print(ms)
14:44:37.134
The problem with datetime.utcnow() and other such solutions is that they are slow.
More efficient solution may look like this one:
def _timestamp(prec=0):
t = time.time()
s = time.strftime("%H:%M:%S", time.localtime(t))
if prec > 0:
s += ("%.9f" % (t % 1,))[1:2+prec]
return s
Where prec would be 3 in your case (milliseconds).
The function works up to 9 decimal places (please note number 9 in the 2nd formatting string).
If you'd like to round the fractional part, I'd suggest building "%.9f" dynamically with desired number of decimal places.
If you are prepared to store the time in a variable and do a little string manipulation, then you can actually do this without using the datetime module.
>>> _now = time.time()
>>> print ("Time : %s.%s\n" % (time.strftime('%x %X',time.localtime(_now)),
... str('%.3f'%_now).split('.')[1])) # Rounds to nearest millisecond
Time : 05/02/21 01:16:58.676
>>>
%.3f will round to out put the nearest millisecond, if you want more or less precision just change the number of decimal places
>>> print ("Time : %s.%s\n" % (time.strftime('%x %X',time.localtime(_now)),
... str('%.1f'%_now).split('.')[1])) # Rounds to nearest tenth of a second
Time : 05/02/21 01:16:58.7
>>>
Tested in Python 2.7 and 3.7 (obviously you need to leave out the brackets when calling print in version 2.x).
Field-width format specification
The UNIX date command allows specifying %3 to reduce the precision to 3 digits:
$ date '+%Y-%m-%d %H:%M:%S.%3N'
2022-01-01 00:01:23.456
Here's a custom function that can do that in Python:
from datetime import datetime
def strftime_(fmt: str, dt: datetime) -> str:
tokens = fmt.split("%")
tokens[1:] = [_format_token(dt, x) for x in tokens[1:]]
return "".join(tokens)
def _format_token(dt: datetime, token: str) -> str:
if len(token) == 0:
return ""
if token[0].isnumeric():
width = int(token[0])
s = dt.strftime(f"%{token[1]}")[:width]
return f"{s}{token[2:]}"
return dt.strftime(f"%{token}")
Example usage:
>>> strftime_("%Y-%m-%d %H:%M:%S.%3f", datetime.now())
'2022-01-01 00:01:23.456'
NOTE: %% is not supported.

Parsing hh:mm in Python

Sometimes I get a string like "02:40" indicating 2 hours and 40 minutes. I'd like to parse that string into the number of minutes (160 in this case) using Python.
Sure, I can parse the string and multiply the hours by 60, but is there something in the standard lib that does this?
Personally, I think simply parsing the string is far easier to read:
>>> s = '02:40'
>>> int(s[:-3]) * 60 + int(s[-2:])
160
Note that using negative indexing means it will handle strings without the leading zero on the hour:
>>> s = '2:40'
>>> int(s[:-3]) * 60 + int(s[-2:])
160
You could also use the split() function:
>>> hours, minutes = s.split(':')
>>> int(hours) * 60 + int(minutes)
160
Or use the map() function to convert the pieces to integers:
>>> hours, minutes = map(int, s.split(':'))
>>> hours * 60 + minutes
160
Speed
Using the timeit module indicates it is also faster than other methods proposed here:
>>> import timeit
>>> parsetime = timeit.timeit("mins = int(s[:-3]) * 60 + int(s[-2:])", "s='02:40'", number=100000) / 100000
>>> parsetime
9.018449783325196e-06
The split() method is a bit slower:
>>> splittime = timeit.timeit("hours,minutes = s.split(':'); mins=int(hours)*60 + int(minutes)", "s='02:40'", number=100000)/100000
>>> splittime
1.1217889785766602e-05
>>> splittime/parsetime
1.2438822697120402
And using map() a bit slower again:
>>> splitmaptime = timeit.timeit("hours,minutes = map(int, s.split(':')); mins=hours*60 + minutes", "s='02:40'", number=100000)/100000
>>> splitmaptime
1.3971350193023682e-05
>>> splitmaptime/parsetime
1.5491964282881776
John Machin's map and sum is about 2.4 times slower:
>>> summaptime = timeit.timeit('mins=sum(map(lambda x, y: x * y, map(int, "2:40".split(":")), [60, 1]))', "s='02:40'", number=100000) / 100000
>>> summaptime
2.1276121139526366e-05
>>> summaptime/parsetime
2.43
Chrono Kitsune's strptime()-based answer is ten times slower:
>>> strp = timeit.timeit("t=time.strptime(s, '%H:%M');mins=t.tm_hour * 60 + t.tm_min", "import time; s='02:40'", number=100000)/100000
>>> strp
9.0362770557403569e-05
>>> strp/parsetime
10.019767557444432
Other than the following, string parsing (or if you want to be even slower for something so simple, use the re module) is the only way I can think of if you rely on the standard library. TimeDelta doesn't seem to suit the task.
>>> import time
>>> x = "02:40"
>>> t = time.strptime(x, "%H:%M")
>>> minutes = t.tm_hour * 60 + t.tm_min
>>> minutes
160
See http://webcache.googleusercontent.com/search?q=cache:EAuL4vECPBEJ:docs.python.org/library/datetime.html+python+datetime&hl=en&client=firefox-a&gl=us&strip=1 since the main Python site is having problems.
The function you want is datetime.strptime or time.strptime, which create either a datetime or time object from a string with a time and another string describing the format.
If you want to not have to describe the format, use dateutil, http://labix.org/python-dateutil.
from dateutil.parser import parse
>>> d = parse('2009/05/13 19:19:30 -0400')
>>> d
datetime.datetime(2009, 5, 13, 19, 19, 30, tzinfo=tzoffset(None, -14400))
See How to parse dates with -0400 timezone string in python?
>>> sum(map(lambda x, y: x * y, map(int, "2:40".split(":")), [60, 1]))
160
I'm sure you can represent the given time as a TimeDelta object. From there I am sure there is an easy way to represent the TimeDelta in minutes.
There is:
from time import strptime
from calendar import timegm
T = '02:40'
t = timegm(strptime('19700101'+T,'%Y%m%d%H:%M'))
print t
But is this really better than brute calculus ?
.
An exotic solution, that doesn't need importing functions :
T = '02:40'
exec('x = %s' % T.replace(':','*60+'))
print x
edit: corrected second solution to obtain minutes, not seconds
.
Simplest solution
T = '02:40'
print int(T[0:2])*60 + int(T[3:])

Print Difference Between Time in Ms

I am reading a log file in my python script, and I have got a list of tuples of startTimes and endTimes -
('[19:49:40:680]', '[19:49:49:128]')
('[11:29:10:837]', '[11:29:15:698]')
('[11:30:18:291]', '[11:30:21:025]')
('[11:37:44:293]', '[11:38:02:008]')
('[11:39:14:897]', '[11:39:21:572]')
('[11:42:19:968]', '[11:42:22:036]')
('[11:43:18:887]', '[11:43:19:633]')
('[11:44:26:533]', '[11:49:29:274]')
('[11:55:03:974]', '[11:55:06:372]')
('[11:56:14:096]', '[11:56:14:493]')
('[11:57:08:372]', '[11:57:08:767]')
('[11:59:26:201]', '[11:59:27:438]')
How can I take a difference of the times in milliseconds?
>>> import datetime
>>> a = ('[19:49:40:680]', '[19:49:49:128]')
>>> start = datetime.datetime.strptime(a[0][:-1]+"000", "[%H:%M:%S:%f")
>>> end = datetime.datetime.strptime(a[1][:-1]+"000", "[%H:%M:%S:%f")
>>> delta = end-start
>>> ms = delta.seconds*1000 + delta.microseconds/1000
>>> ms
8448.0
This even works if the clock wraps around at midnight:
>>> a = ('[23:59:59:000]','[00:00:01:000]')
>>> # <snip> see above
>>> ms = delta.seconds*1000 + delta.microseconds/1000
>>> ms
2000.0
You can try the datetime package. (http://docs.python.org/library/datetime.html)
First read the time per strftime. (http://docs.python.org/library/datetime.html#strftime-strptime-behavior)
Then substract them, which should give you a timedeltaobject (http://docs.python.org/library/datetime.html#datetime.timedelta) in which you will find your millisecounds.
I thought it would be fun to see if this could be done in a oneliner. And yes, it can (split out for a faint attempt at readability):
interval = ('[19:49:40:680]', '[19:49:49:128]')
import datetime
(lambda td:
(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**3)\
(reduce(
lambda a, b: b - a,
[datetime.datetime.strptime(t[1:-1] + '000', '%H:%M:%S:%f')
for t in interval]))
This is Python 2.6. In 2.7 it can be shortened using timedelta.total_seconds(). In Python 3, the reduce() function must be imported from somewhere.

Convert seconds to hh:mm:ss in Python [duplicate]

This question already has answers here:
How do I convert seconds to hours, minutes and seconds?
(18 answers)
Closed 9 years ago.
How do I convert an int (number of seconds) to the formats mm:ss or hh:mm:ss?
I need to do this with Python code (and if possible in a Django template).
I can't believe any of the many answers gives what I'd consider the "one obvious way to do it" (and I'm not even Dutch...!-) -- up to just below 24 hours' worth of seconds (86399 seconds, specifically):
>>> import time
>>> time.strftime('%H:%M:%S', time.gmtime(12345))
'03:25:45'
Doing it in a Django template's more finicky, since the time filter supports a funky time-formatting syntax (inspired, I believe, from PHP), and also needs the datetime module, and a timezone implementation such as pytz, to prep the data. For example:
>>> from django import template as tt
>>> import pytz
>>> import datetime
>>> tt.Template('{{ x|time:"H:i:s" }}').render(
... tt.Context({'x': datetime.datetime.fromtimestamp(12345, pytz.utc)}))
u'03:25:45'
Depending on your exact needs, it might be more convenient to define a custom filter for this formatting task in your app.
>>> a = datetime.timedelta(seconds=65)
datetime.timedelta(0, 65)
>>> str(a)
'0:01:05'
Read up on the datetime module.
SilentGhost's answer has the details my answer leaves out and is reposted here:
>>> a = datetime.timedelta(seconds=65)
datetime.timedelta(0, 65)
>>> str(a)
'0:01:05'
Code that does what was requested, with examples, and showing how cases he didn't specify are handled:
def format_seconds_to_hhmmss(seconds):
hours = seconds // (60*60)
seconds %= (60*60)
minutes = seconds // 60
seconds %= 60
return "%02i:%02i:%02i" % (hours, minutes, seconds)
def format_seconds_to_mmss(seconds):
minutes = seconds // 60
seconds %= 60
return "%02i:%02i" % (minutes, seconds)
minutes = 60
hours = 60*60
assert format_seconds_to_mmss(7*minutes + 30) == "07:30"
assert format_seconds_to_mmss(15*minutes + 30) == "15:30"
assert format_seconds_to_mmss(1000*minutes + 30) == "1000:30"
assert format_seconds_to_hhmmss(2*hours + 15*minutes + 30) == "02:15:30"
assert format_seconds_to_hhmmss(11*hours + 15*minutes + 30) == "11:15:30"
assert format_seconds_to_hhmmss(99*hours + 15*minutes + 30) == "99:15:30"
assert format_seconds_to_hhmmss(500*hours + 15*minutes + 30) == "500:15:30"
You can--and probably should--store this as a timedelta rather than an int, but that's a separate issue and timedelta doesn't actually make this particular task any easier.
You can calculate the number of minutes and hours from the number of seconds by simple division:
seconds = 12345
minutes = seconds // 60
hours = minutes // 60
print "%02d:%02d:%02d" % (hours, minutes % 60, seconds % 60)
print "%02d:%02d" % (minutes, seconds % 60)
Here // is Python's integer division.
If you use divmod, you are immune to different flavors of integer division:
# show time strings for 3800 seconds
# easy way to get mm:ss
print "%02d:%02d" % divmod(3800, 60)
# easy way to get hh:mm:ss
from functools import reduce
print "%02d:%02d:%02d" % \
reduce(lambda ll,b : divmod(ll[0],b) + ll[1:],
[(3800,),60,60])
# function to convert floating point number of seconds to
# hh:mm:ss.sss
def secondsToStr(t):
return "%02d:%02d:%02d.%03d" % \
reduce(lambda ll,b : divmod(ll[0],b) + ll[1:],
[(round(t*1000),),1000,60,60])
print secondsToStr(3800.123)
Prints:
63:20
01:03:20
01:03:20.123
Just be careful when dividing by 60: division between integers returns an integer ->
12/60 = 0 unless you import division from future.
The following is copy and pasted from Python 2.6.2:
IDLE 2.6.2
>>> 12/60
0
>>> from __future__ import division
>>> 12/60
0.20000000000000001
Not being a Python person, but the easiest without any libraries is just:
total = 3800
seconds = total % 60
total = total - seconds
hours = total / 3600
total = total - (hours * 3600)
mins = total / 60
If you need to do this a lot, you can precalculate all possible strings for number of seconds in a day:
try:
from itertools import product
except ImportError:
def product(*seqs):
if len(seqs) == 1:
for p in seqs[0]:
yield p,
else:
for s in seqs[0]:
for p in product(*seqs[1:]):
yield (s,) + p
hhmmss = []
for (h, m, s) in product(range(24), range(60), range(60)):
hhmmss.append("%02d:%02d:%02d" % (h, m, s))
Now conversion of seconds to format string is a fast indexed lookup:
print hhmmss[12345]
prints
'03:25:45'
EDIT:
Updated to 2020, removing Py2 compatibility ugliness, and f-strings!
import sys
from itertools import product
hhmmss = [f"{h:02d}:{m:02d}:{s:02d}"
for h, m, s in product(range(24), range(60), range(60))]
# we can still just index into the list, but define as a function
# for common API with code below
seconds_to_str = hhmmss.__getitem__
print(seconds_to_str(12345))
How much memory does this take? sys.getsizeof of a list won't do, since it will just give us the size of the list and its str refs, but not include the memory of the strs themselves:
# how big is a list of 24*60*60 8-character strs?
list_size = sys.getsizeof(hhmmss) + sum(sys.getsizeof(s) for s in hhmmss)
print("{:,}".format(list_size))
prints:
5,657,616
What if we just had one big str? Every value is exactly 8 characters long, so we can slice into this str and get the correct str for second X of the day:
hhmmss_str = ''.join([f"{h:02d}:{m:02d}:{s:02d}"
for h, m, s in product(range(24),
range(60),
range(60))])
def seconds_to_str(n):
loc = n * 8
return hhmmss_str[loc: loc+8]
print(seconds_to_str(12345))
Did that save any space?
# how big is a str of 24*60*60*8 characters?
str_size = sys.getsizeof(hhmmss_str)
print("{:,}".format(str_size))
prints:
691,249
Reduced to about this much:
print(str_size / list_size)
prints:
0.12218026108523448
On the performance side, this looks like a classic memory vs. CPU tradeoff:
import timeit
print("\nindex into pre-calculated list")
print(timeit.timeit("hhmmss[6]", '''from itertools import product; hhmmss = [f"{h:02d}:{m:02d}:{s:02d}"
for h, m, s in product(range(24),
range(60),
range(60))]'''))
print("\nget slice from pre-calculated str")
print(timeit.timeit("hhmmss_str[6*8:7*8]", '''from itertools import product; hhmmss_str=''.join([f"{h:02d}:{m:02d}:{s:02d}"
for h, m, s in product(range(24),
range(60),
range(60))])'''))
print("\nuse datetime.timedelta from stdlib")
print(timeit.timeit("timedelta(seconds=6)", "from datetime import timedelta"))
print("\ninline compute of h, m, s using divmod")
print(timeit.timeit("n=6;m,s=divmod(n,60);h,m=divmod(m,60);f'{h:02d}:{m:02d}:{s:02d}'"))
On my machine I get:
index into pre-calculated list
0.0434853
get slice from pre-calculated str
0.1085147
use datetime.timedelta from stdlib
0.7625738
inline compute of h, m, s using divmod
2.0477764
Besides the fact that Python has built in support for dates and times (see bigmattyh's response), finding minutes or hours from seconds is easy:
minutes = seconds / 60
hours = minutes / 60
Now, when you want to display minutes or seconds, MOD them by 60 so that they will not be larger than 59

Categories